blob: 64805c124e62fd12715e1755888827be9d3a3406 [file] [log] [blame]
// Copyright 2018 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.util;
import static org.hamcrest.Matchers.isIn;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Bundle;
import android.support.test.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.rules.ExternalResource;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.robolectric.RuntimeEnvironment;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Helper rule to allow executing test runners in tests.
*
* Quis probat ipsas probas?
*/
class TestRunnerTestRule extends ExternalResource {
final Class<? extends BlockJUnit4ClassRunner> mRunnerClass;
/**
* @param runnerClass The runner class to run the test
*/
TestRunnerTestRule(Class<? extends BlockJUnit4ClassRunner> runnerClass) {
mRunnerClass = runnerClass;
}
@Override
protected void before() throws Throwable {
// Register a fake Instrumentation so that class runners for instrumentation tests
// can be run even in Robolectric tests.
Instrumentation instrumentation = new Instrumentation() {
@Override
public Context getTargetContext() {
return RuntimeEnvironment.application;
}
};
InstrumentationRegistry.registerInstance(instrumentation, new Bundle());
}
@Override
protected void after() {
InstrumentationRegistry.registerInstance(null, new Bundle());
}
/**
* A struct-like class containing lists of run and skipped tests.
*/
public static class TestLog {
public final List<Description> runTests = new ArrayList<>();
public final List<Description> skippedTests = new ArrayList<>();
}
/**
* Creates a new test runner and executes the test in the given {@code testClass} on it,
* returning lists of tests that were run and tests that were skipped.
* @param testClass The test class
* @return A {@link TestLog} that contains lists of run and skipped tests.
*/
public TestLog runTest(Class<?> testClass) throws InvocationTargetException,
NoSuchMethodException, InstantiationException,
IllegalAccessException {
TestLog testLog = new TestLog();
// TODO(bauerb): Using Mockito mock() or spy() throws a ClassCastException.
RunListener runListener = new RunListener() {
@Override
public void testStarted(Description description) throws Exception {
testLog.runTests.add(description);
}
@Override
public void testFinished(Description description) throws Exception {
Assert.assertThat(description, isIn(testLog.runTests));
}
@Override
public void testFailure(Failure failure) throws Exception {
fail(failure.toString());
}
@Override
public void testAssumptionFailure(Failure failure) {
fail(failure.toString());
}
@Override
public void testIgnored(Description description) throws Exception {
testLog.skippedTests.add(description);
}
};
RunNotifier runNotifier = new RunNotifier();
runNotifier.addListener(runListener);
Runner runner;
try {
runner = mRunnerClass.getConstructor(Class.class).newInstance(testClass);
} catch (InvocationTargetException e) {
// If constructing the runner caused initialization errors, unwrap them from the
// InvocationTargetException.
Throwable cause = e.getCause();
if (!(cause instanceof InitializationError)) throw e;
List<Throwable> causes = ((InitializationError) cause).getCauses();
// If there was exactly one initialization error, rewrap that one.
if (causes.size() == 1) {
throw new InvocationTargetException(causes.get(0), "Initialization error");
}
// Otherwise, serialize all initialization errors to a string and throw that.
throw new AssertionError(causes.toString());
}
runner.run(runNotifier);
return testLog;
}
}