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.Log;
21 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
22 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
23 import com.android.tradefed.config.Option;
24 import com.android.tradefed.config.Option.Importance;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.result.BugreportCollector;
28 import com.android.tradefed.result.BugreportCollector.Freq;
29 import com.android.tradefed.result.BugreportCollector.Noun;
30 import com.android.tradefed.result.BugreportCollector.Relation;
31 import com.android.tradefed.result.FileInputStreamSource;
32 import com.android.tradefed.result.ITestInvocationListener;
33 import com.android.tradefed.result.InputStreamSource;
34 import com.android.tradefed.result.LogDataType;
35 import com.android.tradefed.testtype.IDeviceTest;
36 import com.android.tradefed.testtype.IRemoteTest;
37 import com.android.tradefed.util.FileUtil;
38 import com.android.tradefed.util.RegexTrie;
39 import com.android.tradefed.util.StreamUtil;
40 import com.android.tradefed.util.proto.TfMetricProtoUtil;
41 
42 import org.junit.Assert;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.ListIterator;
53 import java.util.Map;
54 import java.util.concurrent.TimeUnit;
55 
56 /**
57  * Runs the Media Player stress test. This test will play the video files under
58  * the /sdcard/samples folder and capture the video playback event statistics in
59  * a text file under /sdcard/PlaybackTestResult.txt
60  * <p/>
61  * Note that this test will not run properly unless /sdcard is mounted and
62  * writable.
63  */
64 public class MediaPlayerStressTest implements IDeviceTest, IRemoteTest {
65     private static final String LOG_TAG = "MediaPlayerStress";
66 
67     ITestDevice mTestDevice = null;
68     @Option(name = "test-class", importance = Importance.ALWAYS)
69     private String mTestClassName =
70             "com.android.mediaframeworktest.stress.MediaPlayerStressTest";
71     @Option(name = "metrics-name", importance = Importance.ALWAYS)
72     private String mMetricsRunName = "MediaPlayerStress";
73     @Option(name = "result-file", importance = Importance.ALWAYS)
74     private String mOutputPath = "PlaybackTestResult.txt";
75 
76     //Max test timeout - 10 hrs
77     private static final int MAX_TEST_TIMEOUT = 10 * 60 * 60 * 1000;
78 
79     // Constants for running the tests
80     private static final String TEST_PACKAGE_NAME = "com.android.mediaframeworktest";
81     private static final String TEST_RUNNER_NAME = ".MediaPlayerStressTestRunner";
82 
83     public RegexTrie<String> mPatternMap = new RegexTrie<>();
84 
MediaPlayerStressTest()85     public MediaPlayerStressTest() {
86         mPatternMap.put("PlaybackPass", "^Total Complete: (\\d+)");
87         mPatternMap.put("PlaybackCrash", "^Total Error: (\\d+)");
88         mPatternMap.put("TrackLagging", "^Total Track Lagging: (\\d+)");
89         mPatternMap.put("BadInterleave", "^Total Bad Interleaving: (\\d+)");
90         mPatternMap.put("FailedToCompleteWithNoError",
91                 "^Total Failed To Complete With No Error: (\\d+)");
92     }
93 
94     @Override
run(ITestInvocationListener listener)95     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
96         Assert.assertNotNull(mTestDevice);
97         IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(TEST_PACKAGE_NAME,
98                 TEST_RUNNER_NAME, mTestDevice.getIDevice());
99         runner.setClassName(mTestClassName);
100         runner.setMaxTimeToOutputResponse(MAX_TEST_TIMEOUT, TimeUnit.MILLISECONDS);
101 
102         BugreportCollector bugListener = new BugreportCollector(listener,
103                 mTestDevice);
104         bugListener.addPredicate(BugreportCollector.AFTER_FAILED_TESTCASES);
105         bugListener.setDescriptiveName("media_player_stress_test");
106         bugListener.addPredicate(new BugreportCollector.Predicate(
107                 Relation.AFTER, Freq.EACH, Noun.TESTRUN));
108 
109         mTestDevice.runInstrumentationTests(runner, bugListener);
110 
111         logOutputFile(listener);
112         cleanResultFile();
113     }
114 
115     /**
116      * Clean up the test result file from test run
117      */
cleanResultFile()118     private void cleanResultFile() throws DeviceNotAvailableException {
119         String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
120         mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, mOutputPath));
121     }
122 
123     /**
124      * Pull the output file from the device, add it to the logs, and also parse
125      * out the relevant test metrics and report them.
126      */
logOutputFile(ITestInvocationListener listener)127     private void logOutputFile(ITestInvocationListener listener)
128             throws DeviceNotAvailableException {
129         File outputFile = null;
130         InputStreamSource outputSource = null;
131         try {
132             outputFile = mTestDevice.pullFileFromExternal(mOutputPath);
133 
134             if (outputFile == null) {
135                 return;
136             }
137 
138             // Upload a verbatim copy of the output file
139             Log.d(LOG_TAG, String.format("Sending %d byte file %s into the logosphere!",
140                     outputFile.length(), outputFile));
141             outputSource = new FileInputStreamSource(outputFile);
142             listener.testLog(mOutputPath, LogDataType.TEXT, outputSource);
143             // Parse the output file to upload aggregated metrics
144             parseOutputFile(new FileInputStream(outputFile), listener);
145         } catch (IOException e) {
146             Log.e(LOG_TAG, String.format(
147                 "IOException while reading or parsing output file: %s", e));
148         } finally {
149             FileUtil.deleteFile(outputFile);
150             StreamUtil.cancel(outputSource);
151         }
152     }
153 
154     /**
155      * Parse the relevant metrics from the Instrumentation test output file
156      */
parseOutputFile(InputStream dataStream, ITestInvocationListener listener)157     private void parseOutputFile(InputStream dataStream,
158             ITestInvocationListener listener) {
159         Map<String, String> runMetrics = new HashMap<>();
160 
161         // try to parse it
162         String contents;
163         try {
164             contents = StreamUtil.getStringFromStream(dataStream);
165         } catch (IOException e) {
166             Log.e(LOG_TAG, String.format(
167                     "Got IOException during test processing: %s", e));
168             return;
169         }
170 
171         List<String> lines = Arrays.asList(contents.split("\n"));
172         ListIterator<String> lineIter = lines.listIterator();
173         String line;
174         while (lineIter.hasNext()) {
175             line = lineIter.next();
176             List<List<String>> capture = new ArrayList<>(1);
177             String key = mPatternMap.retrieve(capture, line);
178             if (key != null) {
179                 Log.d(LOG_TAG, String.format("Got '%s' and captures '%s'",
180                         key, capture.toString()));
181             } else if (line.isEmpty()) {
182                 // ignore
183                 continue;
184             } else {
185                 Log.d(LOG_TAG, String.format("Got unmatched line: %s", line));
186                 continue;
187             }
188             runMetrics.put(key, capture.get(0).get(0));
189         }
190         reportMetrics(listener, runMetrics);
191     }
192 
193     /**
194      * Report run metrics by creating an empty test run to stick them in
195      * <p />
196      * Exposed for unit testing
197      */
reportMetrics(ITestInvocationListener listener, Map<String, String> metrics)198     void reportMetrics(ITestInvocationListener listener, Map<String, String> metrics) {
199         Log.d(LOG_TAG, String.format("About to report metrics: %s", metrics));
200         listener.testRunStarted(mMetricsRunName, 0);
201         listener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(metrics));
202     }
203 
204     @Override
setDevice(ITestDevice device)205     public void setDevice(ITestDevice device) {
206         mTestDevice = device;
207     }
208 
209     @Override
getDevice()210     public ITestDevice getDevice() {
211         return mTestDevice;
212     }
213 }
214