1 /* 2 * Copyright (C) 2008 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.ddmlib.testrunner; 18 19 20 import com.android.ddmlib.IDevice; 21 import com.android.ddmlib.Log; 22 23 import java.io.IOException; 24 import java.util.Hashtable; 25 import java.util.Map; 26 import java.util.Map.Entry; 27 28 /** 29 * Runs a Android test command remotely and reports results. 30 */ 31 public class RemoteAndroidTestRunner { 32 33 private final String mPackageName; 34 private final String mRunnerName; 35 private IDevice mRemoteDevice; 36 /** map of name-value instrumentation argument pairs */ 37 private Map<String, String> mArgMap; 38 private InstrumentationResultParser mParser; 39 40 private static final String LOG_TAG = "RemoteAndroidTest"; 41 private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner"; 42 43 private static final char CLASS_SEPARATOR = ','; 44 private static final char METHOD_SEPARATOR = '#'; 45 private static final char RUNNER_SEPARATOR = '/'; 46 47 // defined instrumentation argument names 48 private static final String CLASS_ARG_NAME = "class"; 49 private static final String LOG_ARG_NAME = "log"; 50 private static final String DEBUG_ARG_NAME = "debug"; 51 private static final String COVERAGE_ARG_NAME = "coverage"; 52 private static final String PACKAGE_ARG_NAME = "package"; 53 54 /** 55 * Creates a remote Android test runner. 56 * 57 * @param packageName the Android application package that contains the tests to run 58 * @param runnerName the instrumentation test runner to execute. If null, will use default 59 * runner 60 * @param remoteDevice the Android device to execute tests on 61 */ RemoteAndroidTestRunner(String packageName, String runnerName, IDevice remoteDevice)62 public RemoteAndroidTestRunner(String packageName, 63 String runnerName, 64 IDevice remoteDevice) { 65 66 mPackageName = packageName; 67 mRunnerName = runnerName; 68 mRemoteDevice = remoteDevice; 69 mArgMap = new Hashtable<String, String>(); 70 } 71 72 /** 73 * Alternate constructor. Uses default instrumentation runner. 74 * 75 * @param packageName the Android application package that contains the tests to run 76 * @param remoteDevice the Android device to execute tests on 77 */ RemoteAndroidTestRunner(String packageName, IDevice remoteDevice)78 public RemoteAndroidTestRunner(String packageName, 79 IDevice remoteDevice) { 80 this(packageName, null, remoteDevice); 81 } 82 83 /** 84 * Returns the application package name. 85 */ getPackageName()86 public String getPackageName() { 87 return mPackageName; 88 } 89 90 /** 91 * Returns the runnerName. 92 */ getRunnerName()93 public String getRunnerName() { 94 if (mRunnerName == null) { 95 return DEFAULT_RUNNER_NAME; 96 } 97 return mRunnerName; 98 } 99 100 /** 101 * Returns the complete instrumentation component path. 102 */ getRunnerPath()103 private String getRunnerPath() { 104 return getPackageName() + RUNNER_SEPARATOR + getRunnerName(); 105 } 106 107 /** 108 * Sets to run only tests in this class 109 * Must be called before 'run'. 110 * 111 * @param className fully qualified class name (eg x.y.z) 112 */ setClassName(String className)113 public void setClassName(String className) { 114 addInstrumentationArg(CLASS_ARG_NAME, className); 115 } 116 117 /** 118 * Sets to run only tests in the provided classes 119 * Must be called before 'run'. 120 * <p> 121 * If providing more than one class, requires a InstrumentationTestRunner that supports 122 * the multiple class argument syntax. 123 * 124 * @param classNames array of fully qualified class names (eg x.y.z) 125 */ setClassNames(String[] classNames)126 public void setClassNames(String[] classNames) { 127 StringBuilder classArgBuilder = new StringBuilder(); 128 129 for (int i = 0; i < classNames.length; i++) { 130 if (i != 0) { 131 classArgBuilder.append(CLASS_SEPARATOR); 132 } 133 classArgBuilder.append(classNames[i]); 134 } 135 setClassName(classArgBuilder.toString()); 136 } 137 138 /** 139 * Sets to run only specified test method 140 * Must be called before 'run'. 141 * 142 * @param className fully qualified class name (eg x.y.z) 143 * @param testName method name 144 */ setMethodName(String className, String testName)145 public void setMethodName(String className, String testName) { 146 setClassName(className + METHOD_SEPARATOR + testName); 147 } 148 149 /** 150 * Sets to run all tests in specified package 151 * Must be called before 'run'. 152 * 153 * @param packageName fully qualified package name (eg x.y.z) 154 */ setTestPackageName(String packageName)155 public void setTestPackageName(String packageName) { 156 addInstrumentationArg(PACKAGE_ARG_NAME, packageName); 157 } 158 159 /** 160 * Adds a argument to include in instrumentation command. 161 * <p/> 162 * Must be called before 'run'. If an argument with given name has already been provided, it's 163 * value will be overridden. 164 * 165 * @param name the name of the instrumentation bundle argument 166 * @param value the value of the argument 167 */ addInstrumentationArg(String name, String value)168 public void addInstrumentationArg(String name, String value) { 169 if (name == null || value == null) { 170 throw new IllegalArgumentException("name or value arguments cannot be null"); 171 } 172 mArgMap.put(name, value); 173 } 174 175 /** 176 * Adds a boolean argument to include in instrumentation command. 177 * <p/> 178 * @see RemoteAndroidTestRunner#addInstrumentationArg 179 * 180 * @param name the name of the instrumentation bundle argument 181 * @param value the value of the argument 182 */ addBooleanArg(String name, boolean value)183 public void addBooleanArg(String name, boolean value) { 184 addInstrumentationArg(name, Boolean.toString(value)); 185 } 186 187 /** 188 * Sets this test run to log only mode - skips test execution. 189 */ setLogOnly(boolean logOnly)190 public void setLogOnly(boolean logOnly) { 191 addBooleanArg(LOG_ARG_NAME, logOnly); 192 } 193 194 /** 195 * Sets this debug mode of this test run. If true, the Android test runner will wait for a 196 * debugger to attach before proceeding with test execution. 197 */ setDebug(boolean debug)198 public void setDebug(boolean debug) { 199 addBooleanArg(DEBUG_ARG_NAME, debug); 200 } 201 202 /** 203 * Sets this code coverage mode of this test run. 204 */ setCoverage(boolean coverage)205 public void setCoverage(boolean coverage) { 206 addBooleanArg(COVERAGE_ARG_NAME, coverage); 207 } 208 209 /** 210 * Execute this test run. 211 * 212 * @param listener listens for test results 213 */ run(ITestRunListener listener)214 public void run(ITestRunListener listener) { 215 final String runCaseCommandStr = String.format("am instrument -w -r %s %s", 216 getArgsCommand(), getRunnerPath()); 217 Log.d(LOG_TAG, runCaseCommandStr); 218 mParser = new InstrumentationResultParser(listener); 219 220 try { 221 mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser); 222 } catch (IOException e) { 223 Log.e(LOG_TAG, e); 224 listener.testRunFailed(e.toString()); 225 } 226 } 227 228 /** 229 * Requests cancellation of this test run. 230 */ cancel()231 public void cancel() { 232 if (mParser != null) { 233 mParser.cancel(); 234 } 235 } 236 237 /** 238 * Returns the full instrumentation command line syntax for the provided instrumentation 239 * arguments. 240 * Returns an empty string if no arguments were specified. 241 */ getArgsCommand()242 private String getArgsCommand() { 243 StringBuilder commandBuilder = new StringBuilder(); 244 for (Entry<String, String> argPair : mArgMap.entrySet()) { 245 final String argCmd = String.format(" -e %s %s", argPair.getKey(), 246 argPair.getValue()); 247 commandBuilder.append(argCmd); 248 } 249 return commandBuilder.toString(); 250 } 251 } 252