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.testrunner.IRemoteAndroidTestRunner; 19 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.result.ITestInvocationListener; 25 import com.android.tradefed.result.InputStreamSource; 26 import com.android.tradefed.result.LogDataType; 27 import com.android.tradefed.testtype.IDeviceTest; 28 import com.android.tradefed.testtype.IRemoteTest; 29 import com.android.tradefed.util.FileUtil; 30 import com.android.tradefed.util.RegexTrie; 31 import com.android.tradefed.util.StreamUtil; 32 33 import org.junit.Assert; 34 35 import java.io.BufferedReader; 36 import java.io.File; 37 import java.io.FileReader; 38 import java.io.IOException; 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.concurrent.TimeUnit; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 /** 46 * Run telephony stability test. The test stresses the stability of telephony by 47 * putting device into sleep mode and wake up, voice connection and data connection 48 * are verified after device wakeup. 49 */ 50 public class TelephonyStabilityTest implements IRemoteTest, IDeviceTest { 51 private static final int TEST_TIMEOUT = 9 * 60 * 60 * 1000; // 9 hours 52 53 // Number of iterations to skip taking bugreports for. This is to avoid taking 1000 bugreports 54 // if there are 1000 failures 55 private static final int BUGREPORT_SKIP_ITERATIONS = 10; 56 57 private static final String METRICS_NAME = "telephony_stability"; 58 private static final String VOICE_REGISTRATION_KEY = "voice_registration"; 59 private static final String VOICE_CONNECTION_KEY = "voice_call"; 60 private static final String DATA_REGISTRATION_KEY = "data_registration"; 61 private static final String DATA_CONNECTION_KEY = "data_connection"; 62 private static final String TEST_FAILURE_KEY = "test_failure"; 63 64 private static final String OUTPUT_FILE = "output.txt"; 65 // Output file in format: 66 // iteration voice_registration voice_connection data_registration data_connection 67 private static final Pattern OUTPUT_LINE_REGEX = Pattern.compile( 68 "(\\d+) (\\d+) (\\d+) (\\d+) (\\d+)"); 69 private static final RegexTrie<String> OUTPUT_KEY_PATTERNS = new RegexTrie<String>(); 70 static { 71 OUTPUT_KEY_PATTERNS.put(VOICE_REGISTRATION_KEY, "^Voice registration: (\\d+)"); 72 OUTPUT_KEY_PATTERNS.put(VOICE_CONNECTION_KEY, "^Voice connection: (\\d+)"); 73 OUTPUT_KEY_PATTERNS.put(DATA_REGISTRATION_KEY, "^Data registration: (\\d+)"); 74 OUTPUT_KEY_PATTERNS.put(DATA_CONNECTION_KEY, "^Data connection: (\\d+)"); 75 } 76 77 // Define instrumentation test package and runner. 78 private static final String TEST_PACKAGE_NAME = "com.android.phonetests"; 79 private static final String TEST_RUNNER_NAME = ".PhoneInstrumentationStressTestRunner"; 80 private static final String TEST_CLASS_NAME = 81 "com.android.phonetests.stress.telephony.TelephonyStress2"; 82 private static final String TEST_METHOD = "testStability"; 83 84 private static final String SETTINGS_DB = "/data/data/com.android.providers.settings/databases/" 85 + "settings.db"; 86 private static final String LOCKSCREEN_DB = "/data/system/locksettings.db"; 87 88 @Option(name="phone-number", 89 description="The phone number used for outgoing call test") 90 private String mPhoneNumber = null; 91 92 @Option(name="iterations", 93 description="The number of calls to make during the test") 94 private int mIterations = 100; 95 96 @Option(name="call-duration", 97 description="The time of a call to be held in the test (in seconds)") 98 private long mCallDurationSec = 60; 99 100 @Option(name="suspend-duration", 101 description="The time to allow device staty in suspend mode (in seconds)") 102 private long mSuspendDurationSec = 120; 103 104 @Option(name="screen-timeout", 105 description="Set screen timer (in minutes)") 106 private long mScreenTimeoutMin = 30; 107 108 private ITestDevice mTestDevice = null; 109 110 /** 111 * Run the telephony stability test and collect results 112 */ 113 @Override 114 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 115 Assert.assertNotNull(mTestDevice); 116 Assert.assertNotNull(mPhoneNumber); 117 118 setScreenTimeout(); 119 120 final RadioHelper radioHelper = new RadioHelper(mTestDevice); 121 Assert.assertTrue("Radio activation failed", radioHelper.radioActivation()); 122 Assert.assertTrue("Data setup failed", radioHelper.waitForDataSetup()); 123 124 IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(TEST_PACKAGE_NAME, 125 TEST_RUNNER_NAME, mTestDevice.getIDevice()); 126 runner.setClassName(TEST_CLASS_NAME); 127 runner.setMethodName(TEST_CLASS_NAME, TEST_METHOD); 128 129 runner.addInstrumentationArg("phone-number", mPhoneNumber); 130 runner.addInstrumentationArg("iterations", Integer.toString(mIterations)); 131 runner.addInstrumentationArg("call-duration", Long.toString(mCallDurationSec)); 132 runner.addInstrumentationArg("suspend-duration", Long.toString(mSuspendDurationSec)); 133 runner.setMaxTimeToOutputResponse(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 134 135 Map<String, Integer> metrics = new HashMap<String, Integer>(4); 136 metrics.put(VOICE_REGISTRATION_KEY, 0); 137 metrics.put(VOICE_CONNECTION_KEY, 0); 138 metrics.put(DATA_REGISTRATION_KEY, 0); 139 metrics.put(DATA_CONNECTION_KEY, 0); 140 metrics.put(TEST_FAILURE_KEY, 0); 141 142 int currentIteration = 0; 143 Integer lastBugreportIteration = null; 144 while (currentIteration < mIterations) { 145 runner.addInstrumentationArg("current-iteration", Integer.toString(currentIteration)); 146 mTestDevice.runInstrumentationTests(runner, listener); 147 mTestDevice.waitForDeviceOnline(30 * 1000); 148 currentIteration = processOutputFile(currentIteration, metrics) + 1; 149 150 // Take a bugreport if at last iteration, if we havent taken a bugreport, or if the 151 // bugreport was taken more than BUGREPORT_SKIP_ITERATIONS ago. 152 boolean shouldTakeReport = (currentIteration >= mIterations || 153 lastBugreportIteration == null || 154 (currentIteration - lastBugreportIteration) > BUGREPORT_SKIP_ITERATIONS); 155 156 if (shouldTakeReport) { 157 lastBugreportIteration = currentIteration - 1; 158 InputStreamSource bugreport = mTestDevice.getBugreport(); 159 try { 160 listener.testLog(String.format("bugreport_%04d", lastBugreportIteration), 161 LogDataType.BUGREPORT, bugreport); 162 } finally { 163 bugreport.cancel(); 164 } 165 } 166 167 InputStreamSource screenshot = mTestDevice.getScreenshot(); 168 try { 169 listener.testLog(String.format("screenshot_%04d", lastBugreportIteration), 170 LogDataType.PNG, screenshot); 171 } finally { 172 StreamUtil.cancel(screenshot); 173 } 174 } 175 reportMetrics(listener, metrics); 176 } 177 178 /** 179 * Configure screen timeout property and lockscreen 180 */ 181 private void setScreenTimeout() throws DeviceNotAvailableException { 182 String command = String.format("sqlite3 %s \"UPDATE system SET value=\'%s\' " 183 + "WHERE name=\'screen_off_timeout\';\"", SETTINGS_DB, 184 mScreenTimeoutMin * 60 * 1000); 185 mTestDevice.executeShellCommand(command); 186 187 command = String.format("sqlite3 %s \"UPDATE locksettings SET value=\'1\' " 188 + "WHERE name=\'lockscreen.disabled\'\"", LOCKSCREEN_DB); 189 mTestDevice.executeShellCommand(command); 190 191 command = String.format("sqlite3 %s \"UPDATE locksettings SET value=\'0\' " 192 + "WHERE name=\'lockscreen.password_type\'\"", LOCKSCREEN_DB); 193 mTestDevice.executeShellCommand(command); 194 195 command = String.format("sqlite3 %s \"UPDATE locksettings SET value=\'0\' " 196 + "WHERE name=\'lockscreen.password_type_alternate\'\"", LOCKSCREEN_DB); 197 mTestDevice.executeShellCommand(command); 198 199 // Set device screen_off_timeout as svc power can be set to false in the Wi-Fi test 200 mTestDevice.executeShellCommand("svc power stayon false"); 201 202 // reboot to allow the setting to take effect, post setup will be taken care by the reboot 203 mTestDevice.reboot(); 204 } 205 206 /** 207 * Process the output file, add the failure reason to the file, and return the current 208 * iteration that the call failed on. 209 */ 210 private int processOutputFile(int currentIteration, Map<String, Integer> metrics) 211 throws DeviceNotAvailableException { 212 final File resFile = mTestDevice.pullFileFromExternal(OUTPUT_FILE); 213 BufferedReader reader = null; 214 try { 215 if (resFile == null) { 216 CLog.w("Output file did not exist"); 217 metrics.put(TEST_FAILURE_KEY, metrics.get(TEST_FAILURE_KEY) + 1); 218 return currentIteration; 219 } 220 reader = new BufferedReader(new FileReader(resFile)); 221 String line = getLastLine(reader); 222 223 if (line == null) { 224 CLog.w("Output file was emtpy"); 225 metrics.put(TEST_FAILURE_KEY, metrics.get(TEST_FAILURE_KEY) + 1); 226 return currentIteration; 227 } 228 229 Matcher m = OUTPUT_LINE_REGEX.matcher(line); 230 if (!m.matches()) { 231 CLog.w("Output did not match the expected pattern. Line was \"%s\"", line); 232 metrics.put(TEST_FAILURE_KEY, metrics.get(TEST_FAILURE_KEY) + 1); 233 return currentIteration; 234 } 235 236 CLog.i("Parsed line was \"%s\"", line); 237 238 final int recordedIteration = Integer.parseInt(m.group(1)); 239 metrics.put(VOICE_REGISTRATION_KEY, 240 metrics.get(VOICE_REGISTRATION_KEY) + Integer.parseInt(m.group(2))); 241 metrics.put(VOICE_CONNECTION_KEY, 242 metrics.get(VOICE_CONNECTION_KEY) + Integer.parseInt(m.group(3))); 243 metrics.put(DATA_REGISTRATION_KEY, 244 metrics.get(DATA_REGISTRATION_KEY) + Integer.parseInt(m.group(4))); 245 metrics.put(DATA_CONNECTION_KEY, 246 metrics.get(DATA_CONNECTION_KEY) + Integer.parseInt(m.group(5))); 247 248 return Math.max(recordedIteration, currentIteration); 249 } catch (IOException e) { 250 CLog.e("IOException while reading outputfile %s", resFile.getAbsolutePath()); 251 return currentIteration; 252 } finally { 253 FileUtil.deleteFile(resFile); 254 StreamUtil.close(reader); 255 mTestDevice.executeShellCommand(String.format("rm ${EXTERNAL_STORAGE}/%s", 256 OUTPUT_FILE)); 257 } 258 } 259 260 /** 261 * Get the last line from a buffered reader. 262 */ 263 private String getLastLine(BufferedReader reader) throws IOException { 264 String lastLine = null; 265 String currentLine; 266 while ((currentLine = reader.readLine()) != null) { 267 lastLine = currentLine; 268 } 269 return lastLine; 270 } 271 272 /** 273 * Report run metrics by creating an empty test run to stick them in 274 */ 275 private void reportMetrics(ITestInvocationListener listener, Map<String, Integer> metrics) { 276 Map<String, String> reportedMetrics = new HashMap<String, String>(); 277 for (Map.Entry<String, Integer> entry : metrics.entrySet()) { 278 final Integer keyFailures = entry.getValue(); 279 reportedMetrics.put(entry.getKey(), keyFailures.toString()); 280 } 281 282 // Create an empty testRun to report the parsed runMetrics 283 CLog.d("About to report metrics to %s: %s", METRICS_NAME, reportedMetrics); 284 listener.testRunStarted(METRICS_NAME, 0); 285 listener.testRunEnded(0, reportedMetrics); 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override 292 public void setDevice(ITestDevice testDevice) { 293 mTestDevice = testDevice; 294 } 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override 300 public ITestDevice getDevice() { 301 return mTestDevice; 302 } 303 } 304