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 package com.android.wireless.tests;
17 
18 import com.android.ddmlib.IDevice;
19 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
21 import com.android.tradefed.config.Option;
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.BugreportCollector;
26 import com.android.tradefed.result.FileInputStreamSource;
27 import com.android.tradefed.result.ITestInvocationListener;
28 import com.android.tradefed.result.InputStreamSource;
29 import com.android.tradefed.result.LogDataType;
30 import com.android.tradefed.testtype.IDeviceTest;
31 import com.android.tradefed.testtype.IRemoteTest;
32 import com.android.tradefed.util.FileUtil;
33 import com.android.tradefed.util.RegexTrie;
34 import com.android.tradefed.util.RunUtil;
35 import com.android.tradefed.util.StreamUtil;
36 import com.android.tradefed.util.proto.TfMetricProtoUtil;
37 
38 import org.junit.Assert;
39 
40 import java.io.BufferedReader;
41 import java.io.File;
42 import java.io.FileReader;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.concurrent.TimeUnit;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51 
52 /**
53  * Run the WiFi stress tests. This test stresses WiFi soft ap, WiFi scanning and WiFi reconnection
54  * in which device switches between cellular and WiFi connection.
55  */
56 public class WifiStressTest implements IRemoteTest, IDeviceTest {
57     private ITestDevice mTestDevice = null;
58     private static final long START_TIMER = 5 * 60 * 1000; // 5 minutes
59     // Define instrumentation test package and runner.
60     private static final String TEST_PACKAGE_NAME = "com.android.connectivitymanagertest";
61     private static final String TEST_RUNNER_NAME = ".ConnectivityManagerStressTestRunner";
62 
63     private static final Pattern ITERATION_PATTERN =
64             Pattern.compile("^iteration (\\d+) out of (\\d+)");
65     private static final int AP_TEST_TIMER = 3 * 60 * 60 * 1000; // 3 hours
66     private static final int SCAN_TEST_TIMER = 30 * 60 * 1000; // 30 minutes
67     private static final int RECONNECT_TEST_TIMER = 12 * 60 * 60 * 1000; // 12 hours
68 
69     private String mOutputFile = "WifiStressTestOutput.txt";
70 
71     /**
72      * Stores the test cases that we should consider running.
73      *
74      * <p>This currently consists of "ap", "scanning", and "reconnection" tests.
75      */
76     private List<TestInfo> mTestList = null;
77 
78     private static class TestInfo {
79         public String mTestName = null;
80         public String mTestClass = null;
81         public String mTestMethod = null;
82         public String mTestMetricsName = null;
83         public int mTestTimer;
84         public RegexTrie<String> mPatternMap = null;
85 
86         @Override
toString()87         public String toString() {
88             return String.format(
89                     "TestInfo: mTestName(%s), mTestClass(%s), mTestMethod(%s),"
90                             + " mTestMetricsName(%s), mPatternMap(%s), mTestTimer(%d)",
91                     mTestName,
92                     mTestClass,
93                     mTestMethod,
94                     mTestMetricsName,
95                     mPatternMap.toString(),
96                     mTestTimer);
97         }
98     }
99 
100     @Option(
101             name = "ap-iteration",
102             description = "The number of iterations to run soft ap stress test")
103     private String mApIteration = "0";
104 
105     @Option(name = "idle-time", description = "The device idle time after screen off")
106     private String mIdleTime = "30"; // 30 seconds
107 
108     @Option(
109             name = "reconnect-iteration",
110             description = "The number of iterations to run WiFi reconnection stress test")
111     private String mReconnectionIteration = "100";
112 
113     @Option(
114             name = "reconnect-password",
115             description = "The password for the above ssid in WiFi reconnection stress test")
116     private String mReconnectionPassword = "androidwifi";
117 
118     @Option(name = "reconnect-ssid", description = "The ssid for WiFi recoonection stress test")
119     private String mReconnectionSsid = "securenetdhcp";
120 
121     @Option(
122             name = "reconnection-test",
123             description = "Option to run the wifi reconnection stress test")
124     private boolean mReconnectionTestFlag = true;
125 
126     @Option(
127             name = "scan-iteration",
128             description = "The number of iterations to run WiFi scanning test")
129     private String mScanIteration = "100";
130 
131     @Option(name = "scan-test", description = "Option to run the scan stress test")
132     private boolean mScanTestFlag = true;
133 
134     @Option(
135             name = "skip-set-device-screen-timeout",
136             description = "Option to skip screen timeout configuration")
137     private boolean mSkipSetDeviceScreenTimeout = false;
138 
139     @Option(name = "tether-test", description = "Option to run the tethering stress test")
140     private boolean mTetherTestFlag = true;
141 
142     @Option(name = "wifi-only")
143     private boolean mWifiOnly = false;
144 
setupTests()145     private void setupTests() {
146         if (mTestList != null) {
147             return;
148         }
149         mTestList = new ArrayList<>(3);
150 
151         // Add WiFi scanning test
152         TestInfo t = new TestInfo();
153         t.mTestName = "WifiScanning";
154         t.mTestClass = "com.android.connectivitymanagertest.stress.WifiStressTest";
155         t.mTestMethod = "testWifiScanning";
156         t.mTestMetricsName = "wifi_scan_performance";
157         t.mTestTimer = SCAN_TEST_TIMER;
158         t.mPatternMap = new RegexTrie<>();
159         t.mPatternMap.put("avg_scan_time", "^average scanning time is (\\d+)");
160         t.mPatternMap.put("scan_quality", "ssid appear (\\d+) out of (\\d+) scan iterations");
161         if (mScanTestFlag) {
162             mTestList.add(t);
163         }
164 
165         // Add WiFi reconnection test
166         t = new TestInfo();
167         t.mTestName = "WifiReconnectionStress";
168         t.mTestClass = "com.android.connectivitymanagertest.stress.WifiStressTest";
169         t.mTestMethod = "testWifiReconnectionAfterSleep";
170         t.mTestMetricsName = "wifi_stress";
171         t.mTestTimer = RECONNECT_TEST_TIMER;
172         t.mPatternMap = new RegexTrie<>();
173         t.mPatternMap.put("wifi_reconnection_stress", ITERATION_PATTERN);
174         if (mReconnectionTestFlag) {
175             mTestList.add(t);
176         }
177     }
178 
179     /**
180      * Configure screen timeout property
181      *
182      * @throws DeviceNotAvailableException
183      */
setDeviceScreenTimeout()184     private void setDeviceScreenTimeout() throws DeviceNotAvailableException {
185         // Set device screen_off_timeout as svc power can be set to false in the Wi-Fi test
186         String command =
187                 ("sqlite3 /data/data/com.android.providers.settings/databases/settings.db "
188                         + "\"UPDATE system SET value=\'600000\' WHERE name=\'screen_off_timeout\';\"");
189         CLog.d("Command to set screen timeout value to 10 minutes: %s", command);
190         mTestDevice.executeShellCommand(command);
191 
192         // reboot to allow the setting to take effect, post setup will be taken care by the reboot
193         mTestDevice.reboot();
194     }
195 
196     /**
197      * Enable/disable screen never timeout property
198      *
199      * @param on
200      * @throws DeviceNotAvailableException
201      */
setScreenProperty(boolean on)202     private void setScreenProperty(boolean on) throws DeviceNotAvailableException {
203         CLog.d("set svc power stay on " + on);
204         mTestDevice.executeShellCommand("svc power stayon " + on);
205     }
206 
207     @Override
setDevice(ITestDevice testDevice)208     public void setDevice(ITestDevice testDevice) {
209         mTestDevice = testDevice;
210     }
211 
212     @Override
getDevice()213     public ITestDevice getDevice() {
214         return mTestDevice;
215     }
216 
217     /** Run the Wi-Fi stress test Collect results and post results to dashboard */
218     @Override
run(ITestInvocationListener standardListener)219     public void run(ITestInvocationListener standardListener) throws DeviceNotAvailableException {
220         Assert.assertNotNull(mTestDevice);
221         setupTests();
222         if (!mSkipSetDeviceScreenTimeout) {
223             setDeviceScreenTimeout();
224         }
225         RunUtil.getDefault().sleep(START_TIMER);
226 
227         if (!mWifiOnly) {
228             final RadioHelper radioHelper = new RadioHelper(mTestDevice);
229             Assert.assertTrue("Radio activation failed", radioHelper.radioActivation());
230             Assert.assertTrue("Data setup failed", radioHelper.waitForDataSetup());
231         }
232 
233         IRemoteAndroidTestRunner runner =
234                 new RemoteAndroidTestRunner(
235                         TEST_PACKAGE_NAME, TEST_RUNNER_NAME, mTestDevice.getIDevice());
236         runner.addInstrumentationArg("softap_iterations", mApIteration);
237         runner.addInstrumentationArg("scan_iterations", mScanIteration);
238         runner.addInstrumentationArg("reconnect_iterations", mReconnectionIteration);
239         runner.addInstrumentationArg("reconnect_ssid", mReconnectionSsid);
240         runner.addInstrumentationArg("reconnect_password", mReconnectionPassword);
241         runner.addInstrumentationArg("sleep_time", mIdleTime);
242         if (mWifiOnly) {
243             runner.addInstrumentationArg("wifi-only", String.valueOf(mWifiOnly));
244         }
245 
246         // Add bugreport listener for failed test
247         BugreportCollector bugListener = new BugreportCollector(standardListener, mTestDevice);
248         bugListener.addPredicate(BugreportCollector.AFTER_FAILED_TESTCASES);
249         // Device may reboot during the test, to capture a bugreport after that,
250         // wait for 30 seconds for device to be online, otherwise, bugreport will be empty
251         bugListener.setDeviceWaitTime(30);
252 
253         for (TestInfo testCase : mTestList) {
254             // for Wi-Fi reconnection test,
255             if ("WifiReconnectionStress".equals(testCase.mTestName)) {
256                 setScreenProperty(false);
257             } else {
258                 setScreenProperty(true);
259             }
260             CLog.d("TestInfo: " + testCase.toString());
261             runner.setClassName(testCase.mTestClass);
262             runner.setMethodName(testCase.mTestClass, testCase.mTestMethod);
263             runner.setMaxTimeToOutputResponse(testCase.mTestTimer, TimeUnit.MILLISECONDS);
264             bugListener.setDescriptiveName(testCase.mTestName);
265             mTestDevice.runInstrumentationTests(runner, bugListener);
266             logOutputFile(testCase, bugListener);
267             cleanOutputFiles();
268         }
269     }
270 
271     /**
272      * Collect test results, report test results to dash board.
273      *
274      * @param test
275      * @param listener
276      */
logOutputFile(TestInfo test, ITestInvocationListener listener)277     private void logOutputFile(TestInfo test, ITestInvocationListener listener)
278             throws DeviceNotAvailableException {
279         File resFile = null;
280         InputStreamSource outputSource = null;
281 
282         try {
283             resFile = mTestDevice.pullFileFromExternal(mOutputFile);
284             if (resFile != null) {
285                 // Save a copy of the output file
286                 CLog.d("Sending %d byte file %s into the logosphere!", resFile.length(), resFile);
287                 outputSource = new FileInputStreamSource(resFile);
288                 listener.testLog(
289                         String.format("result-%s.txt", test.mTestName),
290                         LogDataType.TEXT,
291                         outputSource);
292 
293                 // Parse the results file and post results to test listener
294                 parseOutputFile(test, resFile, listener);
295             }
296         } finally {
297             FileUtil.deleteFile(resFile);
298             StreamUtil.cancel(outputSource);
299         }
300     }
301 
parseOutputFile(TestInfo test, File dataFile, ITestInvocationListener listener)302     private void parseOutputFile(TestInfo test, File dataFile, ITestInvocationListener listener) {
303         Map<String, String> runMetrics = new HashMap<>();
304         Map<String, String> runScanMetrics = null;
305         boolean isScanningTest = "WifiScanning".equals(test.mTestName);
306         Integer iteration = null;
307         BufferedReader br = null;
308         try {
309             br = new BufferedReader(new FileReader(dataFile));
310             String line = null;
311             while ((line = br.readLine()) != null) {
312                 List<List<String>> capture = new ArrayList<>(1);
313                 String key = test.mPatternMap.retrieve(capture, line);
314                 if (key != null) {
315                     CLog.d(
316                             "In output file of test case %s: retrieve key: %s, " + "catpure: %s",
317                             test.mTestName, key, capture.toString());
318                     // Save results in the metrics
319                     if ("scan_quality".equals(key)) {
320                         // For scanning test, calculate the scan quality
321                         int count = Integer.parseInt(capture.get(0).get(0));
322                         int total = Integer.parseInt(capture.get(0).get(1));
323                         int quality = 0;
324                         if (total != 0) {
325                             quality = (100 * count) / total;
326                         }
327                         runMetrics.put(key, Integer.toString(quality));
328                     } else {
329                         runMetrics.put(key, capture.get(0).get(0));
330                     }
331                 } else {
332                     // For scanning test, iterations will also be counted.
333                     if (isScanningTest) {
334                         Matcher m = ITERATION_PATTERN.matcher(line);
335                         if (m.matches()) {
336                             iteration = Integer.parseInt(m.group(1));
337                         }
338                     }
339                 }
340             }
341             if (isScanningTest) {
342                 runScanMetrics = new HashMap<>(1);
343                 if (iteration == null) {
344                     // no matching is found
345                     CLog.d("No iteration logs found in %s, set to 0", mOutputFile);
346                     iteration = Integer.valueOf(0);
347                 }
348                 runScanMetrics.put("wifi_scan_stress", iteration.toString());
349             }
350 
351             // Report results
352             reportMetrics(test.mTestMetricsName, listener, runMetrics);
353             if (isScanningTest) {
354                 reportMetrics("wifi_stress", listener, runScanMetrics);
355             }
356         } catch (IOException e) {
357             CLog.e("IOException while reading from data stream");
358             CLog.e(e);
359             return;
360         } finally {
361             StreamUtil.close(br);
362         }
363     }
364 
365     /**
366      * Report run metrics by creating an empty test run to stick them in
367      *
368      * <p>Exposed for unit testing
369      */
reportMetrics( String metricsName, ITestInvocationListener listener, Map<String, String> metrics)370     private void reportMetrics(
371             String metricsName, ITestInvocationListener listener, Map<String, String> metrics) {
372         // Create an empty testRun to report the parsed runMetrics
373         CLog.d("About to report metrics to %s: %s", metricsName, metrics);
374         listener.testRunStarted(metricsName, 0);
375         listener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(metrics));
376     }
377 
378     /** Clean up output files from the last test run */
cleanOutputFiles()379     private void cleanOutputFiles() throws DeviceNotAvailableException {
380         CLog.d("Remove output file: %s", mOutputFile);
381         String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
382         mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, mOutputFile));
383     }
384 }
385