1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.media.tests;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.device.ITestDevice;
24 import com.android.tradefed.log.LogUtil.CLog;
25 import com.android.tradefed.result.FileInputStreamSource;
26 import com.android.tradefed.result.ITestInvocationListener;
27 import com.android.tradefed.result.InputStreamSource;
28 import com.android.tradefed.result.LogDataType;
29 import com.android.tradefed.testtype.IDeviceTest;
30 import com.android.tradefed.testtype.IRemoteTest;
31 import com.android.tradefed.util.FileUtil;
32 import com.android.tradefed.util.StreamUtil;
33 import com.android.tradefed.util.proto.TfMetricProtoUtil;
34 
35 import org.junit.Assert;
36 
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.ListIterator;
45 import java.util.Map;
46 import java.util.concurrent.TimeUnit;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49 
50 /**
51  * Runs the Media stress testcases.
52  * FIXME: more details
53  * <p/>
54  * Note that this test will not run properly unless /sdcard is mounted and writable.
55  */
56 public class MediaStressTest implements IDeviceTest, IRemoteTest {
57 
58     ITestDevice mTestDevice = null;
59     private static final String METRICS_RUN_NAME = "VideoRecordingStress";
60 
61     //Max test timeout - 2 hrs
62     private static final int MAX_TEST_TIMEOUT = 2 * 60 * 60 * 1000;
63 
64     // Constants for running the tests
65     private static final String TEST_CLASS_NAME =
66             "com.android.mediaframeworktest.stress.MediaRecorderStressTest";
67     private static final String TEST_PACKAGE_NAME = "com.android.mediaframeworktest";
68     private static final String TEST_RUNNER_NAME = ".MediaRecorderStressTestRunner";
69 
70     // Constants for parsing the output file
71     private static final Pattern EXPECTED_LOOP_COUNT_PATTERN =
72             Pattern.compile("Total number of loops:\\s*(\\d+)");
73     private static final Pattern ACTUAL_LOOP_COUNT_PATTERN =
74             Pattern.compile("No of loop:.*,\\s*(\\d+)\\s*");
75     private static final String OUTPUT_PATH = "mediaStressOutput.txt";
76 
77     @Override
run(ITestInvocationListener listener)78     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
79         Assert.assertNotNull(mTestDevice);
80 
81         IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(TEST_PACKAGE_NAME,
82                 TEST_RUNNER_NAME, mTestDevice.getIDevice());
83         runner.setClassName(TEST_CLASS_NAME);
84         runner.setMaxTimeToOutputResponse(MAX_TEST_TIMEOUT, TimeUnit.MILLISECONDS);
85 
86         cleanTmpFiles();
87         mTestDevice.runInstrumentationTests(runner, listener);
88         logOutputFile(listener);
89         cleanTmpFiles();
90     }
91 
92     /**
93      * Clean up temp files from test runs
94      */
cleanTmpFiles()95     private void cleanTmpFiles() throws DeviceNotAvailableException {
96         String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
97         mTestDevice.executeShellCommand(String.format("rm %s/temp*.3gp", extStore));
98         mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, OUTPUT_PATH));
99     }
100 
101     /**
102      * Pull the output file from the device, add it to the logs, and also parse out the relevant
103      * test metrics and report them.
104      */
logOutputFile(ITestInvocationListener listener)105     private void logOutputFile(ITestInvocationListener listener)
106             throws DeviceNotAvailableException {
107         File outputFile = null;
108         InputStreamSource outputSource = null;
109         try {
110             outputFile = mTestDevice.pullFileFromExternal(OUTPUT_PATH);
111 
112             if (outputFile == null) {
113                 return;
114             }
115 
116             CLog.d("Sending %d byte file %s into the logosphere!", outputFile.length(), outputFile);
117             outputSource = new FileInputStreamSource(outputFile);
118             listener.testLog(OUTPUT_PATH, LogDataType.TEXT, outputSource);
119             parseOutputFile(outputFile, listener);
120         } finally {
121             FileUtil.deleteFile(outputFile);
122             StreamUtil.cancel(outputSource);
123         }
124     }
125 
126     /**
127      * Parse the relevant metrics from the Instrumentation test output file
128      */
parseOutputFile(File outputFile, ITestInvocationListener listener)129     private void parseOutputFile(File outputFile, ITestInvocationListener listener) {
130         Map<String, String> runMetrics = new HashMap<>();
131         Map<String, String> stanzaKeyMap = new HashMap<>();
132         stanzaKeyMap.put("testStressRecordVideoAndPlayback1080P", "VideoRecordPlayback1080P");
133         stanzaKeyMap.put("testStressRecordVideoAndPlayback720P", "VideoRecordPlayback720P");
134         stanzaKeyMap.put("testStressRecordVideoAndPlayback480P", "VideoRecordPlayback480P");
135         stanzaKeyMap.put("testStressTimeLapse", "TimeLapseRecord");
136 
137         // try to parse it
138         String contents;
139         try {
140             InputStream dataStream = new FileInputStream(outputFile);
141             contents = StreamUtil.getStringFromStream(dataStream);
142         } catch (IOException e) {
143             CLog.e("IOException while parsing the output file:");
144             CLog.e(e);
145             return;
146         }
147 
148         List<String> lines = Arrays.asList(contents.split("\n"));
149         ListIterator<String> lineIter = lines.listIterator();
150         String line;
151         while (lineIter.hasNext()) {
152             line = lineIter.next();
153             String key = null;
154 
155             if (stanzaKeyMap.containsKey(line)) {
156                 key = stanzaKeyMap.get(line);
157             } else {
158                 CLog.d("Got unexpected line: %s", line);
159                 continue;
160             }
161 
162             Integer countExpected = getIntFromOutput(lineIter, EXPECTED_LOOP_COUNT_PATTERN);
163             Integer countActual = getIntFromOutput(lineIter, ACTUAL_LOOP_COUNT_PATTERN);
164             int value = coalesceLoopCounts(countActual, countExpected);
165             runMetrics.put(key, Integer.toString(value));
166         }
167 
168         reportMetrics(listener, runMetrics);
169     }
170 
171     /**
172      * Report run metrics by creating an empty test run to stick them in
173      * <p />
174      * Exposed for unit testing
175      */
reportMetrics(ITestInvocationListener listener, Map<String, String> metrics)176     void reportMetrics(ITestInvocationListener listener, Map<String, String> metrics) {
177         // Create an empty testRun to report the parsed runMetrics
178         CLog.d("About to report metrics: %s", metrics);
179         listener.testRunStarted(METRICS_RUN_NAME, 0);
180         listener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(metrics));
181     }
182 
183     /**
184      * Use the provided {@link Pattern} to parse a number out of the output file
185      */
getIntFromOutput(ListIterator<String> lineIter, Pattern numPattern)186     private Integer getIntFromOutput(ListIterator<String> lineIter, Pattern numPattern) {
187         Integer retval = null;
188         String line = null;
189         if (lineIter.hasNext()) {
190             line = lineIter.next();
191             Matcher m = numPattern.matcher(line);
192             if (m.matches()) {
193                 retval = Integer.parseInt(m.group(1));
194             } else {
195                 CLog.e("Couldn't match pattern %s against line '%s'", numPattern, line);
196             }
197         } else {
198             CLog.e("Encounted EOF while trying to match pattern %s", numPattern);
199         }
200 
201         return retval;
202     }
203 
204     /**
205      * Given an actual and an expected iteration count, determine a single metric to report.
206      */
coalesceLoopCounts(Integer actual, Integer expected)207     private int coalesceLoopCounts(Integer actual, Integer expected) {
208         if (expected == null || expected <= 0) {
209             return -1;
210         } else if (actual == null) {
211             return expected;
212         } else {
213             return actual;
214         }
215     }
216 
217     @Override
setDevice(ITestDevice device)218     public void setDevice(ITestDevice device) {
219         mTestDevice = device;
220     }
221 
222     @Override
getDevice()223     public ITestDevice getDevice() {
224         return mTestDevice;
225     }
226 }
227