package com.squareup.burst; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import static com.squareup.burst.BurstJUnit4.nameWithArguments; import static com.squareup.burst.Util.checkNotNull; /** * A set of tests associated with a particular variation of some test class. */ final class BurstRunner extends BlockJUnit4ClassRunner { private final TestConstructor constructor; private final Enum<?>[] constructorArgs; private final List<FrameworkMethod> methods; BurstRunner(Class<?> cls, TestConstructor constructor, Enum<?>[] constructorArgs, List<FrameworkMethod> methods) throws InitializationError { super(checkNotNull(cls, "cls")); this.constructor = checkNotNull(constructor, "constructor"); this.constructorArgs = checkNotNull(constructorArgs, "constructorArgs"); this.methods = checkNotNull(methods, "methods"); } @Override protected List<FrameworkMethod> getChildren() { return methods; } @Override protected String getName() { return nameWithArguments(super.getName(), constructorArgs); } @Override protected Description describeChild(FrameworkMethod method) { return Description.createTestDescription(getName(), method.getName(), method.getAnnotations()); } @Override protected Object createTest() throws Exception { return constructor.newInstance(constructorArgs); } @Override protected Statement withBeforeClasses(Statement statement) { // The parent runner, BurstJUnit4, will handle @BeforeClass/@AfterClass/@ClassRule once for the // whole class. We don't want to repeat them for each variation. return statement; } @Override protected Statement withAfterClasses(Statement statement) { // The parent runner, BurstJUnit4, will handle @BeforeClass/@AfterClass/@ClassRule once for the // whole class. We don't want to repeat them for each variation. return statement; } @Override protected List<TestRule> classRules() { // The parent runner, BurstJUnit4, will handle @BeforeClass/@AfterClass/@ClassRule once for the // whole class. We don't want to repeat them for each variation. return Collections.emptyList(); } @Override protected void validateConstructor(List<Throwable> errors) { // Constructor was already validated by Burst. } @Override protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, boolean isStatic, List<Throwable> errors) { // Methods were already validated by Burst. } /* * ParentRunner's default filter implementation generates a hierarchy of test descriptions, * applies the filter to those descriptions, and removes any test nodes whose descriptions were * all filtered out. * <p> * This would be problematic for us since we generate non-standard test descriptions which include * parameter information. This implementation generates "plain" descriptions without parameter * information and passes those to the filter. */ @Override public void filter(Filter filter) throws NoTestsRemainException { List<FrameworkMethod> filteredChildren = ParentRunnerSpy.getFilteredChildren(this); // Iterate over a clone so that we can safely mutate the original. for (FrameworkMethod child : new ArrayList<>(filteredChildren)) { if (!filter.shouldRun(describeChildPlain(child))) { filteredChildren.remove(child); } } if (filteredChildren.isEmpty()) { throw new NoTestsRemainException(); } } /** * Generates a "plain" description of a burst test, with no parameter information. This should be * used only for filtering. It would not be safe for {@link #describeChild(FrameworkMethod)} to * return these plain descriptions, as then multiple tests would share the same description. */ private Description describeChildPlain(FrameworkMethod method) { return Description.createTestDescription(getTestClass().getJavaClass(), method.getMethod().getName(), method.getAnnotations()); } }