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