org.gradle.integtests.fixtures.AbstractMultiTestRunner Maven / Gradle / Ivy
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.integtests.fixtures;
import org.gradle.api.Nullable;
import org.gradle.internal.UncheckedException;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
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.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* A base class for those test runners which execute a test multiple times.
*/
public abstract class AbstractMultiTestRunner extends Runner implements Filterable {
protected final Class> target;
private final List executions = new ArrayList();
private Description description;
private Description templateDescription;
private boolean executionsInitialized;
protected AbstractMultiTestRunner(Class> target) {
this.target = target;
}
@Override
public Description getDescription() {
initDescription();
return description;
}
@Override
public void run(RunNotifier notifier) {
initDescription();
for (Execution execution : executions) {
execution.run(notifier);
}
}
public void filter(Filter filter) throws NoTestsRemainException {
initExecutions();
for (Execution execution : executions) {
execution.filter(filter);
}
invalidateDescription();
}
private void initExecutions() {
if (!executionsInitialized) {
try {
Runner descriptionProvider = createRunnerFor(Arrays.asList(target), Collections.emptyList());
templateDescription = descriptionProvider.getDescription();
} catch (InitializationError initializationError) {
throw UncheckedException.throwAsUncheckedException(initializationError);
}
createExecutions();
for (Execution execution : executions) {
execution.init(target, templateDescription);
}
executionsInitialized = true;
}
}
private void initDescription() {
initExecutions();
if (description == null) {
description = Description.createSuiteDescription(target);
for (Execution execution : executions) {
execution.addDescriptions(description);
}
}
}
private void invalidateDescription() {
description = null;
templateDescription = null;
}
protected abstract void createExecutions();
protected void add(Execution execution) {
executions.add(execution);
}
private static Runner createRunnerFor(List extends Class>> targetClasses, final List filters) throws InitializationError {
RunnerBuilder runnerBuilder = new RunnerBuilder() {
@Override
public Runner runnerForClass(Class> testClass) throws Throwable {
for (Class> candidate = testClass; candidate != null; candidate = candidate.getSuperclass()) {
RunWith runWith = candidate.getAnnotation(RunWith.class);
if (runWith != null && !AbstractMultiTestRunner.class.isAssignableFrom(runWith.value())) {
try {
Runner r = (Runner) runWith.value().getConstructors()[0].newInstance(testClass);
return filter(r);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
return filter(new BlockJUnit4ClassRunner(testClass));
}
//we need to filter at the level child runners because the suite is not doing the right thing here
private Runner filter(Runner r) {
for (Filter filter : filters) {
try {
((Filterable)r).filter(filter);
} catch (NoTestsRemainException e) {
//ignore
}
}
return r;
}
};
return new Suite(runnerBuilder, targetClasses.toArray(new Class>[0]));
}
protected static abstract class Execution implements Filterable {
protected Class> target;
private Description templateDescription;
private final Map descriptionTranslations = new HashMap();
private final Set enabledTests = new LinkedHashSet();
private final Set disabledTests = new LinkedHashSet();
private final List filters = new LinkedList();
final void init(Class> target, Description templateDescription) {
this.target = target;
this.templateDescription = templateDescription;
}
private Runner createExecutionRunner() throws InitializationError {
List extends Class>> targetClasses = loadTargetClasses();
return createRunnerFor(targetClasses, filters);
}
final void addDescriptions(Description parent) {
map(templateDescription, parent);
}
final void run(final RunNotifier notifier) {
RunNotifier nested = new RunNotifier();
NestedRunListener nestedListener = new NestedRunListener(notifier);
nested.addListener(nestedListener);
try {
runEnabledTests(nested);
} finally {
nestedListener.cleanup();
}
for (Description disabledTest : disabledTests) {
nested.fireTestStarted(disabledTest);
nested.fireTestIgnored(disabledTest);
}
}
private void runEnabledTests(RunNotifier nested) {
if (enabledTests.isEmpty()) {
return;
}
Runner runner;
try {
runner = createExecutionRunner();
} catch (Throwable t) {
runner = new CannotExecuteRunner(getDisplayName(), target, t);
}
try {
if (!disabledTests.isEmpty()) {
((Filterable) runner).filter(new Filter() {
@Override
public boolean shouldRun(Description description) {
return !disabledTests.contains(description);
}
@Override
public String describe() {
return "disabled tests";
}
});
}
} catch (NoTestsRemainException e) {
return;
}
runner.run(nested);
}
private Description translateDescription(Description description) {
return descriptionTranslations.containsKey(description) ? descriptionTranslations.get(description) : description;
}
public void filter(Filter filter) throws NoTestsRemainException {
filters.add(filter);
for (Map.Entry entry : descriptionTranslations.entrySet()) {
if (!filter.shouldRun(entry.getKey())) {
enabledTests.remove(entry.getValue());
disabledTests.remove(entry.getValue());
}
}
}
protected void before() {
}
protected void after() {
}
private void map(Description source, Description parent) {
for (Description child : source.getChildren()) {
Description mappedChild;
if (child.getMethodName() != null) {
mappedChild = Description.createSuiteDescription(String.format("%s [%s](%s)", child.getMethodName(), getDisplayName(), child.getClassName()));
parent.addChild(mappedChild);
if (!isTestEnabled(new TestDescriptionBackedTestDetails(source, child))) {
disabledTests.add(child);
} else {
enabledTests.add(child);
}
} else {
mappedChild = Description.createSuiteDescription(child.getClassName());
}
descriptionTranslations.put(child, mappedChild);
map(child, parent);
}
}
/**
* Returns a display name for this execution. Used in the JUnit descriptions for test execution.
*/
protected abstract String getDisplayName();
/**
* Returns true if the given test should be executed, false if it should be ignored. Default is true.
*/
protected boolean isTestEnabled(TestDetails testDetails) {
return true;
}
/**
* Checks that this execution can be executed, throwing an exception if not.
*/
protected void assertCanExecute() {
}
/**
* Loads the target classes for this execution. Default is the target class that this runner was constructed with.
*/
protected List extends Class>> loadTargetClasses() {
return Collections.singletonList(target);
}
private static class CannotExecuteRunner extends Runner {
private final Description description;
private final Throwable failure;
public CannotExecuteRunner(String displayName, Class> testClass, Throwable failure) {
description = Description.createSuiteDescription(String.format("%s(%s)", displayName, testClass.getName()));
this.failure = failure;
}
@Override
public Description getDescription() {
return description;
}
@Override
public void run(RunNotifier notifier) {
Description description = getDescription();
notifier.fireTestStarted(description);
notifier.fireTestFailure(new Failure(description, failure));
notifier.fireTestFinished(description);
}
}
private class NestedRunListener extends RunListener {
private final RunNotifier notifier;
boolean started;
boolean complete;
public NestedRunListener(RunNotifier notifier) {
this.notifier = notifier;
}
@Override
public void testStarted(Description description) {
Description translated = translateDescription(description);
notifier.fireTestStarted(translated);
if (!started && !complete) {
try {
assertCanExecute();
started = true;
before();
} catch (Throwable t) {
notifier.fireTestFailure(new Failure(translated, t));
}
}
}
@Override
public void testFailure(Failure failure) {
Description translated = translateDescription(failure.getDescription());
notifier.fireTestFailure(new Failure(translated, failure.getException()));
}
@Override
public void testAssumptionFailure(Failure failure) {
Description translated = translateDescription(failure.getDescription());
notifier.fireTestAssumptionFailed(new Failure(translated, failure.getException()));
}
@Override
public void testIgnored(Description description) {
Description translated = translateDescription(description);
notifier.fireTestIgnored(translated);
}
@Override
public void testFinished(Description description) {
Description translated = translateDescription(description);
notifier.fireTestFinished(translated);
}
public void cleanup() {
if (started) {
after();
}
// Prevent further tests (ignored) from triggering start actions
complete = true;
}
}
}
public interface TestDetails {
/**
* Locates the given annotation for the test. May be inherited from test class.
*/
@Nullable
A getAnnotation(Class type);
}
private static class TestDescriptionBackedTestDetails implements TestDetails {
private final Description parent;
private final Description test;
private TestDescriptionBackedTestDetails(Description parent, Description test) {
this.parent = parent;
this.test = test;
}
@Override
public String toString() {
return test.toString();
}
public A getAnnotation(Class type) {
A annotation = test.getAnnotation(type);
if (annotation != null) {
return annotation;
}
return parent.getAnnotation(type);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy