1 /* 2 * Copyright (C) 2015 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.cts.core.runner; 18 19 import android.app.Activity; 20 import android.app.Instrumentation; 21 import android.os.Bundle; 22 import android.os.Debug; 23 import android.support.test.internal.runner.listener.InstrumentationResultPrinter; 24 import android.support.test.internal.runner.listener.InstrumentationRunListener; 25 import android.support.test.internal.util.AndroidRunnerParams; 26 import android.util.Log; 27 import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder; 28 import com.google.common.base.Splitter; 29 import java.io.BufferedReader; 30 import java.io.ByteArrayOutputStream; 31 import java.io.FileReader; 32 import java.io.IOException; 33 import java.io.PrintStream; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.Collections; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Set; 41 import org.junit.runner.Computer; 42 import org.junit.runner.JUnitCore; 43 import org.junit.runner.Request; 44 import org.junit.runner.Result; 45 import org.junit.runner.Runner; 46 import org.junit.runner.manipulation.Filter; 47 import org.junit.runner.manipulation.Filterable; 48 import org.junit.runner.manipulation.NoTestsRemainException; 49 import org.junit.runner.notification.RunListener; 50 import org.junit.runners.model.InitializationError; 51 import org.junit.runners.model.RunnerBuilder; 52 53 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_COUNT; 54 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG; 55 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY; 56 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_CLASS; 57 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_FILE; 58 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_PACKAGE; 59 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS; 60 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE; 61 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_PACKAGE; 62 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TIMEOUT; 63 64 /** 65 * A drop-in replacement for AndroidJUnitTestRunner, which understands the same arguments, and has 66 * similar functionality, but can filter by expectations and allows a custom runner-builder to be 67 * provided. 68 */ 69 public class CoreTestRunner extends Instrumentation { 70 71 static final String TAG = "LibcoreTestRunner"; 72 73 private static final java.lang.String ARGUMENT_ROOT_CLASSES = "core-root-classes"; 74 75 private static final String ARGUMENT_CORE_LISTENER = "core-listener"; 76 77 private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults(); 78 79 /** The args for the runner. */ 80 private Bundle args; 81 82 /** Only log the number and names of tests, and not run them. */ 83 private boolean logOnly; 84 85 /** The amount of time in millis to wait for a single test to complete. */ 86 private long testTimeout; 87 88 /** 89 * The list of tests to run. 90 */ 91 private TestList testList; 92 93 /** 94 * The list of {@link RunListener} classes to create. 95 */ 96 private List<Class<? extends RunListener>> listenerClasses; 97 private Filter expectationFilter; 98 99 @Override onCreate(final Bundle args)100 public void onCreate(final Bundle args) { 101 super.onCreate(args); 102 this.args = args; 103 104 boolean debug = "true".equalsIgnoreCase(args.getString(ARGUMENT_DEBUG)); 105 if (debug) { 106 Log.i(TAG, "Waiting for debugger to connect..."); 107 Debug.waitForDebugger(); 108 Log.i(TAG, "Debugger connected."); 109 } 110 111 // Log the message only after getting a value from the args so that the args are 112 // unparceled. 113 Log.d(TAG, "In OnCreate: " + args); 114 115 // Treat logOnly and count as the same. This is not quite true as count should only send 116 // the host the number of tests but logOnly should send the name and number. However, 117 // this is how this has always behaved and it does not appear to have caused any problems. 118 // Changing it seems unnecessary given that count is CTSv1 only and CTSv1 will be removed 119 // soon now that CTSv2 is ready. 120 boolean testCountOnly = args.getBoolean(ARGUMENT_COUNT); 121 this.logOnly = "true".equalsIgnoreCase(args.getString(ARGUMENT_LOG_ONLY)) || testCountOnly; 122 this.testTimeout = parseUnsignedLong(args.getString(ARGUMENT_TIMEOUT), ARGUMENT_TIMEOUT); 123 124 expectationFilter = new ExpectationBasedFilter(args); 125 126 // The test can be run specifying a list of tests to run, or as cts-tradefed does it, 127 // by passing a fileName with a test to run on each line. 128 Set<String> testNameSet = new HashSet<>(); 129 String arg; 130 if ((arg = args.getString(ARGUMENT_TEST_FILE)) != null) { 131 // The tests are specified in a file. 132 try { 133 testNameSet.addAll(readTestsFromFile(arg)); 134 } catch (IOException err) { 135 finish(Activity.RESULT_CANCELED, new Bundle()); 136 return; 137 } 138 } else if ((arg = args.getString(ARGUMENT_TEST_CLASS)) != null) { 139 // The tests are specified in a String passed in the bundle. 140 String[] tests = arg.split(","); 141 testNameSet.addAll(Arrays.asList(tests)); 142 } 143 144 // Tests may be excluded from the run by passing a list of tests not to run, 145 // or by passing a fileName with a test not to run on each line. 146 Set<String> notTestNameSet = new HashSet<>(); 147 if ((arg = args.getString(ARGUMENT_NOT_TEST_FILE)) != null) { 148 // The tests are specified in a file. 149 try { 150 notTestNameSet.addAll(readTestsFromFile(arg)); 151 } catch (IOException err) { 152 finish(Activity.RESULT_CANCELED, new Bundle()); 153 return; 154 } 155 } else if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) { 156 // The classes are specified in a String passed in the bundle 157 String[] tests = arg.split(","); 158 notTestNameSet.addAll(Arrays.asList(tests)); 159 } 160 161 Set<String> packageNameSet = new HashSet<>(); 162 if ((arg = args.getString(ARGUMENT_TEST_PACKAGE)) != null) { 163 // The packages are specified in a String passed in the bundle 164 String[] packages = arg.split(","); 165 packageNameSet.addAll(Arrays.asList(packages)); 166 } 167 168 Set<String> notPackageNameSet = new HashSet<>(); 169 if ((arg = args.getString(ARGUMENT_NOT_TEST_PACKAGE)) != null) { 170 // The packages are specified in a String passed in the bundle 171 String[] packages = arg.split(","); 172 notPackageNameSet.addAll(Arrays.asList(packages)); 173 } 174 175 List<String> roots = getRootClassNames(args); 176 if (roots == null) { 177 // Find all test classes 178 Collection<Class<?>> classes = TestClassFinder.getClasses( 179 Collections.singletonList(getContext().getPackageCodePath()), 180 getClass().getClassLoader()); 181 testList = new TestList(classes); 182 } else { 183 testList = TestList.rootList(roots); 184 } 185 186 testList.addIncludeTestPackages(packageNameSet); 187 testList.addExcludeTestPackages(notPackageNameSet); 188 testList.addIncludeTests(testNameSet); 189 testList.addExcludeTests(notTestNameSet); 190 191 listenerClasses = new ArrayList<>(); 192 String listenerArg = args.getString(ARGUMENT_CORE_LISTENER); 193 if (listenerArg != null) { 194 List<String> listenerClassNames = CLASS_LIST_SPLITTER.splitToList(listenerArg); 195 for (String listenerClassName : listenerClassNames) { 196 try { 197 Class<? extends RunListener> listenerClass = Class.forName(listenerClassName) 198 .asSubclass(RunListener.class); 199 listenerClasses.add(listenerClass); 200 } catch (ClassNotFoundException e) { 201 Log.e(TAG, "Could not load listener class: " + listenerClassName, e); 202 } 203 } 204 } 205 206 start(); 207 } 208 getRootClassNames(Bundle args)209 private List<String> getRootClassNames(Bundle args) { 210 String rootClasses = args.getString(ARGUMENT_ROOT_CLASSES); 211 List<String> roots; 212 if (rootClasses == null) { 213 roots = null; 214 } else { 215 roots = CLASS_LIST_SPLITTER.splitToList(rootClasses); 216 } 217 return roots; 218 } 219 220 @Override onStart()221 public void onStart() { 222 if (logOnly) { 223 Log.d(TAG, "Counting/logging tests only"); 224 } else { 225 Log.d(TAG, "Running tests"); 226 } 227 228 AndroidRunnerParams runnerParams = new AndroidRunnerParams(this, args, 229 false, testTimeout, false /*ignoreSuiteMethods*/); 230 231 Runner runner; 232 try { 233 RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams); 234 Class[] classes = testList.getClassesToRun(); 235 for (Class cls : classes) { 236 Log.d(TAG, "Found class to run: " + cls.getName()); 237 } 238 runner = new Computer().getSuite(runnerBuilder, classes); 239 240 if (runner instanceof Filterable) { 241 Log.d(TAG, "Applying filters"); 242 Filterable filterable = (Filterable) runner; 243 244 // Filter out all the tests that are expected to fail. 245 try { 246 filterable.filter(expectationFilter); 247 } catch (NoTestsRemainException e) { 248 // Sometimes filtering will remove all tests but we do not care about that. 249 } 250 Log.d(TAG, "Applied filters"); 251 } 252 253 // If the tests are only supposed to be logged and not actually run then replace the 254 // runner with a runner that will fire notifications for all the tests that would have 255 // been run. This is needed because CTSv2 does a log only run through a CTS module in 256 // order to generate a list of tests that will be run so that it can monitor them. 257 // Encapsulating that in a Runner implementation makes it easier to leverage the 258 // existing code for running tests. 259 if (logOnly) { 260 runner = new DescriptionHierarchyNotifier(runner.getDescription()); 261 } 262 263 } catch (InitializationError e) { 264 throw new RuntimeException("Could not create a suite", e); 265 } 266 267 InstrumentationResultPrinter instrumentationResultPrinter = 268 new InstrumentationResultPrinter(); 269 instrumentationResultPrinter.setInstrumentation(this); 270 271 JUnitCore core = new JUnitCore(); 272 core.addListener(instrumentationResultPrinter); 273 274 // If not logging the list of tests then add any additional configured listeners. These 275 // must be added before firing any events. 276 if (!logOnly) { 277 // Add additional configured listeners. 278 for (Class<? extends RunListener> listenerClass : listenerClasses) { 279 try { 280 RunListener runListener = listenerClass.newInstance(); 281 if (runListener instanceof InstrumentationRunListener) { 282 ((InstrumentationRunListener) runListener).setInstrumentation(this); 283 } 284 core.addListener(runListener); 285 } catch (InstantiationException | IllegalAccessException e) { 286 Log.e(TAG, 287 "Could not create instance of listener: " + listenerClass, e); 288 } 289 } 290 } 291 292 Log.d(TAG, "Finished preparations, running/listing tests"); 293 294 Bundle results = new Bundle(); 295 Result junitResults = new Result(); 296 try { 297 junitResults = core.run(Request.runner(runner)); 298 } catch (RuntimeException e) { 299 final String msg = "Fatal exception when running tests"; 300 Log.e(TAG, msg, e); 301 // report the exception to instrumentation out 302 results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 303 msg + "\n" + Log.getStackTraceString(e)); 304 } finally { 305 ByteArrayOutputStream summaryStream = new ByteArrayOutputStream(); 306 // create the stream used to output summary data to the user 307 PrintStream summaryWriter = new PrintStream(summaryStream); 308 instrumentationResultPrinter.instrumentationRunFinished(summaryWriter, 309 results, junitResults); 310 summaryWriter.close(); 311 results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, 312 String.format("\n%s", summaryStream.toString())); 313 } 314 315 316 Log.d(TAG, "Finished"); 317 finish(Activity.RESULT_OK, results); 318 } 319 320 /** 321 * Read tests from a specified file. 322 * 323 * @return class names of tests. If there was an error reading the file, null is returned. 324 */ readTestsFromFile(String fileName)325 private static List<String> readTestsFromFile(String fileName) throws IOException { 326 try (BufferedReader br = new BufferedReader(new FileReader(fileName))) { 327 List<String> tests = new ArrayList<>(); 328 String line; 329 while ((line = br.readLine()) != null) { 330 tests.add(line); 331 } 332 return tests; 333 } catch (IOException err) { 334 Log.e(TAG, "There was an error reading the test class list: " + err.getMessage()); 335 throw err; 336 } 337 } 338 339 /** 340 * Parse long from given value - except either Long or String. 341 * 342 * @return the value, -1 if not found 343 * @throws NumberFormatException if value is negative or not a number 344 */ parseUnsignedLong(Object value, String name)345 private static long parseUnsignedLong(Object value, String name) { 346 if (value != null) { 347 long longValue = Long.parseLong(value.toString()); 348 if (longValue < 0) { 349 throw new NumberFormatException(name + " can not be negative"); 350 } 351 return longValue; 352 } 353 return -1; 354 } 355 } 356