diff options
author | Paul Duffin <paulduffin@google.com> | 2016-12-14 11:13:37 +0000 |
---|---|---|
committer | Paul Duffin <paulduffin@google.com> | 2016-12-15 18:53:12 +0000 |
commit | 4dd042caba6f0ee54f604a409df7152b3e8205bb (patch) | |
tree | fd9aa7f11b8c8f927c9e98fd828e60086895ca5e /src/main/java/org/junit/runners | |
parent | 50db5f5810104e1dd0b0294145e9d3e602bb2627 (diff) | |
download | junit-4dd042caba6f0ee54f604a409df7152b3e8205bb.tar.gz |
Moved source to match upstream file structure
Will make it simpler to update JUnit source.
Bug: 33613916
Test: make checkbuild
Change-Id: I76984a6defd3e40f34eea995e6ed865d32d53da3
Diffstat (limited to 'src/main/java/org/junit/runners')
17 files changed, 1829 insertions, 0 deletions
diff --git a/src/main/java/org/junit/runners/AllTests.java b/src/main/java/org/junit/runners/AllTests.java new file mode 100644 index 0000000..50c02db --- /dev/null +++ b/src/main/java/org/junit/runners/AllTests.java @@ -0,0 +1,24 @@ +package org.junit.runners; + +import org.junit.internal.runners.SuiteMethod; + +/** Runner for use with JUnit 3.8.x-style AllTests classes + * (those that only implement a static <code>suite()</code> + * method). For example: + * <pre> + * @RunWith(AllTests.class) + * public class ProductTests { + * public static junit.framework.Test suite() { + * ... + * } + * } + * </pre> + */ +public class AllTests extends SuiteMethod { + /** + * Only called reflectively. Do not use programmatically. + */ + public AllTests(Class<?> klass) throws Throwable { + super(klass); + } +} diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java new file mode 100644 index 0000000..92e0d07 --- /dev/null +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -0,0 +1,407 @@ +package org.junit.runners; + +import static org.junit.internal.runners.rules.RuleFieldValidator.RULE_VALIDATOR; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.Test.None; +import org.junit.internal.runners.model.ReflectiveCallable; +import org.junit.internal.runners.statements.ExpectException; +import org.junit.internal.runners.statements.Fail; +import org.junit.internal.runners.statements.FailOnTimeout; +import org.junit.internal.runners.statements.InvokeMethod; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.RunRules; +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.MultipleFailureException; +import org.junit.runners.model.Statement; + +/** + * Implements the JUnit 4 standard test case class model, as defined by the + * annotations in the org.junit package. Many users will never notice this + * class: it is now the default test class runner, but it should have exactly + * the same behavior as the old test class runner ({@code JUnit4ClassRunner}). + * + * BlockJUnit4ClassRunner has advantages for writers of custom JUnit runners + * that are slight changes to the default behavior, however: + * + * <ul> + * <li>It has a much simpler implementation based on {@link Statement}s, + * allowing new operations to be inserted into the appropriate point in the + * execution flow. + * + * <li>It is published, and extension and reuse are encouraged, whereas {@code + * JUnit4ClassRunner} was in an internal package, and is now deprecated. + * </ul> + */ +public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { + /** + * Creates a BlockJUnit4ClassRunner to run {@code klass} + * + * @throws InitializationError + * if the test class is malformed. + */ + public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError { + super(klass); + } + + // + // Implementation of ParentRunner + // + + @Override + protected void runChild(final FrameworkMethod method, RunNotifier notifier) { + Description description= describeChild(method); + if (method.getAnnotation(Ignore.class) != null) { + notifier.fireTestIgnored(description); + } else { + runLeaf(methodBlock(method), description, notifier); + } + } + + @Override + protected Description describeChild(FrameworkMethod method) { + return Description.createTestDescription(getTestClass().getJavaClass(), + testName(method), method.getAnnotations()); + } + + @Override + protected List<FrameworkMethod> getChildren() { + return computeTestMethods(); + } + + // + // Override in subclasses + // + + /** + * Returns the methods that run tests. Default implementation returns all + * methods annotated with {@code @Test} on this class and superclasses that + * are not overridden. + */ + protected List<FrameworkMethod> computeTestMethods() { + return getTestClass().getAnnotatedMethods(Test.class); + } + + @Override + protected void collectInitializationErrors(List<Throwable> errors) { + super.collectInitializationErrors(errors); + + validateNoNonStaticInnerClass(errors); + validateConstructor(errors); + validateInstanceMethods(errors); + validateFields(errors); + } + + protected void validateNoNonStaticInnerClass(List<Throwable> errors) { + if (getTestClass().isANonStaticInnerClass()) { + String gripe= "The inner class " + getTestClass().getName() + + " is not static."; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class has more than one constructor, + * or if the constructor takes parameters. Override if a subclass requires + * different validation rules. + */ + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + validateZeroArgConstructor(errors); + } + + /** + * Adds to {@code errors} if the test class has more than one constructor + * (do not override) + */ + protected void validateOnlyOneConstructor(List<Throwable> errors) { + if (!hasOneConstructor()) { + String gripe= "Test class should have exactly one public constructor"; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class's single constructor takes + * parameters (do not override) + */ + protected void validateZeroArgConstructor(List<Throwable> errors) { + if (!getTestClass().isANonStaticInnerClass() + && hasOneConstructor() + && (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) { + String gripe= "Test class should have exactly one public zero-argument constructor"; + errors.add(new Exception(gripe)); + } + } + + private boolean hasOneConstructor() { + return getTestClass().getJavaClass().getConstructors().length == 1; + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}, + * {@code @Before}, or {@code @After} that is not a public, void instance + * method with no arguments. + * + * @deprecated unused API, will go away in future version + */ + @Deprecated + protected void validateInstanceMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(After.class, false, errors); + validatePublicVoidNoArgMethods(Before.class, false, errors); + validateTestMethods(errors); + + if (computeTestMethods().size() == 0) + errors.add(new Exception("No runnable methods")); + } + + private void validateFields(List<Throwable> errors) { + RULE_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}that + * is not a public, void instance method with no arguments. + */ + protected void validateTestMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(Test.class, false, errors); + } + + /** + * Returns a new fixture for running a test. Default implementation executes + * the test class's no-argument constructor (validation should have ensured + * one exists). + */ + protected Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(); + } + + /** + * Returns the name that describes {@code method} for {@link Description}s. + * Default implementation is the method's name + */ + protected String testName(FrameworkMethod method) { + return method.getName(); + } + + /** + * Returns a Statement that, when executed, either returns normally if + * {@code method} passes, or throws an exception if {@code method} fails. + * + * Here is an outline of the default implementation: + * + * <ul> + * <li>Invoke {@code method} on the result of {@code createTest()}, and + * throw any exceptions thrown by either operation. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * expecting} attribute, return normally only if the previous step threw an + * exception of the correct type, and throw an exception otherwise. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * timeout} attribute, throw an exception if the previous step takes more + * than the specified number of milliseconds. + * <li>ALWAYS run all non-overridden {@code @Before} methods on this class + * and superclasses before any of the previous steps; if any throws an + * Exception, stop execution and pass the exception on. + * <li>ALWAYS run all non-overridden {@code @After} methods on this class + * and superclasses after any of the previous steps; all After methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + * <li>ALWAYS allow {@code @Rule} fields to modify the execution of the + * above steps. A {@code Rule} may prevent all execution of the above steps, + * or add additional behavior before and after, or modify thrown exceptions. + * For more information, see {@link TestRule} + * </ul> + * + * This can be overridden in subclasses, either by overriding this method, + * or the implementations creating each sub-statement. + */ + protected Statement methodBlock(FrameworkMethod method) { + Object test; + try { + test= new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return createTest(); + } + }.run(); + } catch (Throwable e) { + return new Fail(e); + } + + Statement statement= methodInvoker(method, test); + statement= possiblyExpectingExceptions(method, test, statement); + statement= withPotentialTimeout(method, test, statement); + statement= withBefores(method, test, statement); + statement= withAfters(method, test, statement); + statement= withRules(method, test, statement); + return statement; + } + + // + // Statement builders + // + + /** + * Returns a {@link Statement} that invokes {@code method} on {@code test} + */ + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return new InvokeMethod(method, test); + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code expecting} attribute, return normally only if {@code next} + * throws an exception of the correct type, and throw an exception + * otherwise. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement possiblyExpectingExceptions(FrameworkMethod method, + Object test, Statement next) { + Test annotation= method.getAnnotation(Test.class); + return expectsException(annotation) ? new ExpectException(next, + getExpectedException(annotation)) : next; + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code timeout} attribute, throw an exception if {@code next} + * takes more than the specified number of milliseconds. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withPotentialTimeout(FrameworkMethod method, + Object test, Statement next) { + long timeout= getTimeout(method.getAnnotation(Test.class)); + return timeout > 0 ? new FailOnTimeout(next, timeout) : next; + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @Before} + * methods on this class and superclasses before running {@code next}; if + * any throws an Exception, stop execution and pass the exception on. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withBefores(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> befores= getTestClass().getAnnotatedMethods( + Before.class); + return befores.isEmpty() ? statement : new RunBefores(statement, + befores, target); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @After} + * methods on this class and superclasses before running {@code next}; all + * After methods are always executed: exceptions thrown by previous steps + * are combined, if necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withAfters(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> afters= getTestClass().getAnnotatedMethods( + After.class); + return afters.isEmpty() ? statement : new RunAfters(statement, afters, + target); + } + + private Statement withRules(FrameworkMethod method, Object target, + Statement statement) { + Statement result= statement; + result= withMethodRules(method, target, result); + result= withTestRules(method, target, result); + return result; + } + + @SuppressWarnings("deprecation") + private Statement withMethodRules(FrameworkMethod method, Object target, + Statement result) { + List<TestRule> testRules= getTestRules(target); + for (org.junit.rules.MethodRule each : getMethodRules(target)) + if (! testRules.contains(each)) + result= each.apply(result, method, target); + return result; + } + + @SuppressWarnings("deprecation") + private List<org.junit.rules.MethodRule> getMethodRules(Object target) { + return rules(target); + } + + /** + * @param target + * the test case instance + * @return a list of MethodRules that should be applied when executing this + * test + * @deprecated {@link org.junit.rules.MethodRule} is a deprecated interface. Port to + * {@link TestRule} and + * {@link BlockJUnit4ClassRunner#getTestRules(Object)} + */ + @Deprecated + protected List<org.junit.rules.MethodRule> rules(Object target) { + return getTestClass().getAnnotatedFieldValues(target, Rule.class, + org.junit.rules.MethodRule.class); + } + + /** + * Returns a {@link Statement}: apply all non-static {@link Value} fields + * annotated with {@link Rule}. + * + * @param statement The base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withTestRules(FrameworkMethod method, Object target, + Statement statement) { + List<TestRule> testRules= getTestRules(target); + return testRules.isEmpty() ? statement : + new RunRules(statement, testRules, describeChild(method)); + } + + /** + * @param target + * the test case instance + * @return a list of TestRules that should be applied when executing this + * test + */ + protected List<TestRule> getTestRules(Object target) { + return getTestClass().getAnnotatedFieldValues(target, + Rule.class, TestRule.class); + } + + private Class<? extends Throwable> getExpectedException(Test annotation) { + if (annotation == null || annotation.expected() == None.class) + return null; + else + return annotation.expected(); + } + + private boolean expectsException(Test annotation) { + return getExpectedException(annotation) != null; + } + + private long getTimeout(Test annotation) { + if (annotation == null) + return 0; + return annotation.timeout(); + } +} diff --git a/src/main/java/org/junit/runners/JUnit4.java b/src/main/java/org/junit/runners/JUnit4.java new file mode 100644 index 0000000..1e1f347 --- /dev/null +++ b/src/main/java/org/junit/runners/JUnit4.java @@ -0,0 +1,22 @@ +package org.junit.runners; + +import org.junit.runners.model.InitializationError; + +/** + * Aliases the current default JUnit 4 class runner, for future-proofing. If + * future versions of JUnit change the default Runner class, they will also + * change the definition of this class. Developers wanting to explicitly tag a + * class as a JUnit 4 class should use {@code @RunWith(JUnit4.class)}, not, + * for example in JUnit 4.5, {@code @RunWith(BlockJUnit4ClassRunner.class)}. + * This is the only way this class should be used--any extension that + * depends on the implementation details of this class is likely to break + * in future versions. + */ +public final class JUnit4 extends BlockJUnit4ClassRunner { + /** + * Constructs a new instance of the default runner + */ + public JUnit4(Class<?> klass) throws InitializationError { + super(klass); + } +} diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java new file mode 100644 index 0000000..3ebfead --- /dev/null +++ b/src/main/java/org/junit/runners/Parameterized.java @@ -0,0 +1,167 @@ +package org.junit.runners; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.runner.Runner; +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.junit.runners.model.TestClass; + +/** + * <p> + * The custom runner <code>Parameterized</code> implements parameterized tests. + * When running a parameterized test class, instances are created for the + * cross-product of the test methods and the test data elements. + * </p> + * + * For example, to test a Fibonacci function, write: + * + * <pre> + * @RunWith(Parameterized.class) + * public class FibonacciTest { + * @Parameters + * public static List<Object[]> data() { + * return Arrays.asList(new Object[][] { + * { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } + * }); + * } + * + * private int fInput; + * + * private int fExpected; + * + * public FibonacciTest(int input, int expected) { + * fInput= input; + * fExpected= expected; + * } + * + * @Test + * public void test() { + * assertEquals(fExpected, Fibonacci.compute(fInput)); + * } + * } + * </pre> + * + * <p> + * Each instance of <code>FibonacciTest</code> will be constructed using the + * two-argument constructor and the data values in the + * <code>@Parameters</code> method. + * </p> + */ +public class Parameterized extends Suite { + /** + * Annotation for a method which provides parameters to be injected into the + * test class constructor by <code>Parameterized</code> + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface Parameters { + } + + private class TestClassRunnerForParameters extends + BlockJUnit4ClassRunner { + private final int fParameterSetNumber; + + private final List<Object[]> fParameterList; + + TestClassRunnerForParameters(Class<?> type, + List<Object[]> parameterList, int i) throws InitializationError { + super(type); + fParameterList= parameterList; + fParameterSetNumber= i; + } + + @Override + public Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance( + computeParams()); + } + + private Object[] computeParams() throws Exception { + try { + return fParameterList.get(fParameterSetNumber); + } catch (ClassCastException e) { + throw new Exception(String.format( + "%s.%s() must return a Collection of arrays.", + getTestClass().getName(), getParametersMethod( + getTestClass()).getName())); + } + } + + @Override + protected String getName() { + return String.format("[%s]", fParameterSetNumber); + } + + @Override + protected String testName(final FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), + fParameterSetNumber); + } + + @Override + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } + + @Override + protected Annotation[] getRunnerAnnotations() { + return new Annotation[0]; + } + } + + private final ArrayList<Runner> runners= new ArrayList<Runner>(); + + /** + * Only called reflectively. Do not use programmatically. + */ + public Parameterized(Class<?> klass) throws Throwable { + super(klass, Collections.<Runner>emptyList()); + List<Object[]> parametersList= getParametersList(getTestClass()); + for (int i= 0; i < parametersList.size(); i++) + runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(), + parametersList, i)); + } + + @Override + protected List<Runner> getChildren() { + return runners; + } + + @SuppressWarnings("unchecked") + private List<Object[]> getParametersList(TestClass klass) + throws Throwable { + return (List<Object[]>) getParametersMethod(klass).invokeExplosively( + null); + } + + private FrameworkMethod getParametersMethod(TestClass testClass) + throws Exception { + List<FrameworkMethod> methods= testClass + .getAnnotatedMethods(Parameters.class); + for (FrameworkMethod each : methods) { + int modifiers= each.getMethod().getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) + return each; + } + + throw new Exception("No public static parameters method on class " + + testClass.getName()); + } + +} diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java new file mode 100644 index 0000000..a41aad3 --- /dev/null +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -0,0 +1,378 @@ +package org.junit.runners; + +import static org.junit.internal.runners.rules.RuleFieldValidator.CLASS_RULE_VALIDATOR; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.RunRules; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +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.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.RunnerScheduler; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +/** + * Provides most of the functionality specific to a Runner that implements a + * "parent node" in the test tree, with children defined by objects of some data + * type {@code T}. (For {@link BlockJUnit4ClassRunner}, {@code T} is + * {@link Method} . For {@link Suite}, {@code T} is {@link Class}.) Subclasses + * must implement finding the children of the node, describing each child, and + * running each child. ParentRunner will filter and sort children, handle + * {@code @BeforeClass} and {@code @AfterClass} methods, + * handle annotated {@link ClassRule}s, create a composite + * {@link Description}, and run children sequentially. + */ +public abstract class ParentRunner<T> extends Runner implements Filterable, + Sortable { + private final TestClass fTestClass; + + private Sorter fSorter= Sorter.NULL; + + private List<T> fFilteredChildren= null; + + private RunnerScheduler fScheduler= new RunnerScheduler() { + public void schedule(Runnable childStatement) { + childStatement.run(); + } + + public void finished() { + // do nothing + } + }; + + /** + * Constructs a new {@code ParentRunner} that will run {@code @TestClass} + * @throws InitializationError + */ + protected ParentRunner(Class<?> testClass) throws InitializationError { + fTestClass= new TestClass(testClass); + validate(); + } + + // + // Must be overridden + // + + /** + * Returns a list of objects that define the children of this Runner. + */ + protected abstract List<T> getChildren(); + + /** + * Returns a {@link Description} for {@code child}, which can be assumed to + * be an element of the list returned by {@link ParentRunner#getChildren()} + */ + protected abstract Description describeChild(T child); + + /** + * Runs the test corresponding to {@code child}, which can be assumed to be + * an element of the list returned by {@link ParentRunner#getChildren()}. + * Subclasses are responsible for making sure that relevant test events are + * reported through {@code notifier} + */ + protected abstract void runChild(T child, RunNotifier notifier); + + // + // May be overridden + // + + /** + * Adds to {@code errors} a throwable for each problem noted with the test class (available from {@link #getTestClass()}). + * Default implementation adds an error for each method annotated with + * {@code @BeforeClass} or {@code @AfterClass} that is not + * {@code public static void} with no arguments. + */ + protected void collectInitializationErrors(List<Throwable> errors) { + validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); + validatePublicVoidNoArgMethods(AfterClass.class, true, errors); + validateClassRules(errors); + } + + /** + * Adds to {@code errors} if any method in this class is annotated with + * {@code annotation}, but: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, + boolean isStatic, List<Throwable> errors) { + List<FrameworkMethod> methods= getTestClass().getAnnotatedMethods(annotation); + + for (FrameworkMethod eachTestMethod : methods) + eachTestMethod.validatePublicVoidNoArg(isStatic, errors); + } + + private void validateClassRules(List<Throwable> errors) { + CLASS_RULE_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Constructs a {@code Statement} to run all of the tests in the test class. Override to add pre-/post-processing. + * Here is an outline of the implementation: + * <ul> + * <li>Call {@link #runChild(Object, RunNotifier)} on each object returned by {@link #getChildren()} (subject to any imposed filter and sort).</li> + * <li>ALWAYS run all non-overridden {@code @BeforeClass} methods on this class + * and superclasses before the previous step; if any throws an + * Exception, stop execution and pass the exception on. + * <li>ALWAYS run all non-overridden {@code @AfterClass} methods on this class + * and superclasses before any of the previous steps; all AfterClass methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link MultipleFailureException}. + * </ul> + * @param notifier + * @return {@code Statement} + */ + protected Statement classBlock(final RunNotifier notifier) { + Statement statement= childrenInvoker(notifier); + statement= withBeforeClasses(statement); + statement= withAfterClasses(statement); + statement= withClassRules(statement); + return statement; + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class + * and superclasses before executing {@code statement}; if any throws an + * Exception, stop execution and pass the exception on. + */ + protected Statement withBeforeClasses(Statement statement) { + List<FrameworkMethod> befores= fTestClass + .getAnnotatedMethods(BeforeClass.class); + return befores.isEmpty() ? statement : + new RunBefores(statement, befores, null); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class + * and superclasses before executing {@code statement}; all AfterClass methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link MultipleFailureException}. + */ + protected Statement withAfterClasses(Statement statement) { + List<FrameworkMethod> afters= fTestClass + .getAnnotatedMethods(AfterClass.class); + return afters.isEmpty() ? statement : + new RunAfters(statement, afters, null); + } + + /** + * Returns a {@link Statement}: apply all + * static fields assignable to {@link TestRule} + * annotated with {@link ClassRule}. + * + * @param statement + * the base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withClassRules(Statement statement) { + List<TestRule> classRules= classRules(); + return classRules.isEmpty() ? statement : + new RunRules(statement, classRules, getDescription()); + } + + /** + * @return the {@code ClassRule}s that can transform the block that runs + * each method in the tested class. + */ + protected List<TestRule> classRules() { + return fTestClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class); + } + + /** + * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} + * on each object returned by {@link #getChildren()} (subject to any imposed + * filter and sort) + */ + protected Statement childrenInvoker(final RunNotifier notifier) { + return new Statement() { + @Override + public void evaluate() { + runChildren(notifier); + } + }; + } + + private void runChildren(final RunNotifier notifier) { + for (final T each : getFilteredChildren()) + fScheduler.schedule(new Runnable() { + public void run() { + ParentRunner.this.runChild(each, notifier); + } + }); + fScheduler.finished(); + } + + /** + * Returns a name used to describe this Runner + */ + protected String getName() { + return fTestClass.getName(); + } + + // + // Available for subclasses + // + + /** + * Returns a {@link TestClass} object wrapping the class to be executed. + */ + public final TestClass getTestClass() { + return fTestClass; + } + + /** + * Runs a {@link Statement} that represents a leaf (aka atomic) test. + */ + protected final void runLeaf(Statement statement, Description description, + RunNotifier notifier) { + EachTestNotifier eachNotifier= new EachTestNotifier(notifier, description); + eachNotifier.fireTestStarted(); + try { + statement.evaluate(); + } catch (AssumptionViolatedException e) { + eachNotifier.addFailedAssumption(e); + } catch (Throwable e) { + eachNotifier.addFailure(e); + } finally { + eachNotifier.fireTestFinished(); + } + } + + /** + * @return the annotations that should be attached to this runner's + * description. + */ + protected Annotation[] getRunnerAnnotations() { + return fTestClass.getAnnotations(); + } + + // + // Implementation of Runner + // + + @Override + public Description getDescription() { + Description description= Description.createSuiteDescription(getName(), + getRunnerAnnotations()); + for (T child : getFilteredChildren()) + description.addChild(describeChild(child)); + return description; + } + + @Override + public void run(final RunNotifier notifier) { + EachTestNotifier testNotifier= new EachTestNotifier(notifier, + getDescription()); + try { + Statement statement= classBlock(notifier); + statement.evaluate(); + } catch (AssumptionViolatedException e) { + testNotifier.fireTestIgnored(); + } catch (StoppedByUserException e) { + throw e; + } catch (Throwable e) { + testNotifier.addFailure(e); + } + } + + // + // Implementation of Filterable and Sortable + // + + public void filter(Filter filter) throws NoTestsRemainException { + for (Iterator<T> iter = getFilteredChildren().iterator(); iter.hasNext(); ) { + T each = iter.next(); + if (shouldRun(filter, each)) + try { + filter.apply(each); + } catch (NoTestsRemainException e) { + iter.remove(); + } + else + iter.remove(); + } + if (getFilteredChildren().isEmpty()) { + throw new NoTestsRemainException(); + } + } + + public void sort(Sorter sorter) { + fSorter= sorter; + for (T each : getFilteredChildren()) + sortChild(each); + Collections.sort(getFilteredChildren(), comparator()); + } + + // + // Private implementation + // + + private void validate() throws InitializationError { + List<Throwable> errors= new ArrayList<Throwable>(); + collectInitializationErrors(errors); + if (!errors.isEmpty()) + throw new InitializationError(errors); + } + + private List<T> getFilteredChildren() { + if (fFilteredChildren == null) + fFilteredChildren = new ArrayList<T>(getChildren()); + return fFilteredChildren; + } + + private void sortChild(T child) { + fSorter.apply(child); + } + + private boolean shouldRun(Filter filter, T each) { + return filter.shouldRun(describeChild(each)); + } + + private Comparator<? super T> comparator() { + return new Comparator<T>() { + public int compare(T o1, T o2) { + return fSorter.compare(describeChild(o1), describeChild(o2)); + } + }; + } + + /** + * Sets a scheduler that determines the order and parallelization + * of children. Highly experimental feature that may change. + */ + public void setScheduler(RunnerScheduler scheduler) { + this.fScheduler = scheduler; + } +} diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java new file mode 100644 index 0000000..1b3bb48 --- /dev/null +++ b/src/main/java/org/junit/runners/Suite.java @@ -0,0 +1,130 @@ +package org.junit.runners; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; + +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +/** + * Using <code>Suite</code> as a runner allows you to manually + * build a suite containing tests from many classes. It is the JUnit 4 equivalent of the JUnit 3.8.x + * static {@link junit.framework.Test} <code>suite()</code> method. To use it, annotate a class + * with <code>@RunWith(Suite.class)</code> and <code>@SuiteClasses({TestClass1.class, ...})</code>. + * When you run this class, it will run all the tests in all the suite classes. + */ +public class Suite extends ParentRunner<Runner> { + /** + * Returns an empty suite. + */ + public static Runner emptySuite() { + try { + return new Suite((Class<?>)null, new Class<?>[0]); + } catch (InitializationError e) { + throw new RuntimeException("This shouldn't be possible"); + } + } + + /** + * The <code>SuiteClasses</code> annotation specifies the classes to be run when a class + * annotated with <code>@RunWith(Suite.class)</code> is run. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Inherited + public @interface SuiteClasses { + /** + * @return the classes to be run + */ + public Class<?>[] value(); + } + + private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError { + SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class); + if (annotation == null) + throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName())); + return annotation.value(); + } + + private final List<Runner> fRunners; + + /** + * Called reflectively on classes annotated with <code>@RunWith(Suite.class)</code> + * + * @param klass the root class + * @param builder builds runners for classes in the suite + * @throws InitializationError + */ + public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError { + this(builder, klass, getAnnotatedClasses(klass)); + } + + /** + * Call this when there is no single root class (for example, multiple class names + * passed on the command line to {@link org.junit.runner.JUnitCore} + * + * @param builder builds runners for classes in the suite + * @param classes the classes in the suite + * @throws InitializationError + */ + public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError { + this(null, builder.runners(null, classes)); + } + + /** + * Call this when the default builder is good enough. Left in for compatibility with JUnit 4.4. + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + * @throws InitializationError + */ + protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses); + } + + /** + * Called by this class and subclasses once the classes making up the suite have been determined + * + * @param builder builds runners for classes in the suite + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + * @throws InitializationError + */ + protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(klass, builder.runners(klass, suiteClasses)); + } + + /** + * Called by this class and subclasses once the runners making up the suite have been determined + * + * @param klass root of the suite + * @param runners for each class in the suite, a {@link Runner} + * @throws InitializationError + */ + protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError { + super(klass); + fRunners = runners; + } + + @Override + protected List<Runner> getChildren() { + return fRunners; + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + protected void runChild(Runner runner, final RunNotifier notifier) { + runner.run(notifier); + } +} diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java new file mode 100644 index 0000000..4a4d4a4 --- /dev/null +++ b/src/main/java/org/junit/runners/model/FrameworkField.java @@ -0,0 +1,65 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Represents a field on a test class (currently used only for Rules in + * {@link BlockJUnit4ClassRunner}, but custom runners can make other uses) + */ +public class FrameworkField extends FrameworkMember<FrameworkField> { + private final Field fField; + + FrameworkField(Field field) { + fField= field; + } + + public String getName() { + return getField().getName(); + } + + @Override + public Annotation[] getAnnotations() { + return fField.getAnnotations(); + } + + public boolean isPublic() { + int modifiers= fField.getModifiers(); + return Modifier.isPublic(modifiers); + } + + @Override + public boolean isShadowedBy(FrameworkField otherMember) { + return otherMember.getName().equals(getName()); + } + + public boolean isStatic() { + int modifiers= fField.getModifiers(); + return Modifier.isStatic(modifiers); + } + + /** + * @return the underlying java Field + */ + public Field getField() { + return fField; + } + + /** + * @return the underlying Java Field type + * @see java.lang.reflect.Field#getType() + */ + public Class<?> getType() { + return fField.getType(); + } + + /** + * Attempts to retrieve the value of this field on {@code target} + */ + public Object get(Object target) throws IllegalArgumentException, IllegalAccessException { + return fField.get(target); + } +} diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java new file mode 100644 index 0000000..9cccd4b --- /dev/null +++ b/src/main/java/org/junit/runners/model/FrameworkMember.java @@ -0,0 +1,20 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.util.List; + +abstract class FrameworkMember<T extends FrameworkMember<T>> { + /** + * Returns the annotations on this method + */ + abstract Annotation[] getAnnotations(); + + abstract boolean isShadowedBy(T otherMember); + + boolean isShadowedBy(List<T> members) { + for (T each : members) + if (isShadowedBy(each)) + return true; + return false; + } +} diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java new file mode 100644 index 0000000..81c8963 --- /dev/null +++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java @@ -0,0 +1,156 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.List; + +import org.junit.internal.runners.model.ReflectiveCallable; + +/** + * Represents a method on a test class to be invoked at the appropriate point in + * test execution. These methods are usually marked with an annotation (such as + * {@code @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass}, + * {@code @AfterClass}, etc.) + */ +public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { + final Method fMethod; + + /** + * Returns a new {@code FrameworkMethod} for {@code method} + */ + public FrameworkMethod(Method method) { + fMethod= method; + } + + /** + * Returns the underlying Java method + */ + public Method getMethod() { + return fMethod; + } + + /** + * Returns the result of invoking this method on {@code target} with + * parameters {@code params}. {@link InvocationTargetException}s thrown are + * unwrapped, and their causes rethrown. + */ + public Object invokeExplosively(final Object target, final Object... params) + throws Throwable { + return new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return fMethod.invoke(target, params); + } + }.run(); + } + + /** + * Returns the method's name + */ + public String getName() { + return fMethod.getName(); + } + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) { + validatePublicVoid(isStatic, errors); + if (fMethod.getParameterTypes().length != 0) + errors.add(new Exception("Method " + fMethod.getName() + " should have no parameters")); + } + + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + public void validatePublicVoid(boolean isStatic, List<Throwable> errors) { + if (Modifier.isStatic(fMethod.getModifiers()) != isStatic) { + String state= isStatic ? "should" : "should not"; + errors.add(new Exception("Method " + fMethod.getName() + "() " + state + " be static")); + } + if (!Modifier.isPublic(fMethod.getDeclaringClass().getModifiers())) + errors.add(new Exception("Class " + fMethod.getDeclaringClass().getName() + " should be public")); + if (!Modifier.isPublic(fMethod.getModifiers())) + errors.add(new Exception("Method " + fMethod.getName() + "() should be public")); + if (fMethod.getReturnType() != Void.TYPE) + errors.add(new Exception("Method " + fMethod.getName() + "() should be void")); + } + + public void validateNoTypeParametersOnArgs(List<Throwable> errors) { + new NoGenericTypeParametersValidator(fMethod).validate(errors); + } + + @Override + public boolean isShadowedBy(FrameworkMethod other) { + if (!other.getName().equals(getName())) + return false; + if (other.getParameterTypes().length != getParameterTypes().length) + return false; + for (int i= 0; i < other.getParameterTypes().length; i++) + if (!other.getParameterTypes()[i].equals(getParameterTypes()[i])) + return false; + return true; + } + + @Override + public boolean equals(Object obj) { + if (!FrameworkMethod.class.isInstance(obj)) + return false; + return ((FrameworkMethod) obj).fMethod.equals(fMethod); + } + + @Override + public int hashCode() { + return fMethod.hashCode(); + } + + /** + * Returns true iff this is a no-arg method that returns a value assignable + * to {@code type} + * + * @deprecated This is used only by the Theories runner, and does not + * use all the generic type info that it ought to. It will be replaced + * with a forthcoming ParameterSignature#canAcceptResultOf(FrameworkMethod) + * once Theories moves to junit-contrib. + */ + @Deprecated + public boolean producesType(Type type) { + return getParameterTypes().length == 0 && type instanceof Class<?> + && ((Class<?>) type).isAssignableFrom(fMethod.getReturnType()); + } + + private Class<?>[] getParameterTypes() { + return fMethod.getParameterTypes(); + } + + /** + * Returns the annotations on this method + */ + @Override + public Annotation[] getAnnotations() { + return fMethod.getAnnotations(); + } + + /** + * Returns the annotation of type {@code annotationType} on this method, if + * one exists. + */ + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + return fMethod.getAnnotation(annotationType); + } +} diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java new file mode 100644 index 0000000..4de9ea7 --- /dev/null +++ b/src/main/java/org/junit/runners/model/InitializationError.java @@ -0,0 +1,39 @@ +package org.junit.runners.model; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents one or more problems encountered while initializing a Runner + */ +public class InitializationError extends Exception { + private static final long serialVersionUID= 1L; + private final List<Throwable> fErrors; + + /** + * Construct a new {@code InitializationError} with one or more + * errors {@code errors} as causes + */ + public InitializationError(List<Throwable> errors) { + fErrors= errors; + } + + public InitializationError(Throwable error) { + this(Arrays.asList(error)); + } + + /** + * Construct a new {@code InitializationError} with one cause + * with message {@code string} + */ + public InitializationError(String string) { + this(new Exception(string)); + } + + /** + * Returns one or more Throwables that led to this initialization error. + */ + public List<Throwable> getCauses() { + return fErrors; + } +} diff --git a/src/main/java/org/junit/runners/model/MultipleFailureException.java b/src/main/java/org/junit/runners/model/MultipleFailureException.java new file mode 100644 index 0000000..6d70ca0 --- /dev/null +++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java @@ -0,0 +1,60 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package org.junit.runners.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Collects multiple {@code Throwable}s into one exception. + */ +public class MultipleFailureException extends Exception { + private static final long serialVersionUID= 1L; + + private final List<Throwable> fErrors; + + public MultipleFailureException(List<Throwable> errors) { + fErrors= new ArrayList<Throwable>(errors); + } + + public List<Throwable> getFailures() { + return Collections.unmodifiableList(fErrors); + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder( + String.format("There were %d errors:", fErrors.size())); + for (Throwable e : fErrors) { + sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); + } + return sb.toString(); + } + + /** + * Asserts that a list of throwables is empty. If it isn't empty, + * will throw {@link MultipleFailureException} (if there are + * multiple throwables in the list) or the first element in the list + * (if there is only one element). + * + * @param errors list to check + * @throws Throwable if the list is not empty + */ + @SuppressWarnings("deprecation") + public static void assertEmpty(List<Throwable> errors) throws Throwable { + if (errors.isEmpty()) + return; + if (errors.size() == 1) + throw errors.get(0); + + /* + * Many places in the code are documented to throw + * org.junit.internal.runners.model.MultipleFailureException. + * That class now extends this one, so we throw the internal + * exception in case developers have tests that catch + * MultipleFailureException. + */ + throw new org.junit.internal.runners.model.MultipleFailureException(errors); + } +} diff --git a/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java b/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java new file mode 100644 index 0000000..77662b8 --- /dev/null +++ b/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java @@ -0,0 +1,53 @@ +package org.junit.runners.model; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.List; + +class NoGenericTypeParametersValidator { + private final Method fMethod; + + NoGenericTypeParametersValidator(Method method) { + this.fMethod = method; + } + + void validate(List<Throwable> errors) { + for (Type each : fMethod.getGenericParameterTypes()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnType(Type type, List<Throwable> errors) { + if (type instanceof TypeVariable<?>) { + errors.add(new Exception("Method " + fMethod.getName() + + "() contains unresolved type variable " + type)); + } else if (type instanceof ParameterizedType) + validateNoTypeParameterOnParameterizedType((ParameterizedType) type, errors); + else if (type instanceof WildcardType) + validateNoTypeParameterOnWildcardType((WildcardType) type, errors); + else if (type instanceof GenericArrayType) + validateNoTypeParameterOnGenericArrayType((GenericArrayType) type, errors); + } + + private void validateNoTypeParameterOnParameterizedType(ParameterizedType parameterized, + List<Throwable> errors) { + for (Type each : parameterized.getActualTypeArguments()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnWildcardType(WildcardType wildcard, + List<Throwable> errors) { + for (Type each : wildcard.getUpperBounds()) + validateNoTypeParameterOnType(each, errors); + for (Type each : wildcard.getLowerBounds()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnGenericArrayType( + GenericArrayType arrayType, List<Throwable> errors) { + validateNoTypeParameterOnType(arrayType.getGenericComponentType(), errors); + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/runners/model/RunnerBuilder.java b/src/main/java/org/junit/runners/model/RunnerBuilder.java new file mode 100644 index 0000000..3a334be --- /dev/null +++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java @@ -0,0 +1,104 @@ +package org.junit.runners.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Runner; + +/** + * A RunnerBuilder is a strategy for constructing runners for classes. + * + * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking + * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself. + * For example, + * imagine a custom runner that builds suites based on a list of classes in a text file: + * + * <pre> + * \@RunWith(TextFileSuite.class) + * \@SuiteSpecFile("mysuite.txt") + * class MySuite {} + * </pre> + * + * The implementation of TextFileSuite might include: + * + * <pre> + * public TextFileSuite(Class testClass, RunnerBuilder builder) { + * // ... + * for (String className : readClassNames()) + * addRunner(builder.runnerForClass(Class.forName(className))); + * // ... + * } + * </pre> + * + * @see org.junit.runners.Suite + */ +public abstract class RunnerBuilder { + private final Set<Class<?>> parents= new HashSet<Class<?>>(); + + /** + * Override to calculate the correct runner for a test class at runtime. + * + * @param testClass class to be run + * @return a Runner + * @throws Throwable if a runner cannot be constructed + */ + public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; + + /** + * Always returns a runner, even if it is just one that prints an error instead of running tests. + * @param testClass class to be run + * @return a Runner + */ + public Runner safeRunnerForClass(Class<?> testClass) { + try { + return runnerForClass(testClass); + } catch (Throwable e) { + return new ErrorReportingRunner(testClass, e); + } + } + + Class<?> addParent(Class<?> parent) throws InitializationError { + if (!parents.add(parent)) + throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); + return parent; + } + + void removeParent(Class<?> klass) { + parents.remove(klass); + } + + /** + * Constructs and returns a list of Runners, one for each child class in + * {@code children}. Care is taken to avoid infinite recursion: + * this builder will throw an exception if it is requested for another + * runner for {@code parent} before this call completes. + */ + public List<Runner> runners(Class<?> parent, Class<?>[] children) + throws InitializationError { + addParent(parent); + + try { + return runners(children); + } finally { + removeParent(parent); + } + } + + public List<Runner> runners(Class<?> parent, List<Class<?>> children) + throws InitializationError { + return runners(parent, children.toArray(new Class<?>[0])); + } + + private List<Runner> runners(Class<?>[] children) { + ArrayList<Runner> runners= new ArrayList<Runner>(); + for (Class<?> each : children) { + Runner childRunner= safeRunnerForClass(each); + if (childRunner != null) + runners.add(childRunner); + } + return runners; + } +} diff --git a/src/main/java/org/junit/runners/model/RunnerScheduler.java b/src/main/java/org/junit/runners/model/RunnerScheduler.java new file mode 100644 index 0000000..fbc25a4 --- /dev/null +++ b/src/main/java/org/junit/runners/model/RunnerScheduler.java @@ -0,0 +1,21 @@ +package org.junit.runners.model; + +/** + * Represents a strategy for scheduling when individual test methods + * should be run (in serial or parallel) + * + * WARNING: still experimental, may go away. + */ +public interface RunnerScheduler { + /** + * Schedule a child statement to run + */ + void schedule(Runnable childStatement); + + /** + * Override to implement any behavior that must occur + * after all children have been scheduled (for example, + * waiting for them all to finish) + */ + void finished(); +} diff --git a/src/main/java/org/junit/runners/model/Statement.java b/src/main/java/org/junit/runners/model/Statement.java new file mode 100644 index 0000000..a7c5478 --- /dev/null +++ b/src/main/java/org/junit/runners/model/Statement.java @@ -0,0 +1,16 @@ +/** + * + */ +package org.junit.runners.model; + + +/** + * Represents one or more actions to be taken at runtime in the course + * of running a JUnit test suite. + */ +public abstract class Statement { + /** + * Run the action, throwing a {@code Throwable} if anything goes wrong. + */ + public abstract void evaluate() throws Throwable; +}
\ No newline at end of file diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java new file mode 100644 index 0000000..362a13a --- /dev/null +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -0,0 +1,159 @@ +package org.junit.runners.model; + +import static java.lang.reflect.Modifier.isStatic; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; + +/** + * Wraps a class to be run, providing method validation and annotation searching + */ +public class TestClass { + private final Class<?> fClass; + + private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<Class<?>, List<FrameworkMethod>>(); + + private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<Class<?>, List<FrameworkField>>(); + + /** + * Creates a {@code TestClass} wrapping {@code klass}. Each time this + * constructor executes, the class is scanned for annotations, which can be + * an expensive process (we hope in future JDK's it will not be.) Therefore, + * try to share instances of {@code TestClass} where possible. + */ + public TestClass(Class<?> klass) { + fClass= klass; + if (klass != null && klass.getConstructors().length > 1) + throw new IllegalArgumentException( + "Test class can only have one constructor"); + + for (Class<?> eachClass : getSuperClasses(fClass)) { + for (Method eachMethod : eachClass.getDeclaredMethods()) + addToAnnotationLists(new FrameworkMethod(eachMethod), + fMethodsForAnnotations); + for (Field eachField : eachClass.getDeclaredFields()) + addToAnnotationLists(new FrameworkField(eachField), + fFieldsForAnnotations); + } + } + + private <T extends FrameworkMember<T>> void addToAnnotationLists(T member, + Map<Class<?>, List<T>> map) { + for (Annotation each : member.getAnnotations()) { + Class<? extends Annotation> type= each.annotationType(); + List<T> members= getAnnotatedMembers(map, type); + if (member.isShadowedBy(members)) + return; + if (runsTopToBottom(type)) + members.add(0, member); + else + members.add(member); + } + } + + /** + * Returns, efficiently, all the non-overridden methods in this class and + * its superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkMethod> getAnnotatedMethods( + Class<? extends Annotation> annotationClass) { + return getAnnotatedMembers(fMethodsForAnnotations, annotationClass); + } + + /** + * Returns, efficiently, all the non-overridden fields in this class and its + * superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkField> getAnnotatedFields( + Class<? extends Annotation> annotationClass) { + return getAnnotatedMembers(fFieldsForAnnotations, annotationClass); + } + + private <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map, + Class<? extends Annotation> type) { + if (!map.containsKey(type)) + map.put(type, new ArrayList<T>()); + return map.get(type); + } + + private boolean runsTopToBottom(Class<? extends Annotation> annotation) { + return annotation.equals(Before.class) + || annotation.equals(BeforeClass.class); + } + + private List<Class<?>> getSuperClasses(Class<?> testClass) { + ArrayList<Class<?>> results= new ArrayList<Class<?>>(); + Class<?> current= testClass; + while (current != null) { + results.add(current); + current= current.getSuperclass(); + } + return results; + } + + /** + * Returns the underlying Java class. + */ + public Class<?> getJavaClass() { + return fClass; + } + + /** + * Returns the class's name. + */ + public String getName() { + if (fClass == null) + return "null"; + return fClass.getName(); + } + + /** + * Returns the only public constructor in the class, or throws an {@code + * AssertionError} if there are more or less than one. + */ + + public Constructor<?> getOnlyConstructor() { + Constructor<?>[] constructors= fClass.getConstructors(); + Assert.assertEquals(1, constructors.length); + return constructors[0]; + } + + /** + * Returns the annotations on this class + */ + public Annotation[] getAnnotations() { + if (fClass == null) + return new Annotation[0]; + return fClass.getAnnotations(); + } + + public <T> List<T> getAnnotatedFieldValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass) { + List<T> results= new ArrayList<T>(); + for (FrameworkField each : getAnnotatedFields(annotationClass)) { + try { + Object fieldValue= each.get(test); + if (valueClass.isInstance(fieldValue)) + results.add(valueClass.cast(fieldValue)); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "How did getFields return a field we couldn't access?", e); + } + } + return results; + } + + public boolean isANonStaticInnerClass() { + return fClass.isMemberClass() && !isStatic(fClass.getModifiers()); + } +} diff --git a/src/main/java/org/junit/runners/package-info.java b/src/main/java/org/junit/runners/package-info.java new file mode 100644 index 0000000..418acaf --- /dev/null +++ b/src/main/java/org/junit/runners/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides standard {@link org.junit.runner.Runner Runner} implementations. + * + * @since 4.0 + * @see org.junit.runner.Runner + * @see org.junit.runners.BlockJUnit4ClassRunner + */ +package org.junit.runners;
\ No newline at end of file |