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