| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base.test; |
| |
| import static org.chromium.base.test.BaseChromiumAndroidJUnitRunner.shouldListTests; |
| |
| import android.content.Context; |
| import android.support.annotation.CallSuper; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.internal.runner.junit4.AndroidJUnit4ClassRunner; |
| import android.support.test.internal.util.AndroidRunnerParams; |
| |
| import org.junit.rules.MethodRule; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.Description; |
| import org.junit.runner.notification.RunNotifier; |
| import org.junit.runners.model.FrameworkMethod; |
| import org.junit.runners.model.InitializationError; |
| import org.junit.runners.model.Statement; |
| |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.Log; |
| import org.chromium.base.test.BaseTestResult.PreTestHook; |
| import org.chromium.base.test.params.MethodParamAnnotationRule; |
| import org.chromium.base.test.util.DisableIfSkipCheck; |
| import org.chromium.base.test.util.MinAndroidSdkLevelSkipCheck; |
| import org.chromium.base.test.util.RestrictionSkipCheck; |
| import org.chromium.base.test.util.SkipCheck; |
| |
| import java.io.File; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A custom runner for JUnit4 tests that checks requirements to conditionally ignore tests. |
| * |
| * This ClassRunner imports from AndroidJUnit4ClassRunner which is a hidden but accessible |
| * class. The reason is that default JUnit4 runner for Android is a final class, |
| * AndroidJUnit4. We need to extends an inheritable class to change {@link #runChild} |
| * and {@link #isIgnored} to add SkipChecks and PreTesthook. |
| */ |
| public class BaseJUnit4ClassRunner extends AndroidJUnit4ClassRunner { |
| private static final String TAG = "BaseJUnit4ClassRunnr"; |
| |
| private static final String EXTRA_TRACE_FILE = |
| "org.chromium.base.test.BaseJUnit4ClassRunner.TraceFile"; |
| |
| /** |
| * Create a BaseJUnit4ClassRunner to run {@code klass} and initialize values. |
| * |
| * To add more SkipCheck or PreTestHook in subclass, create Lists of checks and hooks, |
| * pass them into the super constructors. If you want make a subclass extendable by other |
| * class runners, you also have to create a constructor similar to the following one that |
| * merges default checks or hooks with this checks and hooks passed in by constructor. |
| * |
| * <pre> |
| * <code> |
| * e.g. |
| * public ChildRunner extends BaseJUnit4ClassRunner { |
| * public ChildRunner(final Class<?> klass) { |
| * throws InitializationError { |
| * this(klass, Collections.emptyList(), Collections.emptyList(), |
| * Collections.emptyList()); |
| * } |
| * |
| * public ChildRunner( |
| * final Class<?> klass, List<SkipCheck> checks, List<PreTestHook> hook, |
| * List<TestRule> rules) { throws InitializationError { super(klass, mergeList( checks, |
| * getSkipChecks()), mergeList(hooks, getPreTestHooks())); |
| * } |
| * |
| * public List<SkipCheck> getSkipChecks() {...} |
| * |
| * public List<PreTestHook> getPreTestHooks() {...} |
| * </code> |
| * </pre> |
| * |
| * @throws InitializationError if the test class malformed |
| */ |
| public BaseJUnit4ClassRunner(final Class<?> klass) throws InitializationError { |
| super(klass, |
| new AndroidRunnerParams(InstrumentationRegistry.getInstrumentation(), |
| InstrumentationRegistry.getArguments(), false, 0L, false)); |
| |
| String traceOutput = InstrumentationRegistry.getArguments().getString(EXTRA_TRACE_FILE); |
| |
| if (traceOutput != null) { |
| File traceOutputFile = new File(traceOutput); |
| File traceOutputDir = traceOutputFile.getParentFile(); |
| |
| if (traceOutputDir != null) { |
| if (traceOutputDir.exists() || traceOutputDir.mkdirs()) { |
| TestTraceEvent.enable(traceOutputFile); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Merge two List into a new ArrayList. |
| * |
| * Used to merge the default SkipChecks/PreTestHooks with the subclasses's |
| * SkipChecks/PreTestHooks. |
| */ |
| private static <T> List<T> mergeList(List<T> listA, List<T> listB) { |
| List<T> l = new ArrayList<>(listA); |
| l.addAll(listB); |
| return l; |
| } |
| |
| @SafeVarargs |
| protected static <T> List<T> addToList(List<T> list, T... additionalEntries) { |
| return mergeList(list, Arrays.asList(additionalEntries)); |
| } |
| |
| @Override |
| protected void collectInitializationErrors(List<Throwable> errors) { |
| super.collectInitializationErrors(errors); |
| // Log any initialization errors to help debugging, as the host-side test runner can get |
| // confused by the thrown exception. |
| if (!errors.isEmpty()) { |
| Log.e(TAG, "Initialization errors in %s: %s", getTestClass().getName(), errors); |
| } |
| } |
| |
| /** |
| * Override this method to return a list of {@link SkipCheck}s}. |
| * |
| * Additional hooks can be added to the list using {@link #addToList}: |
| * {@code return addToList(super.getSkipChecks(), check1, check2);} |
| */ |
| @CallSuper |
| protected List<SkipCheck> getSkipChecks() { |
| return Arrays.asList(new RestrictionSkipCheck(InstrumentationRegistry.getTargetContext()), |
| new MinAndroidSdkLevelSkipCheck(), new DisableIfSkipCheck()); |
| } |
| |
| /** |
| * Override this method to return a list of {@link PreTestHook}s. |
| * |
| * Additional hooks can be added to the list using {@link #addToList}: |
| * {@code return addToList(super.getPreTestHooks(), hook1, hook2);} |
| * TODO(bauerb): Migrate PreTestHook to TestRule. |
| */ |
| @CallSuper |
| protected List<PreTestHook> getPreTestHooks() { |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * Override this method to return a list of method rules that should be applied to all tests |
| * run with this test runner. |
| * |
| * Additional rules can be added to the list using {@link #addToList}: |
| * {@code return addToList(super.getDefaultMethodRules(), rule1, rule2);} |
| */ |
| @CallSuper |
| protected List<MethodRule> getDefaultMethodRules() { |
| return Collections.singletonList(new MethodParamAnnotationRule()); |
| } |
| |
| /** |
| * Override this method to return a list of rules that should be applied to all tests run with |
| * this test runner. |
| * |
| * Additional rules can be added to the list using {@link #addToList}: |
| * {@code return addToList(super.getDefaultTestRules(), rule1, rule2);} |
| */ |
| @CallSuper |
| protected List<TestRule> getDefaultTestRules() { |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * Evaluate whether a FrameworkMethod is ignored based on {@code SkipCheck}s. |
| */ |
| @Override |
| protected boolean isIgnored(FrameworkMethod method) { |
| return super.isIgnored(method) || shouldSkip(method); |
| } |
| |
| @Override |
| protected List<MethodRule> rules(Object target) { |
| List<MethodRule> declaredRules = super.rules(target); |
| List<MethodRule> defaultRules = getDefaultMethodRules(); |
| return mergeList(defaultRules, declaredRules); |
| } |
| |
| @Override |
| protected final List<TestRule> getTestRules(Object target) { |
| List<TestRule> declaredRules = super.getTestRules(target); |
| List<TestRule> defaultRules = getDefaultTestRules(); |
| return mergeList(declaredRules, defaultRules); |
| } |
| |
| /** |
| * Run test with or without execution based on bundle arguments. |
| */ |
| @Override |
| public void run(RunNotifier notifier) { |
| ContextUtils.initApplicationContext( |
| InstrumentationRegistry.getTargetContext().getApplicationContext()); |
| if (shouldListTests(InstrumentationRegistry.getArguments())) { |
| for (Description child : getDescription().getChildren()) { |
| notifier.fireTestStarted(child); |
| notifier.fireTestFinished(child); |
| } |
| return; |
| } |
| |
| if (!CommandLine.isInitialized()) { |
| initCommandLineForTest(); |
| } |
| super.run(notifier); |
| } |
| |
| /** |
| * Override this method to change how test class runner initiate commandline flags |
| */ |
| protected void initCommandLineForTest() { |
| CommandLine.init(null); |
| } |
| |
| @Override |
| protected void runChild(FrameworkMethod method, RunNotifier notifier) { |
| String testName = method.getName(); |
| TestTraceEvent.begin(testName); |
| |
| runPreTestHooks(method); |
| |
| super.runChild(method, notifier); |
| |
| TestTraceEvent.end(testName); |
| |
| // A new instance of BaseJUnit4ClassRunner is created on the device |
| // for each new method, so runChild will only be called once. Thus, we |
| // can disable tracing, and dump the output, once we get here. |
| TestTraceEvent.disable(); |
| } |
| |
| /** |
| * Loop through all the {@code PreTestHook}s to run them |
| */ |
| private void runPreTestHooks(FrameworkMethod frameworkMethod) { |
| Method testMethod = frameworkMethod.getMethod(); |
| Context targetContext = InstrumentationRegistry.getTargetContext(); |
| for (PreTestHook hook : getPreTestHooks()) { |
| hook.run(targetContext, testMethod); |
| } |
| } |
| |
| /** |
| * Loop through all the {@code SkipCheck}s to confirm whether a test should be ignored |
| */ |
| private boolean shouldSkip(FrameworkMethod method) { |
| for (SkipCheck s : getSkipChecks()) { |
| if (s.shouldSkip(method)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Overriding this method to take screenshot of failure before tear down functions are run. |
| */ |
| @Override |
| protected Statement withAfters(FrameworkMethod method, Object test, Statement base) { |
| return super.withAfters(method, test, new ScreenshotOnFailureStatement(base)); |
| } |
| } |