aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit')
-rw-r--r--src/main/java/org/junit/After.java13
-rw-r--r--src/main/java/org/junit/AfterClass.java13
-rw-r--r--src/main/java/org/junit/Assume.java204
-rw-r--r--src/main/java/org/junit/AssumptionViolatedException.java46
-rw-r--r--src/main/java/org/junit/Before.java12
-rw-r--r--src/main/java/org/junit/BeforeClass.java12
-rw-r--r--src/main/java/org/junit/ClassRule.java89
-rw-r--r--src/main/java/org/junit/ComparisonFailure.java299
-rw-r--r--src/main/java/org/junit/FixMethodOrder.java41
-rw-r--r--src/main/java/org/junit/Ignore.java33
-rw-r--r--src/main/java/org/junit/Rule.java69
-rw-r--r--src/main/java/org/junit/Test.java94
-rw-r--r--src/main/java/org/junit/experimental/ParallelComputer.java99
-rw-r--r--src/main/java/org/junit/experimental/categories/Categories.java505
-rw-r--r--src/main/java/org/junit/experimental/categories/Category.java61
-rw-r--r--src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java47
-rw-r--r--src/main/java/org/junit/experimental/categories/CategoryValidator.java62
-rw-r--r--src/main/java/org/junit/experimental/categories/ExcludeCategories.java52
-rw-r--r--src/main/java/org/junit/experimental/categories/IncludeCategories.java52
-rw-r--r--src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java14
-rw-r--r--src/main/java/org/junit/experimental/max/MaxCore.java289
-rw-r--r--src/main/java/org/junit/experimental/max/MaxHistory.java285
-rw-r--r--src/main/java/org/junit/experimental/results/FailureList.java35
-rw-r--r--src/main/java/org/junit/experimental/results/PrintableResult.java82
-rw-r--r--src/main/java/org/junit/experimental/results/ResultMatchers.java102
-rw-r--r--src/main/java/org/junit/experimental/runners/Enclosed.java54
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoint.java51
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoints.java55
-rw-r--r--src/main/java/org/junit/experimental/theories/FromDataPoints.java54
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSignature.java202
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSupplier.java39
-rw-r--r--src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java40
-rw-r--r--src/main/java/org/junit/experimental/theories/PotentialAssignment.java77
-rw-r--r--src/main/java/org/junit/experimental/theories/Theories.java458
-rw-r--r--src/main/java/org/junit/experimental/theories/Theory.java14
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java291
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/Assignments.java249
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java18
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java30
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java85
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java90
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java22
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java26
-rw-r--r--src/main/java/org/junit/internal/ArrayComparisonFailure.java98
-rw-r--r--src/main/java/org/junit/internal/AssumptionViolatedException.java135
-rw-r--r--src/main/java/org/junit/internal/Classes.java18
-rw-r--r--src/main/java/org/junit/internal/ComparisonCriteria.java123
-rw-r--r--src/main/java/org/junit/internal/ExactComparisonCriteria.java8
-rw-r--r--src/main/java/org/junit/internal/InexactComparisonCriteria.java27
-rw-r--r--src/main/java/org/junit/internal/JUnitSystem.java10
-rw-r--r--src/main/java/org/junit/internal/MethodSorter.java72
-rw-r--r--src/main/java/org/junit/internal/RealSystem.java16
-rw-r--r--src/main/java/org/junit/internal/TextListener.java171
-rw-r--r--src/main/java/org/junit/internal/Throwables.java42
-rw-r--r--src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java93
-rw-r--r--src/main/java/org/junit/internal/builders/AnnotatedBuilder.java143
-rw-r--r--src/main/java/org/junit/internal/builders/IgnoredBuilder.java16
-rw-r--r--src/main/java/org/junit/internal/builders/IgnoredClassRunner.java27
-rw-r--r--src/main/java/org/junit/internal/builders/JUnit3Builder.java22
-rw-r--r--src/main/java/org/junit/internal/builders/JUnit4Builder.java11
-rw-r--r--src/main/java/org/junit/internal/builders/NullBuilder.java11
-rw-r--r--src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java32
-rw-r--r--src/main/java/org/junit/internal/matchers/CombinableMatcher.java34
-rw-r--r--src/main/java/org/junit/internal/matchers/Each.java24
-rw-r--r--src/main/java/org/junit/internal/matchers/IsCollectionContaining.java67
-rw-r--r--src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java56
-rw-r--r--src/main/java/org/junit/internal/matchers/StringContains.java31
-rw-r--r--src/main/java/org/junit/internal/matchers/SubstringMatcher.java28
-rw-r--r--src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java50
-rw-r--r--src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java37
-rw-r--r--src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java21
-rw-r--r--src/main/java/org/junit/internal/requests/ClassRequest.java41
-rw-r--r--src/main/java/org/junit/internal/requests/FilterRequest.java57
-rw-r--r--src/main/java/org/junit/internal/requests/SortingRequest.java24
-rw-r--r--src/main/java/org/junit/internal/runners/ClassRoadie.java118
-rw-r--r--src/main/java/org/junit/internal/runners/ErrorReportingRunner.java102
-rw-r--r--src/main/java/org/junit/internal/runners/FailedBefore.java5
-rw-r--r--src/main/java/org/junit/internal/runners/InitializationError.java41
-rw-r--r--src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java294
-rw-r--r--src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java236
-rw-r--r--src/main/java/org/junit/internal/runners/MethodRoadie.java256
-rw-r--r--src/main/java/org/junit/internal/runners/MethodValidator.java120
-rw-r--r--src/main/java/org/junit/internal/runners/SuiteMethod.java37
-rw-r--r--src/main/java/org/junit/internal/runners/TestClass.java165
-rw-r--r--src/main/java/org/junit/internal/runners/TestMethod.java80
-rw-r--r--src/main/java/org/junit/internal/runners/model/EachTestNotifier.java79
-rw-r--r--src/main/java/org/junit/internal/runners/model/MultipleFailureException.java8
-rw-r--r--src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java19
-rw-r--r--src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java92
-rw-r--r--src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java344
-rw-r--r--src/main/java/org/junit/internal/runners/rules/ValidationError.java11
-rw-r--r--src/main/java/org/junit/internal/runners/statements/ExpectException.java60
-rw-r--r--src/main/java/org/junit/internal/runners/statements/Fail.java17
-rw-r--r--src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java372
-rw-r--r--src/main/java/org/junit/internal/runners/statements/InvokeMethod.java27
-rw-r--r--src/main/java/org/junit/internal/runners/statements/RunAfters.java56
-rw-r--r--src/main/java/org/junit/internal/runners/statements/RunBefores.java32
-rw-r--r--src/main/java/org/junit/matchers/JUnitMatchers.java166
-rw-r--r--src/main/java/org/junit/package-info.java2
-rw-r--r--src/main/java/org/junit/rules/DisableOnDebug.java127
-rw-r--r--src/main/java/org/junit/rules/ErrorCollector.java113
-rw-r--r--src/main/java/org/junit/rules/ExpectedException.java382
-rw-r--r--src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java46
-rw-r--r--src/main/java/org/junit/rules/ExternalResource.java101
-rw-r--r--src/main/java/org/junit/rules/MethodRule.java32
-rw-r--r--src/main/java/org/junit/rules/RuleChain.java112
-rw-r--r--src/main/java/org/junit/rules/RunRules.java33
-rw-r--r--src/main/java/org/junit/rules/Stopwatch.java183
-rw-r--r--src/main/java/org/junit/rules/TemporaryFolder.java253
-rw-r--r--src/main/java/org/junit/rules/TestName.java50
-rw-r--r--src/main/java/org/junit/rules/TestRule.java36
-rw-r--r--src/main/java/org/junit/rules/TestWatcher.java228
-rw-r--r--src/main/java/org/junit/rules/TestWatchman.java145
-rw-r--r--src/main/java/org/junit/rules/Timeout.java256
-rw-r--r--src/main/java/org/junit/rules/Verifier.java44
-rw-r--r--src/main/java/org/junit/runner/Computer.java52
-rw-r--r--src/main/java/org/junit/runner/Describable.java10
-rw-r--r--src/main/java/org/junit/runner/FilterFactories.java82
-rw-r--r--src/main/java/org/junit/runner/FilterFactory.java24
-rw-r--r--src/main/java/org/junit/runner/FilterFactoryParams.java23
-rw-r--r--src/main/java/org/junit/runner/JUnitCommandLineParseResult.java149
-rw-r--r--src/main/java/org/junit/runner/JUnitCore.java311
-rw-r--r--src/main/java/org/junit/runner/Request.java273
-rw-r--r--src/main/java/org/junit/runner/Result.java274
-rw-r--r--src/main/java/org/junit/runner/RunWith.java24
-rw-r--r--src/main/java/org/junit/runner/Runner.java47
-rw-r--r--src/main/java/org/junit/runner/manipulation/Filter.java192
-rw-r--r--src/main/java/org/junit/runner/manipulation/Filterable.java15
-rw-r--r--src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java4
-rw-r--r--src/main/java/org/junit/runner/manipulation/Sortable.java13
-rw-r--r--src/main/java/org/junit/runner/manipulation/Sorter.java64
-rw-r--r--src/main/java/org/junit/runner/notification/Failure.java116
-rw-r--r--src/main/java/org/junit/runner/notification/RunListener.java171
-rw-r--r--src/main/java/org/junit/runner/notification/RunNotifier.java346
-rw-r--r--src/main/java/org/junit/runner/notification/StoppedByUserException.java7
-rw-r--r--src/main/java/org/junit/runner/notification/SynchronizedRunListener.java103
-rw-r--r--src/main/java/org/junit/runners/AllTests.java17
-rw-r--r--src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java756
-rw-r--r--src/main/java/org/junit/runners/JUnit4.java14
-rw-r--r--src/main/java/org/junit/runners/MethodSorters.java41
-rw-r--r--src/main/java/org/junit/runners/Parameterized.java452
-rwxr-xr-x[-rw-r--r--]src/main/java/org/junit/runners/ParentRunner.java734
-rw-r--r--src/main/java/org/junit/runners/Suite.java202
-rw-r--r--src/main/java/org/junit/runners/model/Annotatable.java20
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkField.java101
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkMember.java51
-rw-r--r--src/main/java/org/junit/runners/model/FrameworkMethod.java326
-rw-r--r--src/main/java/org/junit/runners/model/InitializationError.java62
-rw-r--r--src/main/java/org/junit/runners/model/MultipleFailureException.java107
-rw-r--r--src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java87
-rw-r--r--src/main/java/org/junit/runners/model/RunnerBuilder.java136
-rw-r--r--src/main/java/org/junit/runners/model/RunnerScheduler.java26
-rw-r--r--src/main/java/org/junit/runners/model/Statement.java13
-rwxr-xr-x[-rw-r--r--]src/main/java/org/junit/runners/model/TestClass.java432
-rw-r--r--src/main/java/org/junit/runners/model/TestTimedOutException.java44
-rw-r--r--src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java143
-rw-r--r--src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java18
-rw-r--r--src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java21
-rw-r--r--src/main/java/org/junit/runners/parameterized/TestWithParameters.java82
-rw-r--r--src/main/java/org/junit/validator/AnnotationValidator.java60
-rw-r--r--src/main/java/org/junit/validator/AnnotationValidatorFactory.java42
-rw-r--r--src/main/java/org/junit/validator/AnnotationsValidator.java120
-rw-r--r--src/main/java/org/junit/validator/PublicClassValidator.java33
-rw-r--r--src/main/java/org/junit/validator/TestClassValidator.java21
-rw-r--r--src/main/java/org/junit/validator/ValidateWith.java19
165 files changed, 11022 insertions, 6348 deletions
diff --git a/src/main/java/org/junit/After.java b/src/main/java/org/junit/After.java
index 39aa6e5..eae7e3a 100644
--- a/src/main/java/org/junit/After.java
+++ b/src/main/java/org/junit/After.java
@@ -6,15 +6,15 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>If you allocate external resources in a {@link org.junit.Before} method you need to release them
+ * If you allocate external resources in a {@link org.junit.Before} method you need to release them
* after the test runs. Annotating a <code>public void</code> method
* with <code>&#064;After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>&#064;After</code>
- * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an
+ * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an
* exception. The <code>&#064;After</code> methods declared in superclasses will be run after those of the current
- * class.</p>
- *
+ * class, unless they are overridden in the current class.
+ * <p>
* Here is a simple example:
-* <pre>
+ * <pre>
* public class Example {
* File output;
* &#064;Before public void createOutputFile() {
@@ -28,9 +28,10 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
- *
+ *
* @see org.junit.Before
* @see org.junit.Test
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/main/java/org/junit/AfterClass.java b/src/main/java/org/junit/AfterClass.java
index 2d6bc80..dba4109 100644
--- a/src/main/java/org/junit/AfterClass.java
+++ b/src/main/java/org/junit/AfterClass.java
@@ -6,15 +6,15 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them
+ * If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them
* after all the tests in the class have run. Annotating a <code>public static void</code> method
* with <code>&#064;AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>&#064;AfterClass</code>
- * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an
+ * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an
* exception. The <code>&#064;AfterClass</code> methods declared in superclasses will be run after those of the current
- * class.</p>
- *
+ * class, unless they are shadowed in the current class.
+ * <p>
* Here is a simple example:
-* <pre>
+ * <pre>
* public class Example {
* private static DatabaseConnection database;
* &#064;BeforeClass public static void login() {
@@ -31,9 +31,10 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
- *
+ *
* @see org.junit.BeforeClass
* @see org.junit.Test
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
diff --git a/src/main/java/org/junit/Assume.java b/src/main/java/org/junit/Assume.java
index 7b6c21a..b7687f7 100644
--- a/src/main/java/org/junit/Assume.java
+++ b/src/main/java/org/junit/Assume.java
@@ -1,27 +1,25 @@
package org.junit;
import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
+
import org.hamcrest.Matcher;
-import org.junit.internal.AssumptionViolatedException;
-import org.junit.internal.matchers.Each;
/**
* A set of methods useful for stating assumptions about the conditions in which a test is meaningful.
- * A failed assumption does not mean the code is broken, but that the test provides no useful information.
- * The default JUnit runner treats tests with failing assumptions as ignored. Custom runners may behave differently.
- *
- * For example:
- * <pre>
- * // only provides information if database is reachable.
- * \@Test public void calculateTotalSalary() {
- * DBConnection dbc = Database.connect();
- * assumeNotNull(dbc);
- * // ...
- * }
- * </pre>
+ * A failed assumption does not mean the code is broken, but that the test provides no useful information. Assume
+ * basically means "don't run this test if these conditions don't apply". The default JUnit runner skips tests with
+ * failing assumptions. Custom runners may behave differently.
+ * <p>
+ * A good example of using assumptions is in <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a> where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case.
+ * </p>
+ * Failed assumptions are usually not logged, because there may be many tests that don't apply to certain
+ * configurations.
+ *
+ * <p>
* These methods can be used directly: <code>Assume.assumeTrue(...)</code>, however, they
* read better if they are referenced through static import:<br/>
* <pre>
@@ -29,66 +27,132 @@ import org.junit.internal.matchers.Each;
* ...
* assumeTrue(...);
* </pre>
+ * </p>
+ *
+ * @see <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a>
+ *
+ * @since 4.4
*/
public class Assume {
- /**
- * If called with an expression evaluating to {@code false}, the test will halt and be ignored.
- * @param b
- */
- public static void assumeTrue(boolean b) {
- assumeThat(b, is(true));
- }
+ /**
+ * If called with an expression evaluating to {@code false}, the test will halt and be ignored.
+ */
+ public static void assumeTrue(boolean b) {
+ assumeThat(b, is(true));
+ }
+
+ /**
+ * The inverse of {@link #assumeTrue(boolean)}.
+ */
+ public static void assumeFalse(boolean b) {
+ assumeTrue(!b);
+ }
+
+ /**
+ * If called with an expression evaluating to {@code false}, the test will halt and be ignored.
+ *
+ * @param b If <code>false</code>, the method will attempt to stop the test and ignore it by
+ * throwing {@link AssumptionViolatedException}.
+ * @param message A message to pass to {@link AssumptionViolatedException}.
+ */
+ public static void assumeTrue(String message, boolean b) {
+ if (!b) throw new AssumptionViolatedException(message);
+ }
- /**
- * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored.
- * @param objects
- */
- public static void assumeNotNull(Object... objects) {
- assumeThat(asList(objects), Each.each(notNullValue()));
- }
+ /**
+ * The inverse of {@link #assumeTrue(String, boolean)}.
+ */
+ public static void assumeFalse(String message, boolean b) {
+ assumeTrue(message, !b);
+ }
+
+ /**
+ * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored.
+ */
+ public static void assumeNotNull(Object... objects) {
+ assumeThat(asList(objects), everyItem(notNullValue()));
+ }
+
+ /**
+ * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>.
+ * If not, the test halts and is ignored.
+ * Example:
+ * <pre>:
+ * assumeThat(1, is(1)); // passes
+ * foo(); // will execute
+ * assumeThat(0, is(1)); // assumption failure! test halts
+ * int x = 1 / 0; // will never execute
+ * </pre>
+ *
+ * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))}
+ * @param actual the computed value being compared
+ * @param matcher an expression, built of {@link Matcher}s, specifying allowed values
+ * @see org.hamcrest.CoreMatchers
+ * @see org.junit.matchers.JUnitMatchers
+ */
+ public static <T> void assumeThat(T actual, Matcher<T> matcher) {
+ if (!matcher.matches(actual)) {
+ throw new AssumptionViolatedException(actual, matcher);
+ }
+ }
+
+ /**
+ * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>.
+ * If not, the test halts and is ignored.
+ * Example:
+ * <pre>:
+ * assumeThat("alwaysPasses", 1, is(1)); // passes
+ * foo(); // will execute
+ * assumeThat("alwaysFails", 0, is(1)); // assumption failure! test halts
+ * int x = 1 / 0; // will never execute
+ * </pre>
+ *
+ * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))}
+ * @param actual the computed value being compared
+ * @param matcher an expression, built of {@link Matcher}s, specifying allowed values
+ * @see org.hamcrest.CoreMatchers
+ * @see org.junit.matchers.JUnitMatchers
+ */
+ public static <T> void assumeThat(String message, T actual, Matcher<T> matcher) {
+ if (!matcher.matches(actual)) {
+ throw new AssumptionViolatedException(message, actual, matcher);
+ }
+ }
- /**
- * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>.
- * If not, the test halts and is ignored.
- * Example:
- * <pre>:
- * assumeThat(1, is(1)); // passes
- * foo(); // will execute
- * assumeThat(0, is(1)); // assumption failure! test halts
- * int x = 1 / 0; // will never execute
- * </pre>
- *
- * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))}
- * @param actual the computed value being compared
- * @param matcher an expression, built of {@link Matcher}s, specifying allowed values
- *
- * @see org.hamcrest.CoreMatchers
- * @see org.junit.matchers.JUnitMatchers
- */
- public static <T> void assumeThat(T actual, Matcher<T> matcher) {
- if (!matcher.matches(actual))
- throw new AssumptionViolatedException(actual, matcher);
- }
+ /**
+ * Use to assume that an operation completes normally. If {@code e} is non-null, the test will halt and be ignored.
+ *
+ * For example:
+ * <pre>
+ * \@Test public void parseDataFile() {
+ * DataFile file;
+ * try {
+ * file = DataFile.open("sampledata.txt");
+ * } catch (IOException e) {
+ * // stop test and ignore if data can't be opened
+ * assumeNoException(e);
+ * }
+ * // ...
+ * }
+ * </pre>
+ *
+ * @param e if non-null, the offending exception
+ */
+ public static void assumeNoException(Throwable e) {
+ assumeThat(e, nullValue());
+ }
/**
- * Use to assume that an operation completes normally. If {@code t} is non-null, the test will halt and be ignored.
- *
- * For example:
- * <pre>
- * \@Test public void parseDataFile() {
- * DataFile file;
- * try {
- * file = DataFile.open("sampledata.txt");
- * } catch (IOException e) {
- * // stop test and ignore if data can't be opened
- * assumeNoException(e);
- * }
- * // ...
- * }
- * </pre>
- * @param t if non-null, the offending exception
- */
- public static void assumeNoException(Throwable t) {
- assumeThat(t, nullValue());
- }
+ * Attempts to halt the test and ignore it if Throwable <code>e</code> is
+ * not <code>null</code>. Similar to {@link #assumeNoException(Throwable)},
+ * but provides an additional message that can explain the details
+ * concerning the assumption.
+ *
+ * @param e if non-null, the offending exception
+ * @param message Additional message to pass to {@link AssumptionViolatedException}.
+ * @see #assumeNoException(Throwable)
+ */
+ public static void assumeNoException(String message, Throwable e) {
+ assumeThat(message, e, nullValue());
+ }
}
diff --git a/src/main/java/org/junit/AssumptionViolatedException.java b/src/main/java/org/junit/AssumptionViolatedException.java
new file mode 100644
index 0000000..e48ddf0
--- /dev/null
+++ b/src/main/java/org/junit/AssumptionViolatedException.java
@@ -0,0 +1,46 @@
+package org.junit;
+
+import org.hamcrest.Matcher;
+
+/**
+ * An exception class used to implement <i>assumptions</i> (state in which a given test
+ * is meaningful and should or should not be executed). A test for which an assumption
+ * fails should not generate a test case failure.
+ *
+ * @see org.junit.Assume
+ * @since 4.12
+ */
+@SuppressWarnings("deprecation")
+public class AssumptionViolatedException extends org.junit.internal.AssumptionViolatedException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * An assumption exception with the given <i>actual</i> value and a <i>matcher</i> describing
+ * the expectation that failed.
+ */
+ public <T> AssumptionViolatedException(T actual, Matcher<T> matcher) {
+ super(actual, matcher);
+ }
+
+ /**
+ * An assumption exception with a message with the given <i>actual</i> value and a
+ * <i>matcher</i> describing the expectation that failed.
+ */
+ public <T> AssumptionViolatedException(String message, T expected, Matcher<T> matcher) {
+ super(message, expected, matcher);
+ }
+
+ /**
+ * An assumption exception with the given message only.
+ */
+ public AssumptionViolatedException(String message) {
+ super(message);
+ }
+
+ /**
+ * An assumption exception with the given message and a cause.
+ */
+ public AssumptionViolatedException(String assumption, Throwable t) {
+ super(assumption, t);
+ }
+}
diff --git a/src/main/java/org/junit/Before.java b/src/main/java/org/junit/Before.java
index 66b34ee..def8adb 100644
--- a/src/main/java/org/junit/Before.java
+++ b/src/main/java/org/junit/Before.java
@@ -6,13 +6,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>When writing tests, it is common to find that several tests need similar
+ * When writing tests, it is common to find that several tests need similar
* objects created before they can run. Annotating a <code>public void</code> method
* with <code>&#064;Before</code> causes that method to be run before the {@link org.junit.Test} method.
- * The <code>&#064;Before</code> methods of superclasses will be run before those of the current class.
- * No other ordering is defined.
- * </p>
- *
+ * The <code>&#064;Before</code> methods of superclasses will be run before those of the current class,
+ * unless they are overridden in the current class. No other ordering is defined.
+ * <p>
* Here is a simple example:
* <pre>
* public class Example {
@@ -28,9 +27,10 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
- *
+ *
* @see org.junit.BeforeClass
* @see org.junit.After
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
diff --git a/src/main/java/org/junit/BeforeClass.java b/src/main/java/org/junit/BeforeClass.java
index 35b7854..8183fa0 100644
--- a/src/main/java/org/junit/BeforeClass.java
+++ b/src/main/java/org/junit/BeforeClass.java
@@ -6,13 +6,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>Sometimes several tests need to share computationally expensive setup
- * (like logging into a database). While this can compromise the independence of
+ * Sometimes several tests need to share computationally expensive setup
+ * (like logging into a database). While this can compromise the independence of
* tests, sometimes it is a necessary optimization. Annotating a <code>public static void</code> no-arg method
- * with <code>@BeforeClass</code> causes it to be run once before any of
+ * with <code>@BeforeClass</code> causes it to be run once before any of
* the test methods in the class. The <code>@BeforeClass</code> methods of superclasses
- * will be run before those the current class.</p>
- *
+ * will be run before those of the current class, unless they are shadowed in the current class.
+ * <p>
* For example:
* <pre>
* public class Example {
@@ -27,7 +27,9 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
+ *
* @see org.junit.AfterClass
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
diff --git a/src/main/java/org/junit/ClassRule.java b/src/main/java/org/junit/ClassRule.java
index 97a111f..02c40a7 100644
--- a/src/main/java/org/junit/ClassRule.java
+++ b/src/main/java/org/junit/ClassRule.java
@@ -6,55 +6,82 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Annotates static fields that contain rules. Such a field must be public,
- * static, and a subtype of {@link org.junit.rules.TestRule}.
- * The {@link org.junit.runners.model.Statement} passed
- * to the {@link org.junit.rules.TestRule} will run any {@link BeforeClass} methods,
+ * Annotates static fields that reference rules or methods that return them. A field must be public,
+ * static, and a subtype of {@link org.junit.rules.TestRule}. A method must be public static, and return
+ * a subtype of {@link org.junit.rules.TestRule}.
+ * <p>
+ * The {@link org.junit.runners.model.Statement} passed
+ * to the {@link org.junit.rules.TestRule} will run any {@link BeforeClass} methods,
* then the entire body of the test class (all contained methods, if it is
- * a standard JUnit test class, or all contained classes, if it is a
+ * a standard JUnit test class, or all contained classes, if it is a
* {@link org.junit.runners.Suite}), and finally any {@link AfterClass} methods.
- *
+ * <p>
* The statement passed to the {@link org.junit.rules.TestRule} will never throw an exception,
* and throwing an exception from the {@link org.junit.rules.TestRule} will result in undefined
- * behavior. This means that some {@link org.junit.rules.TestRule}s, such as
- * {@link org.junit.rules.ErrorCollector},
- * {@link org.junit.rules.ExpectedException},
+ * behavior. This means that some {@link org.junit.rules.TestRule}s, such as
+ * {@link org.junit.rules.ErrorCollector},
+ * {@link org.junit.rules.ExpectedException},
* and {@link org.junit.rules.Timeout},
* have undefined behavior when used as {@link ClassRule}s.
- *
+ * <p>
* If there are multiple
* annotated {@link ClassRule}s on a class, they will be applied in an order
* that depends on your JVM's implementation of the reflection API, which is
- * undefined, in general.
- *
+ * undefined, in general. However, Rules defined by fields will always be applied
+ * before Rules defined by methods.
+ * <p>
* For example, here is a test suite that connects to a server once before
* all the test classes run, and disconnects after they are finished:
- *
* <pre>
- *
* &#064;RunWith(Suite.class)
* &#064;SuiteClasses({A.class, B.class, C.class})
* public class UsesExternalResource {
- * public static Server myServer= new Server();
- *
- * &#064;ClassRule
- * public static ExternalResource resource= new ExternalResource() {
- * &#064;Override
- * protected void before() throws Throwable {
- * myServer.connect();
- * };
- *
- * &#064;Override
- * protected void after() {
- * myServer.disconnect();
- * };
- * };
+ * public static Server myServer= new Server();
+ *
+ * &#064;ClassRule
+ * public static ExternalResource resource= new ExternalResource() {
+ * &#064;Override
+ * protected void before() throws Throwable {
+ * myServer.connect();
+ * }
+ *
+ * &#064;Override
+ * protected void after() {
+ * myServer.disconnect();
+ * }
+ * };
+ * }
+ * </pre>
+ * <p>
+ * and the same using a method
+ * <pre>
+ * &#064;RunWith(Suite.class)
+ * &#064;SuiteClasses({A.class, B.class, C.class})
+ * public class UsesExternalResource {
+ * public static Server myServer= new Server();
+ *
+ * &#064;ClassRule
+ * public static ExternalResource getResource() {
+ * return new ExternalResource() {
+ * &#064;Override
+ * protected void before() throws Throwable {
+ * myServer.connect();
+ * }
+ *
+ * &#064;Override
+ * protected void after() {
+ * myServer.disconnect();
+ * }
+ * };
+ * }
* }
* </pre>
- *
- * For more information and more examples, see {@link org.junit.rules.TestRule}.
+ * <p>
+ * For more information and more examples, see {@link org.junit.rules.TestRule}.
+ *
+ * @since 4.9
*/
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD})
+@Target({ElementType.FIELD, ElementType.METHOD})
public @interface ClassRule {
}
diff --git a/src/main/java/org/junit/ComparisonFailure.java b/src/main/java/org/junit/ComparisonFailure.java
index d37db4f..9563e61 100644
--- a/src/main/java/org/junit/ComparisonFailure.java
+++ b/src/main/java/org/junit/ComparisonFailure.java
@@ -1,138 +1,171 @@
package org.junit;
/**
- * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw
- * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex
- * strings.
- *
+ * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails.
+ * Create and throw a <code>ComparisonFailure</code> manually if you want to show users the
+ * difference between two complex strings.
+ * <p/>
* Inspired by a patch from Alex Chaffee (alex@purpletech.com)
+ *
+ * @since 4.0
*/
-public class ComparisonFailure extends AssertionError {
- /**
- * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened.
- * @see ComparisonCompactor
- */
- private static final int MAX_CONTEXT_LENGTH= 20;
- private static final long serialVersionUID= 1L;
-
- private String fExpected;
- private String fActual;
-
- /**
- * Constructs a comparison failure.
- * @param message the identifying message or null
- * @param expected the expected string value
- * @param actual the actual string value
- */
- public ComparisonFailure (String message, String expected, String actual) {
- super (message);
- fExpected= expected;
- fActual= actual;
- }
-
- /**
- * Returns "..." in place of common prefix and "..." in
- * place of common suffix between expected and actual.
- *
- * @see Throwable#getMessage()
- */
- @Override
- public String getMessage() {
- return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
- }
-
- /**
- * Returns the actual string value
- * @return the actual string value
- */
- public String getActual() {
- return fActual;
- }
- /**
- * Returns the expected string value
- * @return the expected string value
- */
- public String getExpected() {
- return fExpected;
- }
-
- private static class ComparisonCompactor {
- private static final String ELLIPSIS= "...";
- private static final String DELTA_END= "]";
- private static final String DELTA_START= "[";
-
- /**
- * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code>
- * is exceeded, the Strings are shortened
- */
- private int fContextLength;
- private String fExpected;
- private String fActual;
- private int fPrefix;
- private int fSuffix;
-
- /**
- * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength
- * is exceeded, the Strings are shortened
- * @param expected the expected string value
- * @param actual the actual string value
- */
- public ComparisonCompactor(int contextLength, String expected, String actual) {
- fContextLength= contextLength;
- fExpected= expected;
- fActual= actual;
- }
-
- private String compact(String message) {
- if (fExpected == null || fActual == null || areStringsEqual())
- return Assert.format(message, fExpected, fActual);
-
- findCommonPrefix();
- findCommonSuffix();
- String expected= compactString(fExpected);
- String actual= compactString(fActual);
- return Assert.format(message, expected, actual);
- }
-
- private String compactString(String source) {
- String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
- if (fPrefix > 0)
- result= computeCommonPrefix() + result;
- if (fSuffix > 0)
- result= result + computeCommonSuffix();
- return result;
- }
-
- private void findCommonPrefix() {
- fPrefix= 0;
- int end= Math.min(fExpected.length(), fActual.length());
- for (; fPrefix < end; fPrefix++) {
- if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix))
- break;
- }
- }
-
- private void findCommonSuffix() {
- int expectedSuffix= fExpected.length() - 1;
- int actualSuffix= fActual.length() - 1;
- for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) {
- if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix))
- break;
- }
- fSuffix= fExpected.length() - expectedSuffix;
- }
-
- private String computeCommonPrefix() {
- return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
- }
-
- private String computeCommonSuffix() {
- int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
- return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : "");
- }
-
- private boolean areStringsEqual() {
- return fExpected.equals(fActual);
- }
- }
-} \ No newline at end of file
+public class ComparisonFailure extends AssertionError {
+ /**
+ * The maximum length for expected and actual strings. If it is exceeded, the strings should be shortened.
+ *
+ * @see ComparisonCompactor
+ */
+ private static final int MAX_CONTEXT_LENGTH = 20;
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private String fExpected;
+ private String fActual;
+
+ /**
+ * Constructs a comparison failure.
+ *
+ * @param message the identifying message or null
+ * @param expected the expected string value
+ * @param actual the actual string value
+ */
+ public ComparisonFailure(String message, String expected, String actual) {
+ super(message);
+ this.fExpected = expected;
+ this.fActual = actual;
+ }
+
+ /**
+ * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual.
+ *
+ * @see Throwable#getMessage()
+ */
+ @Override
+ public String getMessage() {
+ return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage());
+ }
+
+ /**
+ * Returns the actual string value
+ *
+ * @return the actual string value
+ */
+ public String getActual() {
+ return fActual;
+ }
+
+ /**
+ * Returns the expected string value
+ *
+ * @return the expected string value
+ */
+ public String getExpected() {
+ return fExpected;
+ }
+
+ private static class ComparisonCompactor {
+ private static final String ELLIPSIS = "...";
+ private static final String DIFF_END = "]";
+ private static final String DIFF_START = "[";
+
+ /**
+ * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When
+ * <code>contextLength</code> is exceeded, the Strings are shortened.
+ */
+ private final int contextLength;
+ private final String expected;
+ private final String actual;
+
+ /**
+ * @param contextLength the maximum length of context surrounding the difference between the compared strings.
+ * When context length is exceeded, the prefixes and suffixes are compacted.
+ * @param expected the expected string value
+ * @param actual the actual string value
+ */
+ public ComparisonCompactor(int contextLength, String expected, String actual) {
+ this.contextLength = contextLength;
+ this.expected = expected;
+ this.actual = actual;
+ }
+
+ public String compact(String message) {
+ if (expected == null || actual == null || expected.equals(actual)) {
+ return Assert.format(message, expected, actual);
+ } else {
+ DiffExtractor extractor = new DiffExtractor();
+ String compactedPrefix = extractor.compactPrefix();
+ String compactedSuffix = extractor.compactSuffix();
+ return Assert.format(message,
+ compactedPrefix + extractor.expectedDiff() + compactedSuffix,
+ compactedPrefix + extractor.actualDiff() + compactedSuffix);
+ }
+ }
+
+ private String sharedPrefix() {
+ int end = Math.min(expected.length(), actual.length());
+ for (int i = 0; i < end; i++) {
+ if (expected.charAt(i) != actual.charAt(i)) {
+ return expected.substring(0, i);
+ }
+ }
+ return expected.substring(0, end);
+ }
+
+ private String sharedSuffix(String prefix) {
+ int suffixLength = 0;
+ int maxSuffixLength = Math.min(expected.length() - prefix.length(),
+ actual.length() - prefix.length()) - 1;
+ for (; suffixLength <= maxSuffixLength; suffixLength++) {
+ if (expected.charAt(expected.length() - 1 - suffixLength)
+ != actual.charAt(actual.length() - 1 - suffixLength)) {
+ break;
+ }
+ }
+ return expected.substring(expected.length() - suffixLength);
+ }
+
+ private class DiffExtractor {
+ private final String sharedPrefix;
+ private final String sharedSuffix;
+
+ /**
+ * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}.
+ */
+ private DiffExtractor() {
+ sharedPrefix = sharedPrefix();
+ sharedSuffix = sharedSuffix(sharedPrefix);
+ }
+
+ public String expectedDiff() {
+ return extractDiff(expected);
+ }
+
+ public String actualDiff() {
+ return extractDiff(actual);
+ }
+
+ public String compactPrefix() {
+ if (sharedPrefix.length() <= contextLength) {
+ return sharedPrefix;
+ }
+ return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength);
+ }
+
+ public String compactSuffix() {
+ if (sharedSuffix.length() <= contextLength) {
+ return sharedSuffix;
+ }
+ return sharedSuffix.substring(0, contextLength) + ELLIPSIS;
+ }
+
+ private String extractDiff(String source) {
+ return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length())
+ + DIFF_END;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/junit/FixMethodOrder.java b/src/main/java/org/junit/FixMethodOrder.java
new file mode 100644
index 0000000..aaa0313
--- /dev/null
+++ b/src/main/java/org/junit/FixMethodOrder.java
@@ -0,0 +1,41 @@
+package org.junit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.runners.MethodSorters;
+
+/**
+ * This class allows the user to choose the order of execution of the methods within a test class.
+ *
+ * <p>The default order of execution of JUnit tests within a class is deterministic but not predictable.
+ * The order of execution is not guaranteed for Java 7 (and some previous versions), and can even change
+ * from run to run, so the order of execution was changed to be deterministic (in JUnit 4.11)
+ *
+ * <p>It is recommended that test methods be written so that they are independent of the order that they are executed.
+ * However, there may be a number of dependent tests either through error or by design.
+ * This class allows the user to specify the order of execution of test methods.
+ *
+ * <p>For possibilities, see {@link MethodSorters}
+ *
+ * Here is an example:
+ *
+ * <pre>
+ * &#064;FixMethodOrder(MethodSorters.NAME_ASCENDING)
+ * public class MyTest {
+ * }
+ * </pre>
+ *
+ * @see org.junit.runners.MethodSorters
+ * @since 4.11
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface FixMethodOrder {
+ /**
+ * Optionally specify <code>value</code> to have the methods executed in a particular order
+ */
+ MethodSorters value() default MethodSorters.DEFAULT;
+}
diff --git a/src/main/java/org/junit/Ignore.java b/src/main/java/org/junit/Ignore.java
index de530a9..db23581 100644
--- a/src/main/java/org/junit/Ignore.java
+++ b/src/main/java/org/junit/Ignore.java
@@ -6,34 +6,35 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with
+ * Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with
* {@link org.junit.Test} that are also annotated with <code>&#064;Ignore</code> will not be executed as tests.
- * Also, you can annotate a class containing test methods with <code>&#064;Ignore</code> and none of the containing
- * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the
- * number of tests that ran and the number of tests that failed.</p>
- *
- * For example:
+ * Also, you can annotate a class containing test methods with <code>&#064;Ignore</code> and none of the containing
+ * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the
+ * number of tests that ran and the number of tests that failed.
+ *
+ * <p>For example:
* <pre>
* &#064;Ignore &#064;Test public void something() { ...
* </pre>
- * &#064;Ignore takes an optional default parameter if you want to record why a test is being ignored:<br/>
+ * &#064;Ignore takes an optional default parameter if you want to record why a test is being ignored:
* <pre>
* &#064;Ignore("not ready yet") &#064;Test public void something() { ...
* </pre>
- * &#064;Ignore can also be applied to the test class:<br/>
+ * &#064;Ignore can also be applied to the test class:
* <pre>
- * &#064;Ignore public class IgnoreMe {
- * &#064;Test public void test1() { ... }
- * &#064;Test public void test2() { ... }
- * }
+ * &#064;Ignore public class IgnoreMe {
+ * &#064;Test public void test1() { ... }
+ * &#064;Test public void test2() { ... }
+ * }
* </pre>
*
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Ignore {
- /**
- * The optional reason why the test is ignored.
- */
- String value() default "";
+ /**
+ * The optional reason why the test is ignored.
+ */
+ String value() default "";
}
diff --git a/src/main/java/org/junit/Rule.java b/src/main/java/org/junit/Rule.java
index 9e67c07..711235c 100644
--- a/src/main/java/org/junit/Rule.java
+++ b/src/main/java/org/junit/Rule.java
@@ -6,42 +6,65 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Annotates fields that contain rules. Such a field must be public, not
- * static, and a subtype of {@link org.junit.rules.TestRule}.
- * The {@link org.junit.runners.model.Statement} passed
- * to the {@link org.junit.rules.TestRule} will run any {@link Before} methods,
+ * Annotates fields that reference rules or methods that return a rule. A field must be public, not
+ * static, and a subtype of {@link org.junit.rules.TestRule} (preferred) or
+ * {@link org.junit.rules.MethodRule}. A method must be public, not static,
+ * and must return a subtype of {@link org.junit.rules.TestRule} (preferred) or
+ * {@link org.junit.rules.MethodRule}.
+ * <p>
+ * The {@link org.junit.runners.model.Statement} passed
+ * to the {@link org.junit.rules.TestRule} will run any {@link Before} methods,
* then the {@link Test} method, and finally any {@link After} methods,
* throwing an exception if any of these fail. If there are multiple
- * annotated {@link Rule}s on a class, they will be applied in an order
+ * annotated {@link Rule}s on a class, they will be applied in order of fields first, then methods.
+ * However, if there are multiple fields (or methods) they will be applied in an order
* that depends on your JVM's implementation of the reflection API, which is
- * undefined, in general.
- *
+ * undefined, in general. Rules defined by fields will always be applied
+ * before Rules defined by methods. You can use a {@link org.junit.rules.RuleChain} if you want
+ * to have control over the order in which the Rules are applied.
+ * <p>
* For example, here is a test class that creates a temporary folder before
* each test method, and deletes it after each:
+ * <pre>
+ * public static class HasTempFolder {
+ * &#064;Rule
+ * public TemporaryFolder folder= new TemporaryFolder();
*
+ * &#064;Test
+ * public void testUsingTempFolder() throws IOException {
+ * File createdFile= folder.newFile(&quot;myfile.txt&quot;);
+ * File createdFolder= folder.newFolder(&quot;subfolder&quot;);
+ * // ...
+ * }
+ * }
+ * </pre>
+ * <p>
+ * And the same using a method.
* <pre>
* public static class HasTempFolder {
- * &#064;Rule
- * public TemporaryFolder folder= new TemporaryFolder();
- *
- * &#064;Test
- * public void testUsingTempFolder() throws IOException {
- * File createdFile= folder.newFile(&quot;myfile.txt&quot;);
- * File createdFolder= folder.newFolder(&quot;subfolder&quot;);
- * // ...
- * }
+ * private TemporaryFolder folder= new TemporaryFolder();
+ *
+ * &#064;Rule
+ * public TemporaryFolder getFolder() {
+ * return folder;
+ * }
+ *
+ * &#064;Test
+ * public void testUsingTempFolder() throws IOException {
+ * File createdFile= folder.newFile(&quot;myfile.txt&quot;);
+ * File createdFolder= folder.newFolder(&quot;subfolder&quot;);
+ * // ...
+ * }
* }
* </pre>
- *
- * For more information and more examples, see
- * {@link org.junit.rules.TestRule}.
+ * <p>
+ * For more information and more examples, see
+ * {@link org.junit.rules.TestRule}.
*
- * Note: for backwards compatibility, this annotation may also mark
- * fields of type {@link org.junit.rules.MethodRule}, which will be honored. However,
- * this is a deprecated interface and feature.
+ * @since 4.7
*/
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD})
+@Target({ElementType.FIELD, ElementType.METHOD})
public @interface Rule {
} \ No newline at end of file
diff --git a/src/main/java/org/junit/Test.java b/src/main/java/org/junit/Test.java
index 23dc78a..71ac428 100644
--- a/src/main/java/org/junit/Test.java
+++ b/src/main/java/org/junit/Test.java
@@ -6,25 +6,24 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * <p>The <code>Test</code> annotation tells JUnit that the <code>public void</code> method
+ * The <code>Test</code> annotation tells JUnit that the <code>public void</code> method
* to which it is attached can be run as a test case. To run the method,
* JUnit first constructs a fresh instance of the class then invokes the
* annotated method. Any exceptions thrown by the test will be reported
* by JUnit as a failure. If no exceptions are thrown, the test is assumed
- * to have succeeded.</p>
- *
- * <p>A simple test looks like this:
+ * to have succeeded.
+ * <p>
+ * A simple test looks like this:
* <pre>
* public class Example {
- * <b>&#064;Test</b>
+ * <b>&#064;Test</b>
* public void method() {
* org.junit.Assert.assertTrue( new ArrayList().isEmpty() );
* }
* }
* </pre>
- * </p>
- *
- * <p>The <code>Test</code> annotation supports two optional parameters.
+ * <p>
+ * The <code>Test</code> annotation supports two optional parameters.
* The first, <code>expected</code>, declares that a test method should throw
* an exception. If it doesn't throw an exception or if it throws a different exception
* than the one declared, the test fails. For example, the following test succeeds:
@@ -32,37 +31,68 @@ import java.lang.annotation.Target;
* &#064;Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() {
* new ArrayList&lt;Object&gt;().get(1);
* }
- * </pre></p>
- *
- * <p>The second optional parameter, <code>timeout</code>, causes a test to fail if it takes
+ * </pre>
+ * If the exception's message or one of its properties should be verified, the
+ * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further
+ * information about exception testing can be found at the
+ * <a href="https://github.com/junit-team/junit/wiki/Exception-testing">JUnit Wiki</a>.
+ * <p>
+ * The second optional parameter, <code>timeout</code>, causes a test to fail if it takes
* longer than a specified amount of clock time (measured in milliseconds). The following test fails:
* <pre>
* &#064;Test(<b>timeout=100</b>) public void infinity() {
* while(true);
* }
- * </pre></p>
+ * </pre>
+ * <b>Warning</b>: while <code>timeout</code> is useful to catch and terminate
+ * infinite loops, it should <em>not</em> be considered deterministic. The
+ * following test may or may not fail depending on how the operating system
+ * schedules threads:
+ * <pre>
+ * &#064;Test(<b>timeout=100</b>) public void sleep100() {
+ * Thread.sleep(100);
+ * }
+ * </pre>
+ * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the
+ * thread which runs the fixture's @Before and @After methods. This may yield different behavior for
+ * code that is not thread safe when compared to the same test method without a timeout parameter.
+ * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the
+ * same thread as the fixture's @Before and @After methods.
+ *
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
-
- /**
- * Default empty exception
- */
- static class None extends Throwable {
- private static final long serialVersionUID= 1L;
- private None() {
- }
- }
-
- /**
- * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed iff
- * an exception of the specified class is thrown by the method.
- */
- Class<? extends Throwable> expected() default None.class;
-
- /**
- * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it
- * takes longer than that number of milliseconds.*/
- long timeout() default 0L;
+
+ /**
+ * Default empty exception
+ */
+ static class None extends Throwable {
+ private static final long serialVersionUID = 1L;
+
+ private None() {
+ }
+ }
+
+ /**
+ * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed if
+ * and only if an exception of the specified class is thrown by the method. If the Throwable's
+ * message or one of its properties should be verified, the
+ * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used instead.
+ */
+ Class<? extends Throwable> expected() default None.class;
+
+ /**
+ * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it
+ * takes longer than that number of milliseconds.
+ * <p>
+ * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the
+ * thread which runs the fixture's @Before and @After methods. This may yield different behavior for
+ * code that is not thread safe when compared to the same test method without a timeout parameter.
+ * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the
+ * same thread as the fixture's @Before and @After methods.
+ * </p>
+ */
+ long timeout() default 0L;
}
diff --git a/src/main/java/org/junit/experimental/ParallelComputer.java b/src/main/java/org/junit/experimental/ParallelComputer.java
index fccb97c..97da0f7 100644
--- a/src/main/java/org/junit/experimental/ParallelComputer.java
+++ b/src/main/java/org/junit/experimental/ParallelComputer.java
@@ -1,11 +1,8 @@
package org.junit.experimental;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import org.junit.runner.Computer;
import org.junit.runner.Runner;
@@ -15,64 +12,56 @@ import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.RunnerScheduler;
public class ParallelComputer extends Computer {
- private final boolean fClasses;
+ private final boolean classes;
- private final boolean fMethods;
+ private final boolean methods;
- public ParallelComputer(boolean classes, boolean methods) {
- fClasses= classes;
- fMethods= methods;
- }
+ public ParallelComputer(boolean classes, boolean methods) {
+ this.classes = classes;
+ this.methods = methods;
+ }
- public static Computer classes() {
- return new ParallelComputer(true, false);
- }
+ public static Computer classes() {
+ return new ParallelComputer(true, false);
+ }
- public static Computer methods() {
- return new ParallelComputer(false, true);
- }
+ public static Computer methods() {
+ return new ParallelComputer(false, true);
+ }
- private static <T> Runner parallelize(Runner runner) {
- if (runner instanceof ParentRunner<?>) {
- ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() {
- private final List<Future<Object>> fResults= new ArrayList<Future<Object>>();
+ private static Runner parallelize(Runner runner) {
+ if (runner instanceof ParentRunner) {
+ ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() {
+ private final ExecutorService fService = Executors.newCachedThreadPool();
- private final ExecutorService fService= Executors
- .newCachedThreadPool();
+ public void schedule(Runnable childStatement) {
+ fService.submit(childStatement);
+ }
- public void schedule(final Runnable childStatement) {
- fResults.add(fService.submit(new Callable<Object>() {
- public Object call() throws Exception {
- childStatement.run();
- return null;
- }
- }));
- }
+ public void finished() {
+ try {
+ fService.shutdown();
+ fService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace(System.err);
+ }
+ }
+ });
+ }
+ return runner;
+ }
- public void finished() {
- for (Future<Object> each : fResults)
- try {
- each.get();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- return runner;
- }
+ @Override
+ public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
+ throws InitializationError {
+ Runner suite = super.getSuite(builder, classes);
+ return this.classes ? parallelize(suite) : suite;
+ }
- @Override
- public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
- throws InitializationError {
- Runner suite= super.getSuite(builder, classes);
- return fClasses ? parallelize(suite) : suite;
- }
-
- @Override
- protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
- throws Throwable {
- Runner runner= super.getRunner(builder, testClass);
- return fMethods ? parallelize(runner) : runner;
- }
+ @Override
+ protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
+ throws Throwable {
+ Runner runner = super.getRunner(builder, testClass);
+ return methods ? parallelize(runner) : runner;
+ }
}
diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java
index d57b4d3..290c180 100644
--- a/src/main/java/org/junit/experimental/categories/Categories.java
+++ b/src/main/java/org/junit/experimental/categories/Categories.java
@@ -1,13 +1,10 @@
-/**
- *
- */
package org.junit.experimental.categories;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
@@ -20,173 +17,369 @@ import org.junit.runners.model.RunnerBuilder;
* From a given set of test classes, runs only the classes and methods that are
* annotated with either the category given with the @IncludeCategory
* annotation, or a subtype of that category.
- *
+ * <p>
* Note that, for now, annotating suites with {@code @Category} has no effect.
* Categories must be annotated on the direct method or class.
- *
+ * <p>
* Example:
- *
* <pre>
* public interface FastTests {
* }
- *
+ *
* public interface SlowTests {
* }
- *
+ *
+ * public interface SmokeTests
+ * }
+ *
* public static class A {
- * &#064;Test
- * public void a() {
- * fail();
- * }
- *
- * &#064;Category(SlowTests.class)
- * &#064;Test
- * public void b() {
- * }
+ * &#064;Test
+ * public void a() {
+ * fail();
+ * }
+ *
+ * &#064;Category(SlowTests.class)
+ * &#064;Test
+ * public void b() {
+ * }
+ *
+ * &#064;Category({FastTests.class, SmokeTests.class})
+ * &#064;Test
+ * public void c() {
+ * }
* }
- *
- * &#064;Category( { SlowTests.class, FastTests.class })
+ *
+ * &#064;Category({SlowTests.class, FastTests.class})
* public static class B {
- * &#064;Test
- * public void c() {
- *
- * }
+ * &#064;Test
+ * public void d() {
+ * }
* }
- *
+ *
* &#064;RunWith(Categories.class)
* &#064;IncludeCategory(SlowTests.class)
- * &#064;SuiteClasses( { A.class, B.class })
+ * &#064;SuiteClasses({A.class, B.class})
* // Note that Categories is a kind of Suite
* public static class SlowTestSuite {
+ * // Will run A.b and B.d, but not A.a and A.c
+ * }
+ * </pre>
+ * <p>
+ * Example to run multiple categories:
+ * <pre>
+ * &#064;RunWith(Categories.class)
+ * &#064;IncludeCategory({FastTests.class, SmokeTests.class})
+ * &#064;SuiteClasses({A.class, B.class})
+ * public static class FastOrSmokeTestSuite {
+ * // Will run A.c and B.d, but not A.b because it is not any of FastTests or SmokeTests
* }
* </pre>
+ *
+ * @version 4.12
+ * @see <a href="https://github.com/junit-team/junit/wiki/Categories">Categories at JUnit wiki</a>
*/
public class Categories extends Suite {
- // the way filters are implemented makes this unnecessarily complicated,
- // buggy, and difficult to specify. A new way of handling filters could
- // someday enable a better new implementation.
- // https://github.com/KentBeck/junit/issues/issue/172
-
- @Retention(RetentionPolicy.RUNTIME)
- public @interface IncludeCategory {
- public Class<?> value();
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ExcludeCategory {
- public Class<?> value();
- }
-
- public static class CategoryFilter extends Filter {
- public static CategoryFilter include(Class<?> categoryType) {
- return new CategoryFilter(categoryType, null);
- }
-
- private final Class<?> fIncluded;
-
- private final Class<?> fExcluded;
-
- public CategoryFilter(Class<?> includedCategory,
- Class<?> excludedCategory) {
- fIncluded= includedCategory;
- fExcluded= excludedCategory;
- }
-
- @Override
- public String describe() {
- return "category " + fIncluded;
- }
-
- @Override
- public boolean shouldRun(Description description) {
- if (hasCorrectCategoryAnnotation(description))
- return true;
- for (Description each : description.getChildren())
- if (shouldRun(each))
- return true;
- return false;
- }
-
- private boolean hasCorrectCategoryAnnotation(Description description) {
- List<Class<?>> categories= categories(description);
- if (categories.isEmpty())
- return fIncluded == null;
- for (Class<?> each : categories)
- if (fExcluded != null && fExcluded.isAssignableFrom(each))
- return false;
- for (Class<?> each : categories)
- if (fIncluded == null || fIncluded.isAssignableFrom(each))
- return true;
- return false;
- }
-
- private List<Class<?>> categories(Description description) {
- ArrayList<Class<?>> categories= new ArrayList<Class<?>>();
- categories.addAll(Arrays.asList(directCategories(description)));
- categories.addAll(Arrays.asList(directCategories(parentDescription(description))));
- return categories;
- }
-
- private Description parentDescription(Description description) {
- Class<?> testClass= description.getTestClass();
- if (testClass == null)
- return null;
- return Description.createSuiteDescription(testClass);
- }
-
- private Class<?>[] directCategories(Description description) {
- if (description == null)
- return new Class<?>[0];
- Category annotation= description.getAnnotation(Category.class);
- if (annotation == null)
- return new Class<?>[0];
- return annotation.value();
- }
- }
-
- public Categories(Class<?> klass, RunnerBuilder builder)
- throws InitializationError {
- super(klass, builder);
- try {
- filter(new CategoryFilter(getIncludedCategory(klass),
- getExcludedCategory(klass)));
- } catch (NoTestsRemainException e) {
- throw new InitializationError(e);
- }
- assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
- }
-
- private Class<?> getIncludedCategory(Class<?> klass) {
- IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
- return annotation == null ? null : annotation.value();
- }
-
- private Class<?> getExcludedCategory(Class<?> klass) {
- ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
- return annotation == null ? null : annotation.value();
- }
-
- private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
- if (!canHaveCategorizedChildren(description))
- assertNoDescendantsHaveCategoryAnnotations(description);
- for (Description each : description.getChildren())
- assertNoCategorizedDescendentsOfUncategorizeableParents(each);
- }
-
- private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError {
- for (Description each : description.getChildren()) {
- if (each.getAnnotation(Category.class) != null)
- throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods.");
- assertNoDescendantsHaveCategoryAnnotations(each);
- }
- }
-
- // If children have names like [0], our current magical category code can't determine their
- // parentage.
- private static boolean canHaveCategorizedChildren(Description description) {
- for (Description each : description.getChildren())
- if (each.getTestClass() == null)
- return false;
- return true;
- }
-} \ No newline at end of file
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface IncludeCategory {
+ /**
+ * Determines the tests to run that are annotated with categories specified in
+ * the value of this annotation or their subtypes unless excluded with {@link ExcludeCategory}.
+ */
+ public Class<?>[] value() default {};
+
+ /**
+ * If <tt>true</tt>, runs tests annotated with <em>any</em> of the categories in
+ * {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with <em>all</em> of the categories.
+ */
+ public boolean matchAny() default true;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ExcludeCategory {
+ /**
+ * Determines the tests which do not run if they are annotated with categories specified in the
+ * value of this annotation or their subtypes regardless of being included in {@link IncludeCategory#value()}.
+ */
+ public Class<?>[] value() default {};
+
+ /**
+ * If <tt>true</tt>, the tests annotated with <em>any</em> of the categories in {@link ExcludeCategory#value()}
+ * do not run. Otherwise, the tests do not run if and only if annotated with <em>all</em> categories.
+ */
+ public boolean matchAny() default true;
+ }
+
+ public static class CategoryFilter extends Filter {
+ private final Set<Class<?>> included;
+ private final Set<Class<?>> excluded;
+ private final boolean includedAny;
+ private final boolean excludedAny;
+
+ public static CategoryFilter include(boolean matchAny, Class<?>... categories) {
+ if (hasNull(categories)) {
+ throw new NullPointerException("has null category");
+ }
+ return categoryFilter(matchAny, createSet(categories), true, null);
+ }
+
+ public static CategoryFilter include(Class<?> category) {
+ return include(true, category);
+ }
+
+ public static CategoryFilter include(Class<?>... categories) {
+ return include(true, categories);
+ }
+
+ public static CategoryFilter exclude(boolean matchAny, Class<?>... categories) {
+ if (hasNull(categories)) {
+ throw new NullPointerException("has null category");
+ }
+ return categoryFilter(true, null, matchAny, createSet(categories));
+ }
+
+ public static CategoryFilter exclude(Class<?> category) {
+ return exclude(true, category);
+ }
+
+ public static CategoryFilter exclude(Class<?>... categories) {
+ return exclude(true, categories);
+ }
+
+ public static CategoryFilter categoryFilter(boolean matchAnyInclusions, Set<Class<?>> inclusions,
+ boolean matchAnyExclusions, Set<Class<?>> exclusions) {
+ return new CategoryFilter(matchAnyInclusions, inclusions, matchAnyExclusions, exclusions);
+ }
+
+ protected CategoryFilter(boolean matchAnyIncludes, Set<Class<?>> includes,
+ boolean matchAnyExcludes, Set<Class<?>> excludes) {
+ includedAny = matchAnyIncludes;
+ excludedAny = matchAnyExcludes;
+ included = copyAndRefine(includes);
+ excluded = copyAndRefine(excludes);
+ }
+
+ /**
+ * @see #toString()
+ */
+ @Override
+ public String describe() {
+ return toString();
+ }
+
+ /**
+ * Returns string in the form <tt>&quot;[included categories] - [excluded categories]&quot;</tt>, where both
+ * sets have comma separated names of categories.
+ *
+ * @return string representation for the relative complement of excluded categories set
+ * in the set of included categories. Examples:
+ * <ul>
+ * <li> <tt>&quot;categories [all]&quot;</tt> for all included categories and no excluded ones;
+ * <li> <tt>&quot;categories [all] - [A, B]&quot;</tt> for all included categories and given excluded ones;
+ * <li> <tt>&quot;categories [A, B] - [C, D]&quot;</tt> for given included categories and given excluded ones.
+ * </ul>
+ * @see Class#toString() name of category
+ */
+ @Override public String toString() {
+ StringBuilder description= new StringBuilder("categories ")
+ .append(included.isEmpty() ? "[all]" : included);
+ if (!excluded.isEmpty()) {
+ description.append(" - ").append(excluded);
+ }
+ return description.toString();
+ }
+
+ @Override
+ public boolean shouldRun(Description description) {
+ if (hasCorrectCategoryAnnotation(description)) {
+ return true;
+ }
+
+ for (Description each : description.getChildren()) {
+ if (shouldRun(each)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean hasCorrectCategoryAnnotation(Description description) {
+ final Set<Class<?>> childCategories= categories(description);
+
+ // If a child has no categories, immediately return.
+ if (childCategories.isEmpty()) {
+ return included.isEmpty();
+ }
+
+ if (!excluded.isEmpty()) {
+ if (excludedAny) {
+ if (matchesAnyParentCategories(childCategories, excluded)) {
+ return false;
+ }
+ } else {
+ if (matchesAllParentCategories(childCategories, excluded)) {
+ return false;
+ }
+ }
+ }
+
+ if (included.isEmpty()) {
+ // Couldn't be excluded, and with no suite's included categories treated as should run.
+ return true;
+ } else {
+ if (includedAny) {
+ return matchesAnyParentCategories(childCategories, included);
+ } else {
+ return matchesAllParentCategories(childCategories, included);
+ }
+ }
+ }
+
+ /**
+ * @return <tt>true</tt> if at least one (any) parent category match a child, otherwise <tt>false</tt>.
+ * If empty <tt>parentCategories</tt>, returns <tt>false</tt>.
+ */
+ private boolean matchesAnyParentCategories(Set<Class<?>> childCategories, Set<Class<?>> parentCategories) {
+ for (Class<?> parentCategory : parentCategories) {
+ if (hasAssignableTo(childCategories, parentCategory)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return <tt>false</tt> if at least one parent category does not match children, otherwise <tt>true</tt>.
+ * If empty <tt>parentCategories</tt>, returns <tt>true</tt>.
+ */
+ private boolean matchesAllParentCategories(Set<Class<?>> childCategories, Set<Class<?>> parentCategories) {
+ for (Class<?> parentCategory : parentCategories) {
+ if (!hasAssignableTo(childCategories, parentCategory)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static Set<Class<?>> categories(Description description) {
+ Set<Class<?>> categories= new HashSet<Class<?>>();
+ Collections.addAll(categories, directCategories(description));
+ Collections.addAll(categories, directCategories(parentDescription(description)));
+ return categories;
+ }
+
+ private static Description parentDescription(Description description) {
+ Class<?> testClass= description.getTestClass();
+ return testClass == null ? null : Description.createSuiteDescription(testClass);
+ }
+
+ private static Class<?>[] directCategories(Description description) {
+ if (description == null) {
+ return new Class<?>[0];
+ }
+
+ Category annotation= description.getAnnotation(Category.class);
+ return annotation == null ? new Class<?>[0] : annotation.value();
+ }
+
+ private static Set<Class<?>> copyAndRefine(Set<Class<?>> classes) {
+ HashSet<Class<?>> c= new HashSet<Class<?>>();
+ if (classes != null) {
+ c.addAll(classes);
+ }
+ c.remove(null);
+ return c;
+ }
+
+ private static boolean hasNull(Class<?>... classes) {
+ if (classes == null) return false;
+ for (Class<?> clazz : classes) {
+ if (clazz == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public Categories(Class<?> klass, RunnerBuilder builder) throws InitializationError {
+ super(klass, builder);
+ try {
+ Set<Class<?>> included= getIncludedCategory(klass);
+ Set<Class<?>> excluded= getExcludedCategory(klass);
+ boolean isAnyIncluded= isAnyIncluded(klass);
+ boolean isAnyExcluded= isAnyExcluded(klass);
+
+ filter(CategoryFilter.categoryFilter(isAnyIncluded, included, isAnyExcluded, excluded));
+ } catch (NoTestsRemainException e) {
+ throw new InitializationError(e);
+ }
+ assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
+ }
+
+ private static Set<Class<?>> getIncludedCategory(Class<?> klass) {
+ IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
+ return createSet(annotation == null ? null : annotation.value());
+ }
+
+ private static boolean isAnyIncluded(Class<?> klass) {
+ IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
+ return annotation == null || annotation.matchAny();
+ }
+
+ private static Set<Class<?>> getExcludedCategory(Class<?> klass) {
+ ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
+ return createSet(annotation == null ? null : annotation.value());
+ }
+
+ private static boolean isAnyExcluded(Class<?> klass) {
+ ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
+ return annotation == null || annotation.matchAny();
+ }
+
+ private static void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
+ if (!canHaveCategorizedChildren(description)) {
+ assertNoDescendantsHaveCategoryAnnotations(description);
+ }
+ for (Description each : description.getChildren()) {
+ assertNoCategorizedDescendentsOfUncategorizeableParents(each);
+ }
+ }
+
+ private static void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError {
+ for (Description each : description.getChildren()) {
+ if (each.getAnnotation(Category.class) != null) {
+ throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods.");
+ }
+ assertNoDescendantsHaveCategoryAnnotations(each);
+ }
+ }
+
+ // If children have names like [0], our current magical category code can't determine their parentage.
+ private static boolean canHaveCategorizedChildren(Description description) {
+ for (Description each : description.getChildren()) {
+ if (each.getTestClass() == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasAssignableTo(Set<Class<?>> assigns, Class<?> to) {
+ for (final Class<?> from : assigns) {
+ if (to.isAssignableFrom(from)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Set<Class<?>> createSet(Class<?>... t) {
+ final Set<Class<?>> set= new HashSet<Class<?>>();
+ if (t != null) {
+ Collections.addAll(set, t);
+ }
+ return set;
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/Category.java b/src/main/java/org/junit/experimental/categories/Category.java
index 3a4c0b9..8eae836 100644
--- a/src/main/java/org/junit/experimental/categories/Category.java
+++ b/src/main/java/org/junit/experimental/categories/Category.java
@@ -1,43 +1,48 @@
package org.junit.experimental.categories;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import org.junit.validator.ValidateWith;
+
/**
* Marks a test class or test method as belonging to one or more categories of tests.
* The value is an array of arbitrary classes.
- *
+ *
* This annotation is only interpreted by the Categories runner (at present).
- *
+ *
* For example:
-<pre>
- public interface FastTests {}
- public interface SlowTests {}
-
- public static class A {
- &#064;Test
- public void a() {
- fail();
- }
-
- &#064;Category(SlowTests.class)
- &#064;Test
- public void b() {
- }
- }
-
- &#064;Category({SlowTests.class, FastTests.class})
- public static class B {
- &#064;Test
- public void c() {
-
- }
- }
-</pre>
- *
+ * <pre>
+ * public interface FastTests {}
+ * public interface SlowTests {}
+ *
+ * public static class A {
+ * &#064;Test
+ * public void a() {
+ * fail();
+ * }
+ *
+ * &#064;Category(SlowTests.class)
+ * &#064;Test
+ * public void b() {
+ * }
+ * }
+ *
+ * &#064;Category({SlowTests.class, FastTests.class})
+ * public static class B {
+ * &#064;Test
+ * public void c() {
+ *
+ * }
+ * }
+ * </pre>
+ *
* For more usage, see code example on {@link Categories}.
*/
@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@ValidateWith(CategoryValidator.class)
public @interface Category {
- Class<?>[] value();
+ Class<?>[] value();
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java
new file mode 100644
index 0000000..cee1ae7
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java
@@ -0,0 +1,47 @@
+package org.junit.experimental.categories;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory;
+import org.junit.runner.FilterFactoryParams;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Implementation of FilterFactory for Category filtering.
+ */
+abstract class CategoryFilterFactory implements FilterFactory {
+ /**
+ * Creates a {@link org.junit.experimental.categories.Categories.CategoryFilter} given a
+ * {@link FilterFactoryParams} argument.
+ *
+ * @param params Parameters needed to create the {@link Filter}
+ */
+ public Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException {
+ try {
+ return createFilter(parseCategories(params.getArgs()));
+ } catch (ClassNotFoundException e) {
+ throw new FilterNotCreatedException(e);
+ }
+ }
+
+ /**
+ * Creates a {@link org.junit.experimental.categories.Categories.CategoryFilter} given an array of classes.
+ *
+ * @param categories Category classes.
+ */
+ protected abstract Filter createFilter(List<Class<?>> categories);
+
+ private List<Class<?>> parseCategories(String categories) throws ClassNotFoundException {
+ List<Class<?>> categoryClasses = new ArrayList<Class<?>>();
+
+ for (String category : categories.split(",")) {
+ Class<?> categoryClass = Classes.getClass(category);
+
+ categoryClasses.add(categoryClass);
+ }
+
+ return categoryClasses;
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/CategoryValidator.java b/src/main/java/org/junit/experimental/categories/CategoryValidator.java
new file mode 100644
index 0000000..491d8ac
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/CategoryValidator.java
@@ -0,0 +1,62 @@
+package org.junit.experimental.categories;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableSet;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.validator.AnnotationValidator;
+
+/**
+ * Validates that there are no errors in the use of the {@code Category}
+ * annotation. If there is, a {@code Throwable} object will be added to the list
+ * of errors.
+ *
+ * @since 4.12
+ */
+public final class CategoryValidator extends AnnotationValidator {
+
+ @SuppressWarnings("unchecked")
+ private static final Set<Class<? extends Annotation>> INCOMPATIBLE_ANNOTATIONS = unmodifiableSet(new HashSet<Class<? extends Annotation>>(
+ asList(BeforeClass.class, AfterClass.class, Before.class, After.class)));
+
+ /**
+ * Adds to {@code errors} a throwable for each problem detected. Looks for
+ * {@code BeforeClass}, {@code AfterClass}, {@code Before} and {@code After}
+ * annotations.
+ *
+ * @param method the method that is being validated
+ * @return A list of exceptions detected
+ *
+ * @since 4.12
+ */
+ @Override
+ public List<Exception> validateAnnotatedMethod(FrameworkMethod method) {
+ List<Exception> errors = new ArrayList<Exception>();
+ Annotation[] annotations = method.getAnnotations();
+ for (Annotation annotation : annotations) {
+ for (Class<?> clazz : INCOMPATIBLE_ANNOTATIONS) {
+ if (annotation.annotationType().isAssignableFrom(clazz)) {
+ addErrorMessage(errors, clazz);
+ }
+ }
+ }
+ return unmodifiableList(errors);
+ }
+
+ private void addErrorMessage(List<Exception> errors, Class<?> clazz) {
+ String message = String.format("@%s can not be combined with @Category",
+ clazz.getSimpleName());
+ errors.add(new Exception(message));
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/ExcludeCategories.java b/src/main/java/org/junit/experimental/categories/ExcludeCategories.java
new file mode 100644
index 0000000..8ccb6b5
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/ExcludeCategories.java
@@ -0,0 +1,52 @@
+package org.junit.experimental.categories;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.experimental.categories.Categories.CategoryFilter;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * {@link org.junit.runner.FilterFactory} to exclude categories.
+ *
+ * The {@link Filter} that is created will filter out tests that are categorized with any of the
+ * given categories.
+ *
+ * Usage from command line:
+ * <code>
+ * --filter=org.junit.experimental.categories.ExcludeCategories=pkg.of.Cat1,pkg.of.Cat2
+ * </code>
+ *
+ * Usage from API:
+ * <code>
+ * new ExcludeCategories().createFilter(Cat1.class, Cat2.class);
+ * </code>
+ */
+public final class ExcludeCategories extends CategoryFilterFactory {
+ /**
+ * Creates a {@link Filter} which is only passed by tests that are
+ * not categorized with any of the specified categories.
+ *
+ * @param categories Category classes.
+ */
+ @Override
+ protected Filter createFilter(List<Class<?>> categories) {
+ return new ExcludesAny(categories);
+ }
+
+ private static class ExcludesAny extends CategoryFilter {
+ public ExcludesAny(List<Class<?>> categories) {
+ this(new HashSet<Class<?>>(categories));
+ }
+
+ public ExcludesAny(Set<Class<?>> categories) {
+ super(true, null, true, categories);
+ }
+
+ @Override
+ public String describe() {
+ return "excludes " + super.describe();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/IncludeCategories.java b/src/main/java/org/junit/experimental/categories/IncludeCategories.java
new file mode 100644
index 0000000..38eb693
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/IncludeCategories.java
@@ -0,0 +1,52 @@
+package org.junit.experimental.categories;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.experimental.categories.Categories.CategoryFilter;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * {@link org.junit.runner.FilterFactory} to include categories.
+ *
+ * The {@link Filter} that is created will filter out tests that are categorized with any of the
+ * given categories.
+ *
+ * Usage from command line:
+ * <code>
+ * --filter=org.junit.experimental.categories.IncludeCategories=pkg.of.Cat1,pkg.of.Cat2
+ * </code>
+ *
+ * Usage from API:
+ * <code>
+ * new IncludeCategories().createFilter(Cat1.class, Cat2.class);
+ * </code>
+ */
+public final class IncludeCategories extends CategoryFilterFactory {
+ /**
+ * Creates a {@link Filter} which is only passed by tests that are
+ * categorized with any of the specified categories.
+ *
+ * @param categories Category classes.
+ */
+ @Override
+ protected Filter createFilter(List<Class<?>> categories) {
+ return new IncludesAny(categories);
+ }
+
+ private static class IncludesAny extends CategoryFilter {
+ public IncludesAny(List<Class<?>> categories) {
+ this(new HashSet<Class<?>>(categories));
+ }
+
+ public IncludesAny(Set<Class<?>> categories) {
+ super(true, categories, true, null);
+ }
+
+ @Override
+ public String describe() {
+ return "includes " + super.describe();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java
index 03c3c8c..116d755 100644
--- a/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java
+++ b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java
@@ -4,12 +4,12 @@ package org.junit.experimental.max;
* Thrown when Max cannot read the MaxCore serialization
*/
public class CouldNotReadCoreException extends Exception {
- private static final long serialVersionUID= 1L;
+ private static final long serialVersionUID = 1L;
- /**
- * Constructs
- */
- public CouldNotReadCoreException(Throwable e) {
- super(e);
- }
+ /**
+ * Constructs
+ */
+ public CouldNotReadCoreException(Throwable e) {
+ super(e);
+ }
}
diff --git a/src/main/java/org/junit/experimental/max/MaxCore.java b/src/main/java/org/junit/experimental/max/MaxCore.java
index a2a34a9..625cade 100644
--- a/src/main/java/org/junit/experimental/max/MaxCore.java
+++ b/src/main/java/org/junit/experimental/max/MaxCore.java
@@ -6,7 +6,6 @@ import java.util.Collections;
import java.util.List;
import junit.framework.TestSuite;
-
import org.junit.internal.requests.SortingRequest;
import org.junit.internal.runners.ErrorReportingRunner;
import org.junit.internal.runners.JUnit38ClassRunner;
@@ -21,150 +20,162 @@ import org.junit.runners.model.InitializationError;
/**
* A replacement for JUnitCore, which keeps track of runtime and failure history, and reorders tests
* to maximize the chances that a failing test occurs early in the test run.
- *
+ *
* The rules for sorting are:
* <ol>
* <li> Never-run tests first, in arbitrary order
* <li> Group remaining tests by the date at which they most recently failed.
* <li> Sort groups such that the most recent failure date is first, and never-failing tests are at the end.
- * <li> Within a group, run the fastest tests first.
+ * <li> Within a group, run the fastest tests first.
* </ol>
*/
public class MaxCore {
- private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX= "malformed JUnit 3 test class: ";
-
- /**
- * Create a new MaxCore from a serialized file stored at storedResults
- * @deprecated use storedLocally()
- */
- @Deprecated
- public static MaxCore forFolder(String folderName) {
- return storedLocally(new File(folderName));
- }
-
- /**
- * Create a new MaxCore from a serialized file stored at storedResults
- */
- public static MaxCore storedLocally(File storedResults) {
- return new MaxCore(storedResults);
- }
-
- private final MaxHistory fHistory;
-
- private MaxCore(File storedResults) {
- fHistory = MaxHistory.forFolder(storedResults);
- }
-
- /**
- * Run all the tests in <code>class</code>.
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Class<?> testClass) {
- return run(Request.aClass(testClass));
- }
-
- /**
- * Run all the tests contained in <code>request</code>.
- * @param request the request describing tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Request request) {
- return run(request, new JUnitCore());
- }
-
- /**
- * Run all the tests contained in <code>request</code>.
- *
- * This variant should be used if {@code core} has attached listeners that this
- * run should notify.
- *
- * @param request the request describing tests
- * @param core a JUnitCore to delegate to.
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Request request, JUnitCore core) {
- core.addListener(fHistory.listener());
- return core.run(sortRequest(request).getRunner());
- }
-
- /**
- * @param request
- * @return a new Request, which contains all of the same tests, but in a new order.
- */
- public Request sortRequest(Request request) {
- if (request instanceof SortingRequest) // We'll pay big karma points for this
- return request;
- List<Description> leaves= findLeaves(request);
- Collections.sort(leaves, fHistory.testComparator());
- return constructLeafRequest(leaves);
- }
-
- private Request constructLeafRequest(List<Description> leaves) {
- final List<Runner> runners = new ArrayList<Runner>();
- for (Description each : leaves)
- runners.add(buildRunner(each));
- return new Request() {
- @Override
- public Runner getRunner() {
- try {
- return new Suite((Class<?>)null, runners) {};
- } catch (InitializationError e) {
- return new ErrorReportingRunner(null, e);
- }
- }
- };
- }
-
- private Runner buildRunner(Description each) {
- if (each.toString().equals("TestSuite with 0 tests"))
- return Suite.emptySuite();
- if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX))
- // This is cheating, because it runs the whole class
- // to get the warning for this method, but we can't do better,
- // because JUnit 3.8's
- // thrown away which method the warning is for.
- return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each)));
- Class<?> type= each.getTestClass();
- if (type == null)
- throw new RuntimeException("Can't build a runner from description [" + each + "]");
- String methodName= each.getMethodName();
- if (methodName == null)
- return Request.aClass(type).getRunner();
- return Request.method(type, methodName).getRunner();
- }
-
- private Class<?> getMalformedTestClass(Description each) {
- try {
- return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, ""));
- } catch (ClassNotFoundException e) {
- return null;
- }
- }
-
- /**
- * @param request a request to run
- * @return a list of method-level tests to run, sorted in the order
- * specified in the class comment.
- */
- public List<Description> sortedLeavesForTest(Request request) {
- return findLeaves(sortRequest(request));
- }
-
- private List<Description> findLeaves(Request request) {
- List<Description> results= new ArrayList<Description>();
- findLeaves(null, request.getRunner().getDescription(), results);
- return results;
- }
-
- private void findLeaves(Description parent, Description description, List<Description> results) {
- if (description.getChildren().isEmpty())
- if (description.toString().equals("warning(junit.framework.TestSuite$1)"))
- results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent));
- else
- results.add(description);
- else
- for (Description each : description.getChildren())
- findLeaves(description, each, results);
- }
-}
+ private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX = "malformed JUnit 3 test class: ";
+
+ /**
+ * Create a new MaxCore from a serialized file stored at storedResults
+ *
+ * @deprecated use storedLocally()
+ */
+ @Deprecated
+ public static MaxCore forFolder(String folderName) {
+ return storedLocally(new File(folderName));
+ }
+
+ /**
+ * Create a new MaxCore from a serialized file stored at storedResults
+ */
+ public static MaxCore storedLocally(File storedResults) {
+ return new MaxCore(storedResults);
+ }
+
+ private final MaxHistory history;
+
+ private MaxCore(File storedResults) {
+ history = MaxHistory.forFolder(storedResults);
+ }
+
+ /**
+ * Run all the tests in <code>class</code>.
+ *
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Class<?> testClass) {
+ return run(Request.aClass(testClass));
+ }
+
+ /**
+ * Run all the tests contained in <code>request</code>.
+ *
+ * @param request the request describing tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Request request) {
+ return run(request, new JUnitCore());
+ }
+
+ /**
+ * Run all the tests contained in <code>request</code>.
+ *
+ * This variant should be used if {@code core} has attached listeners that this
+ * run should notify.
+ *
+ * @param request the request describing tests
+ * @param core a JUnitCore to delegate to.
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Request request, JUnitCore core) {
+ core.addListener(history.listener());
+ return core.run(sortRequest(request).getRunner());
+ }
+
+ /**
+ * @return a new Request, which contains all of the same tests, but in a new order.
+ */
+ public Request sortRequest(Request request) {
+ if (request instanceof SortingRequest) {
+ // We'll pay big karma points for this
+ return request;
+ }
+ List<Description> leaves = findLeaves(request);
+ Collections.sort(leaves, history.testComparator());
+ return constructLeafRequest(leaves);
+ }
+
+ private Request constructLeafRequest(List<Description> leaves) {
+ final List<Runner> runners = new ArrayList<Runner>();
+ for (Description each : leaves) {
+ runners.add(buildRunner(each));
+ }
+ return new Request() {
+ @Override
+ public Runner getRunner() {
+ try {
+ return new Suite((Class<?>) null, runners) {
+ };
+ } catch (InitializationError e) {
+ return new ErrorReportingRunner(null, e);
+ }
+ }
+ };
+ }
+
+ private Runner buildRunner(Description each) {
+ if (each.toString().equals("TestSuite with 0 tests")) {
+ return Suite.emptySuite();
+ }
+ if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX)) {
+ // This is cheating, because it runs the whole class
+ // to get the warning for this method, but we can't do better,
+ // because JUnit 3.8's
+ // thrown away which method the warning is for.
+ return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each)));
+ }
+ Class<?> type = each.getTestClass();
+ if (type == null) {
+ throw new RuntimeException("Can't build a runner from description [" + each + "]");
+ }
+ String methodName = each.getMethodName();
+ if (methodName == null) {
+ return Request.aClass(type).getRunner();
+ }
+ return Request.method(type, methodName).getRunner();
+ }
+
+ private Class<?> getMalformedTestClass(Description each) {
+ try {
+ return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, ""));
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param request a request to run
+ * @return a list of method-level tests to run, sorted in the order
+ * specified in the class comment.
+ */
+ public List<Description> sortedLeavesForTest(Request request) {
+ return findLeaves(sortRequest(request));
+ }
+
+ private List<Description> findLeaves(Request request) {
+ List<Description> results = new ArrayList<Description>();
+ findLeaves(null, request.getRunner().getDescription(), results);
+ return results;
+ }
+ private void findLeaves(Description parent, Description description, List<Description> results) {
+ if (description.getChildren().isEmpty()) {
+ if (description.toString().equals("warning(junit.framework.TestSuite$1)")) {
+ results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent));
+ } else {
+ results.add(description);
+ }
+ } else {
+ for (Description each : description.getChildren()) {
+ findLeaves(description, each, results);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/max/MaxHistory.java b/src/main/java/org/junit/experimental/max/MaxHistory.java
index e091793..45a4033 100644
--- a/src/main/java/org/junit/experimental/max/MaxHistory.java
+++ b/src/main/java/org/junit/experimental/max/MaxHistory.java
@@ -24,143 +24,150 @@ import org.junit.runner.notification.RunListener;
* </ul>
*/
public class MaxHistory implements Serializable {
- private static final long serialVersionUID= 1L;
-
- /**
- * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
- * will be saved to {@code file}.
- */
- public static MaxHistory forFolder(File file) {
- if (file.exists())
- try {
- return readHistory(file);
- } catch (CouldNotReadCoreException e) {
- e.printStackTrace();
- file.delete();
- }
- return new MaxHistory(file);
- }
-
- private static MaxHistory readHistory(File storedResults)
- throws CouldNotReadCoreException {
- try {
- FileInputStream file= new FileInputStream(storedResults);
- try {
- ObjectInputStream stream= new ObjectInputStream(file);
- try {
- return (MaxHistory) stream.readObject();
- } finally {
- stream.close();
- }
- } finally {
- file.close();
- }
- } catch (Exception e) {
- throw new CouldNotReadCoreException(e);
- }
- }
-
- private final Map<String, Long> fDurations= new HashMap<String, Long>();
-
- private final Map<String, Long> fFailureTimestamps= new HashMap<String, Long>();
-
- private final File fHistoryStore;
-
- private MaxHistory(File storedResults) {
- fHistoryStore= storedResults;
- }
-
- private void save() throws IOException {
- ObjectOutputStream stream= new ObjectOutputStream(new FileOutputStream(
- fHistoryStore));
- stream.writeObject(this);
- stream.close();
- }
-
- Long getFailureTimestamp(Description key) {
- return fFailureTimestamps.get(key.toString());
- }
-
- void putTestFailureTimestamp(Description key, long end) {
- fFailureTimestamps.put(key.toString(), end);
- }
-
- boolean isNewTest(Description key) {
- return !fDurations.containsKey(key.toString());
- }
-
- Long getTestDuration(Description key) {
- return fDurations.get(key.toString());
- }
-
- void putTestDuration(Description description, long duration) {
- fDurations.put(description.toString(), duration);
- }
-
- private final class RememberingListener extends RunListener {
- private long overallStart= System.currentTimeMillis();
-
- private Map<Description, Long> starts= new HashMap<Description, Long>();
-
- @Override
- public void testStarted(Description description) throws Exception {
- starts.put(description, System.nanoTime()); // Get most accurate
- // possible time
- }
-
- @Override
- public void testFinished(Description description) throws Exception {
- long end= System.nanoTime();
- long start= starts.get(description);
- putTestDuration(description, end - start);
- }
-
- @Override
- public void testFailure(Failure failure) throws Exception {
- putTestFailureTimestamp(failure.getDescription(), overallStart);
- }
-
- @Override
- public void testRunFinished(Result result) throws Exception {
- save();
- }
- }
-
- private class TestComparator implements Comparator<Description> {
- public int compare(Description o1, Description o2) {
- // Always prefer new tests
- if (isNewTest(o1))
- return -1;
- if (isNewTest(o2))
- return 1;
- // Then most recently failed first
- int result= getFailure(o2).compareTo(getFailure(o1));
- return result != 0 ? result
- // Then shorter tests first
- : getTestDuration(o1).compareTo(getTestDuration(o2));
- }
-
- private Long getFailure(Description key) {
- Long result= getFailureTimestamp(key);
- if (result == null)
- return 0L; // 0 = "never failed (that I know about)"
- return result;
- }
- }
-
- /**
- * @return a listener that will update this history based on the test
- * results reported.
- */
- public RunListener listener() {
- return new RememberingListener();
- }
-
- /**
- * @return a comparator that ranks tests based on the JUnit Max sorting
- * rules, as described in the {@link MaxCore} class comment.
- */
- public Comparator<Description> testComparator() {
- return new TestComparator();
- }
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
+ * will be saved to {@code file}.
+ */
+ public static MaxHistory forFolder(File file) {
+ if (file.exists()) {
+ try {
+ return readHistory(file);
+ } catch (CouldNotReadCoreException e) {
+ e.printStackTrace();
+ file.delete();
+ }
+ }
+ return new MaxHistory(file);
+ }
+
+ private static MaxHistory readHistory(File storedResults)
+ throws CouldNotReadCoreException {
+ try {
+ FileInputStream file = new FileInputStream(storedResults);
+ try {
+ ObjectInputStream stream = new ObjectInputStream(file);
+ try {
+ return (MaxHistory) stream.readObject();
+ } finally {
+ stream.close();
+ }
+ } finally {
+ file.close();
+ }
+ } catch (Exception e) {
+ throw new CouldNotReadCoreException(e);
+ }
+ }
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final Map<String, Long> fDurations = new HashMap<String, Long>();
+ private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>();
+ private final File fHistoryStore;
+
+ private MaxHistory(File storedResults) {
+ fHistoryStore = storedResults;
+ }
+
+ private void save() throws IOException {
+ ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(
+ fHistoryStore));
+ stream.writeObject(this);
+ stream.close();
+ }
+
+ Long getFailureTimestamp(Description key) {
+ return fFailureTimestamps.get(key.toString());
+ }
+
+ void putTestFailureTimestamp(Description key, long end) {
+ fFailureTimestamps.put(key.toString(), end);
+ }
+
+ boolean isNewTest(Description key) {
+ return !fDurations.containsKey(key.toString());
+ }
+
+ Long getTestDuration(Description key) {
+ return fDurations.get(key.toString());
+ }
+
+ void putTestDuration(Description description, long duration) {
+ fDurations.put(description.toString(), duration);
+ }
+
+ private final class RememberingListener extends RunListener {
+ private long overallStart = System.currentTimeMillis();
+
+ private Map<Description, Long> starts = new HashMap<Description, Long>();
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ starts.put(description, System.nanoTime()); // Get most accurate
+ // possible time
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ long end = System.nanoTime();
+ long start = starts.get(description);
+ putTestDuration(description, end - start);
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ putTestFailureTimestamp(failure.getDescription(), overallStart);
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ save();
+ }
+ }
+
+ private class TestComparator implements Comparator<Description> {
+ public int compare(Description o1, Description o2) {
+ // Always prefer new tests
+ if (isNewTest(o1)) {
+ return -1;
+ }
+ if (isNewTest(o2)) {
+ return 1;
+ }
+ // Then most recently failed first
+ int result = getFailure(o2).compareTo(getFailure(o1));
+ return result != 0 ? result
+ // Then shorter tests first
+ : getTestDuration(o1).compareTo(getTestDuration(o2));
+ }
+
+ private Long getFailure(Description key) {
+ Long result = getFailureTimestamp(key);
+ if (result == null) {
+ return 0L; // 0 = "never failed (that I know about)"
+ }
+ return result;
+ }
+ }
+
+ /**
+ * @return a listener that will update this history based on the test
+ * results reported.
+ */
+ public RunListener listener() {
+ return new RememberingListener();
+ }
+
+ /**
+ * @return a comparator that ranks tests based on the JUnit Max sorting
+ * rules, as described in the {@link MaxCore} class comment.
+ */
+ public Comparator<Description> testComparator() {
+ return new TestComparator();
+ }
}
diff --git a/src/main/java/org/junit/experimental/results/FailureList.java b/src/main/java/org/junit/experimental/results/FailureList.java
index f4bc9b7..e02eeae 100644
--- a/src/main/java/org/junit/experimental/results/FailureList.java
+++ b/src/main/java/org/junit/experimental/results/FailureList.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.experimental.results;
import java.util.List;
@@ -10,22 +7,22 @@ import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
class FailureList {
- private final List<Failure> failures;
+ private final List<Failure> failures;
- public FailureList(List<Failure> failures) {
- this.failures= failures;
- }
+ public FailureList(List<Failure> failures) {
+ this.failures = failures;
+ }
- public Result result() {
- Result result= new Result();
- RunListener listener= result.createListener();
- for (Failure failure : failures) {
- try {
- listener.testFailure(failure);
- } catch (Exception e) {
- throw new RuntimeException("I can't believe this happened");
- }
- }
- return result;
- }
+ public Result result() {
+ Result result = new Result();
+ RunListener listener = result.createListener();
+ for (Failure failure : failures) {
+ try {
+ listener.testFailure(failure);
+ } catch (Exception e) {
+ throw new RuntimeException("I can't believe this happened");
+ }
+ }
+ return result;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/results/PrintableResult.java b/src/main/java/org/junit/experimental/results/PrintableResult.java
index 8bc6f54..ffe22f0 100644
--- a/src/main/java/org/junit/experimental/results/PrintableResult.java
+++ b/src/main/java/org/junit/experimental/results/PrintableResult.java
@@ -14,50 +14,50 @@ import org.junit.runner.notification.Failure;
* A test result that prints nicely in error messages.
* This is only intended to be used in JUnit self-tests.
* For example:
- *
+ *
* <pre>
* assertThat(testResult(HasExpectedException.class), isSuccessful());
* </pre>
*/
public class PrintableResult {
- /**
- * The result of running JUnit on {@code type}
- */
- public static PrintableResult testResult(Class<?> type) {
- return testResult(Request.aClass(type));
- }
-
- /**
- * The result of running JUnit on Request {@code request}
- */
- public static PrintableResult testResult(Request request) {
- return new PrintableResult(new JUnitCore().run(request));
- }
-
- private Result result;
-
- /**
- * A result that includes the given {@code failures}
- */
- public PrintableResult(List<Failure> failures) {
- this(new FailureList(failures).result());
- }
-
- private PrintableResult(Result result) {
- this.result = result;
- }
-
- @Override
- public String toString() {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- new TextListener(new PrintStream(stream)).testRunFinished(result);
- return stream.toString();
- }
-
- /**
- * Returns the number of failures in this result.
- */
- public int failureCount() {
- return result.getFailures().size();
- }
+ private Result result;
+
+ /**
+ * The result of running JUnit on {@code type}
+ */
+ public static PrintableResult testResult(Class<?> type) {
+ return testResult(Request.aClass(type));
+ }
+
+ /**
+ * The result of running JUnit on Request {@code request}
+ */
+ public static PrintableResult testResult(Request request) {
+ return new PrintableResult(new JUnitCore().run(request));
+ }
+
+ /**
+ * A result that includes the given {@code failures}
+ */
+ public PrintableResult(List<Failure> failures) {
+ this(new FailureList(failures).result());
+ }
+
+ private PrintableResult(Result result) {
+ this.result = result;
+ }
+
+ /**
+ * Returns the number of failures in this result.
+ */
+ public int failureCount() {
+ return result.getFailures().size();
+ }
+
+ @Override
+ public String toString() {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ new TextListener(new PrintStream(stream)).testRunFinished(result);
+ return stream.toString();
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/results/ResultMatchers.java b/src/main/java/org/junit/experimental/results/ResultMatchers.java
index 220d0dc..cf58f1b 100644
--- a/src/main/java/org/junit/experimental/results/ResultMatchers.java
+++ b/src/main/java/org/junit/experimental/results/ResultMatchers.java
@@ -3,68 +3,68 @@ package org.junit.experimental.results;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
-import org.junit.internal.matchers.TypeSafeMatcher;
+import org.hamcrest.TypeSafeMatcher;
/**
* Matchers on a PrintableResult, to enable JUnit self-tests.
* For example:
- *
+ *
* <pre>
- * assertThat(testResult(HasExpectedException.class), isSuccessful());
+ * assertThat(testResult(HasExpectedException.class), isSuccessful());
* </pre>
*/
public class ResultMatchers {
- /**
- * Matches if the tests are all successful
- */
- public static Matcher<PrintableResult> isSuccessful() {
- return failureCountIs(0);
- }
+ /**
+ * Matches if the tests are all successful
+ */
+ public static Matcher<PrintableResult> isSuccessful() {
+ return failureCountIs(0);
+ }
- /**
- * Matches if there are {@code count} failures
- */
- public static Matcher<PrintableResult> failureCountIs(final int count) {
- return new TypeSafeMatcher<PrintableResult>() {
- public void describeTo(Description description) {
- description.appendText("has " + count + " failures");
- }
+ /**
+ * Matches if there are {@code count} failures
+ */
+ public static Matcher<PrintableResult> failureCountIs(final int count) {
+ return new TypeSafeMatcher<PrintableResult>() {
+ public void describeTo(Description description) {
+ description.appendText("has " + count + " failures");
+ }
- @Override
- public boolean matchesSafely(PrintableResult item) {
- return item.failureCount() == count;
- }
- };
- }
-
- /**
- * Matches if the result has exactly one failure, and it contains {@code string}
- */
- public static Matcher<Object> hasSingleFailureContaining(final String string) {
- return new BaseMatcher<Object>() {
- public boolean matches(Object item) {
- return item.toString().contains(string) && failureCountIs(1).matches(item);
- }
+ @Override
+ public boolean matchesSafely(PrintableResult item) {
+ return item.failureCount() == count;
+ }
+ };
+ }
- public void describeTo(Description description) {
- description.appendText("has single failure containing " + string);
- }
- };
- }
+ /**
+ * Matches if the result has exactly one failure, and it contains {@code string}
+ */
+ public static Matcher<Object> hasSingleFailureContaining(final String string) {
+ return new BaseMatcher<Object>() {
+ public boolean matches(Object item) {
+ return item.toString().contains(string) && failureCountIs(1).matches(item);
+ }
- /**
- * Matches if the result has one or more failures, and at least one of them
- * contains {@code string}
- */
- public static Matcher<PrintableResult> hasFailureContaining(final String string) {
- return new BaseMatcher<PrintableResult>() {
- public boolean matches(Object item) {
- return item.toString().contains(string);
- }
+ public void describeTo(Description description) {
+ description.appendText("has single failure containing " + string);
+ }
+ };
+ }
- public void describeTo(Description description) {
- description.appendText("has failure containing " + string);
- }
- };
- }
+ /**
+ * Matches if the result has one or more failures, and at least one of them
+ * contains {@code string}
+ */
+ public static Matcher<PrintableResult> hasFailureContaining(final String string) {
+ return new BaseMatcher<PrintableResult>() {
+ public boolean matches(Object item) {
+ return item.toString().contains(string);
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("has failure containing " + string);
+ }
+ };
+ }
}
diff --git a/src/main/java/org/junit/experimental/runners/Enclosed.java b/src/main/java/org/junit/experimental/runners/Enclosed.java
index b0560ed..610b970 100644
--- a/src/main/java/org/junit/experimental/runners/Enclosed.java
+++ b/src/main/java/org/junit/experimental/runners/Enclosed.java
@@ -1,31 +1,45 @@
package org.junit.experimental.runners;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
import org.junit.runners.Suite;
import org.junit.runners.model.RunnerBuilder;
-
/**
* If you put tests in inner classes, Ant, for example, won't find them. By running the outer class
* with Enclosed, the tests in the inner classes will be run. You might put tests in inner classes
- * to group them for convenience or to share constants.
- *
- * So, for example:
- * <pre>
- * \@RunWith(Enclosed.class)
- * public class ListTests {
- * ...useful shared stuff...
- * public static class OneKindOfListTest {...}
- * public static class AnotherKind {...}
- * }
- * </pre>
- *
- * For a real example, @see org.junit.tests.manipulation.SortableTest.
+ * to group them for convenience or to share constants. Abstract inner classes are ignored.
+ * <p>
+ * So, for example:
+ * <pre>
+ * &#064;RunWith(Enclosed.class)
+ * public class ListTests {
+ * ...useful shared stuff...
+ * public static class OneKindOfListTest {...}
+ * public static class AnotherKind {...}
+ * abstract public static class Ignored {...}
+ * }
+ * </pre>
*/
public class Enclosed extends Suite {
- /**
- * Only called reflectively. Do not use programmatically.
- */
- public Enclosed(Class<?> klass, RunnerBuilder builder) throws Throwable {
- super(builder, klass, klass.getClasses());
- }
+ /**
+ * Only called reflectively. Do not use programmatically.
+ */
+ public Enclosed(Class<?> klass, RunnerBuilder builder) throws Throwable {
+ super(builder, klass, filterAbstractClasses(klass.getClasses()));
+ }
+
+ private static Class<?>[] filterAbstractClasses(final Class<?>[] classes) {
+ final List<Class<?>> filteredList= new ArrayList<Class<?>>(classes.length);
+
+ for (final Class<?> clazz : classes) {
+ if (!Modifier.isAbstract(clazz.getModifiers())) {
+ filteredList.add(clazz);
+ }
+ }
+
+ return filteredList.toArray(new Class<?>[filteredList.size()]);
+ }
}
diff --git a/src/main/java/org/junit/experimental/theories/DataPoint.java b/src/main/java/org/junit/experimental/theories/DataPoint.java
index 2aaba6a..0a017bb 100644
--- a/src/main/java/org/junit/experimental/theories/DataPoint.java
+++ b/src/main/java/org/junit/experimental/theories/DataPoint.java
@@ -1,9 +1,56 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Annotating an field or method with &#064;DataPoint will cause the field value
+ * or the value returned by the method to be used as a potential parameter for
+ * theories in that class, when run with the
+ * {@link org.junit.experimental.theories.Theories Theories} runner.
+ * <p>
+ * A DataPoint is only considered as a potential value for parameters for
+ * which its type is assignable. When multiple {@code DataPoint}s exist
+ * with overlapping types more control can be obtained by naming each DataPoint
+ * using the value of this annotation, e.g. with
+ * <code>&#064;DataPoint({"dataset1", "dataset2"})</code>, and then specifying
+ * which named set to consider as potential values for each parameter using the
+ * {@link org.junit.experimental.theories.FromDataPoints &#064;FromDataPoints}
+ * annotation.
+ * <p>
+ * Parameters with no specified source (i.e. without &#064;FromDataPoints or
+ * other {@link org.junit.experimental.theories.ParametersSuppliedBy
+ * &#064;ParameterSuppliedBy} annotations) will use all {@code DataPoint}s that are
+ * assignable to the parameter type as potential values, including named sets of
+ * {@code DataPoint}s.
+ *
+ * <pre>
+ * &#064;DataPoint
+ * public static String dataPoint = "value";
+ *
+ * &#064;DataPoint("generated")
+ * public static String generatedDataPoint() {
+ * return "generated value";
+ * }
+ *
+ * &#064;Theory
+ * public void theoryMethod(String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({FIELD, METHOD})
public @interface DataPoint {
-
-}
+ String[] value() default {};
+ Class<? extends Throwable>[] ignoredExceptions() default {};
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/DataPoints.java b/src/main/java/org/junit/experimental/theories/DataPoints.java
index 42145e3..b47461b 100644
--- a/src/main/java/org/junit/experimental/theories/DataPoints.java
+++ b/src/main/java/org/junit/experimental/theories/DataPoints.java
@@ -1,9 +1,64 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Annotating an array or iterable-typed field or method with &#064;DataPoints
+ * will cause the values in the array or iterable given to be used as potential
+ * parameters for theories in that class when run with the
+ * {@link org.junit.experimental.theories.Theories Theories} runner.
+ * <p>
+ * DataPoints will only be considered as potential values for parameters for
+ * which their types are assignable. When multiple sets of DataPoints exist with
+ * overlapping types more control can be obtained by naming the DataPoints using
+ * the value of this annotation, e.g. with
+ * <code>&#064;DataPoints({"dataset1", "dataset2"})</code>, and then specifying
+ * which named set to consider as potential values for each parameter using the
+ * {@link org.junit.experimental.theories.FromDataPoints &#064;FromDataPoints}
+ * annotation.
+ * <p>
+ * Parameters with no specified source (i.e. without &#064;FromDataPoints or
+ * other {@link org.junit.experimental.theories.ParametersSuppliedBy
+ * &#064;ParameterSuppliedBy} annotations) will use all DataPoints that are
+ * assignable to the parameter type as potential values, including named sets of
+ * DataPoints.
+ * <p>
+ * DataPoints methods whose array types aren't assignable from the target
+ * parameter type (and so can't possibly return relevant values) will not be
+ * called when generating values for that parameter. Iterable-typed datapoints
+ * methods must always be called though, as this information is not available
+ * here after generic type erasure, so expensive methods returning iterable
+ * datapoints are a bad idea.
+ *
+ * <pre>
+ * &#064;DataPoints
+ * public static String[] dataPoints = new String[] { ... };
+ *
+ * &#064;DataPoints
+ * public static String[] generatedDataPoints() {
+ * return new String[] { ... };
+ * }
+ *
+ * &#064;Theory
+ * public void theoryMethod(String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({ FIELD, METHOD })
public @interface DataPoints {
+ String[] value() default {};
+ Class<? extends Throwable>[] ignoredExceptions() default {};
}
diff --git a/src/main/java/org/junit/experimental/theories/FromDataPoints.java b/src/main/java/org/junit/experimental/theories/FromDataPoints.java
new file mode 100644
index 0000000..2b149ca
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/FromDataPoints.java
@@ -0,0 +1,54 @@
+package org.junit.experimental.theories;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.experimental.theories.internal.SpecificDataPointsSupplier;
+
+/**
+ * Annotating a parameter of a {@link org.junit.experimental.theories.Theory
+ * &#064;Theory} method with <code>&#064;FromDataPoints</code> will limit the
+ * datapoints considered as potential values for that parameter to just the
+ * {@link org.junit.experimental.theories.DataPoints DataPoints} with the given
+ * name. DataPoint names can be given as the value parameter of the
+ * &#064;DataPoints annotation.
+ * <p>
+ * DataPoints without names will not be considered as values for any parameters
+ * annotated with &#064;FromDataPoints.
+ * <pre>
+ * &#064;DataPoints
+ * public static String[] unnamed = new String[] { ... };
+ *
+ * &#064;DataPoints("regexes")
+ * public static String[] regexStrings = new String[] { ... };
+ *
+ * &#064;DataPoints({"forMatching", "alphanumeric"})
+ * public static String[] testStrings = new String[] { ... };
+ *
+ * &#064;Theory
+ * public void stringTheory(String param) {
+ * // This will be called with every value in 'regexStrings',
+ * // 'testStrings' and 'unnamed'.
+ * }
+ *
+ * &#064;Theory
+ * public void regexTheory(&#064;FromDataPoints("regexes") String regex,
+ * &#064;FromDataPoints("forMatching") String value) {
+ * // This will be called with only the values in 'regexStrings' as
+ * // regex, only the values in 'testStrings' as value, and none
+ * // of the values in 'unnamed'.
+ * }
+ * </pre>
+ *
+ * @see org.junit.experimental.theories.Theory
+ * @see org.junit.experimental.theories.DataPoint
+ * @see org.junit.experimental.theories.DataPoints
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@ParametersSuppliedBy(SpecificDataPointsSupplier.class)
+public @interface FromDataPoints {
+ String value();
+}
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSignature.java b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
index e7150fc..cf22583 100644
--- a/src/main/java/org/junit/experimental/theories/ParameterSignature.java
+++ b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.experimental.theories;
import java.lang.annotation.Annotation;
@@ -8,83 +5,130 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ParameterSignature {
- public static ArrayList<ParameterSignature> signatures(Method method) {
- return signatures(method.getParameterTypes(), method
- .getParameterAnnotations());
- }
-
- public static List<ParameterSignature> signatures(Constructor<?> constructor) {
- return signatures(constructor.getParameterTypes(), constructor
- .getParameterAnnotations());
- }
-
- private static ArrayList<ParameterSignature> signatures(
- Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
- ArrayList<ParameterSignature> sigs= new ArrayList<ParameterSignature>();
- for (int i= 0; i < parameterTypes.length; i++) {
- sigs.add(new ParameterSignature(parameterTypes[i],
- parameterAnnotations[i]));
- }
- return sigs;
- }
-
- private final Class<?> type;
-
- private final Annotation[] annotations;
-
- private ParameterSignature(Class<?> type, Annotation[] annotations) {
- this.type= type;
- this.annotations= annotations;
- }
-
- public boolean canAcceptType(Class<?> candidate) {
- return type.isAssignableFrom(candidate);
- }
-
- public Class<?> getType() {
- return type;
- }
-
- public List<Annotation> getAnnotations() {
- return Arrays.asList(annotations);
- }
-
- public boolean canAcceptArrayType(Class<?> type) {
- return type.isArray() && canAcceptType(type.getComponentType());
- }
-
- public boolean hasAnnotation(Class<? extends Annotation> type) {
- return getAnnotation(type) != null;
- }
-
- public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
- Annotation[] annotations2= annotations;
- return findDeepAnnotation(annotations2, annotationType, 3);
- }
-
- private <T extends Annotation> T findDeepAnnotation(
- Annotation[] annotations, Class<T> annotationType, int depth) {
- if (depth == 0)
- return null;
- for (Annotation each : annotations) {
- if (annotationType.isInstance(each))
- return annotationType.cast(each);
- Annotation candidate= findDeepAnnotation(each.annotationType()
- .getAnnotations(), annotationType, depth - 1);
- if (candidate != null)
- return annotationType.cast(candidate);
- }
-
- return null;
- }
-
- public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
- for (Annotation each : getAnnotations())
- if (annotationType.isInstance(each))
- return annotationType.cast(each);
- return null;
- }
+
+ private static final Map<Class<?>, Class<?>> CONVERTABLE_TYPES_MAP = buildConvertableTypesMap();
+
+ private static Map<Class<?>, Class<?>> buildConvertableTypesMap() {
+ Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
+
+ putSymmetrically(map, boolean.class, Boolean.class);
+ putSymmetrically(map, byte.class, Byte.class);
+ putSymmetrically(map, short.class, Short.class);
+ putSymmetrically(map, char.class, Character.class);
+ putSymmetrically(map, int.class, Integer.class);
+ putSymmetrically(map, long.class, Long.class);
+ putSymmetrically(map, float.class, Float.class);
+ putSymmetrically(map, double.class, Double.class);
+
+ return Collections.unmodifiableMap(map);
+ }
+
+ private static <T> void putSymmetrically(Map<T, T> map, T a, T b) {
+ map.put(a, b);
+ map.put(b, a);
+ }
+
+ public static ArrayList<ParameterSignature> signatures(Method method) {
+ return signatures(method.getParameterTypes(), method
+ .getParameterAnnotations());
+ }
+
+ public static List<ParameterSignature> signatures(Constructor<?> constructor) {
+ return signatures(constructor.getParameterTypes(), constructor
+ .getParameterAnnotations());
+ }
+
+ private static ArrayList<ParameterSignature> signatures(
+ Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
+ ArrayList<ParameterSignature> sigs = new ArrayList<ParameterSignature>();
+ for (int i = 0; i < parameterTypes.length; i++) {
+ sigs.add(new ParameterSignature(parameterTypes[i],
+ parameterAnnotations[i]));
+ }
+ return sigs;
+ }
+
+ private final Class<?> type;
+
+ private final Annotation[] annotations;
+
+ private ParameterSignature(Class<?> type, Annotation[] annotations) {
+ this.type = type;
+ this.annotations = annotations;
+ }
+
+ public boolean canAcceptValue(Object candidate) {
+ return (candidate == null) ? !type.isPrimitive() : canAcceptType(candidate.getClass());
+ }
+
+ public boolean canAcceptType(Class<?> candidate) {
+ return type.isAssignableFrom(candidate) ||
+ isAssignableViaTypeConversion(type, candidate);
+ }
+
+ public boolean canPotentiallyAcceptType(Class<?> candidate) {
+ return candidate.isAssignableFrom(type) ||
+ isAssignableViaTypeConversion(candidate, type) ||
+ canAcceptType(candidate);
+ }
+
+ private boolean isAssignableViaTypeConversion(Class<?> targetType, Class<?> candidate) {
+ if (CONVERTABLE_TYPES_MAP.containsKey(candidate)) {
+ Class<?> wrapperClass = CONVERTABLE_TYPES_MAP.get(candidate);
+ return targetType.isAssignableFrom(wrapperClass);
+ } else {
+ return false;
+ }
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+
+ public List<Annotation> getAnnotations() {
+ return Arrays.asList(annotations);
+ }
+
+ public boolean hasAnnotation(Class<? extends Annotation> type) {
+ return getAnnotation(type) != null;
+ }
+
+ public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
+ Annotation[] annotations2 = annotations;
+ return findDeepAnnotation(annotations2, annotationType, 3);
+ }
+
+ private <T extends Annotation> T findDeepAnnotation(
+ Annotation[] annotations, Class<T> annotationType, int depth) {
+ if (depth == 0) {
+ return null;
+ }
+ for (Annotation each : annotations) {
+ if (annotationType.isInstance(each)) {
+ return annotationType.cast(each);
+ }
+ Annotation candidate = findDeepAnnotation(each.annotationType()
+ .getAnnotations(), annotationType, depth - 1);
+ if (candidate != null) {
+ return annotationType.cast(candidate);
+ }
+ }
+
+ return null;
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ for (Annotation each : getAnnotations()) {
+ if (annotationType.isInstance(each)) {
+ return annotationType.cast(each);
+ }
+ }
+ return null;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
index 9016c91..bac8b34 100644
--- a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
@@ -2,7 +2,42 @@ package org.junit.experimental.theories;
import java.util.List;
-
+/**
+ * Abstract parent class for suppliers of input data points for theories. Extend this class to customize how {@link
+ * org.junit.experimental.theories.Theories Theories} runner
+ * finds accepted data points. Then use your class together with <b>&#064;ParametersSuppliedBy</b> on input
+ * parameters for theories.
+ *
+ * <p>
+ * For example, here is a supplier for values between two integers, and an annotation that references it:
+ *
+ * <pre>
+ * &#064;Retention(RetentionPolicy.RUNTIME)
+ * <b>&#064;ParametersSuppliedBy</b>(BetweenSupplier.class)
+ * public @interface Between {
+ * int first();
+ *
+ * int last();
+ * }
+ *
+ * public static class BetweenSupplier extends <b>ParameterSupplier</b> {
+ * &#064;Override
+ * public List&lt;<b>PotentialAssignment</b>&gt; getValueSources(<b>ParameterSignature</b> sig) {
+ * List&lt;<b>PotentialAssignment</b>&gt; list = new ArrayList&lt;PotentialAssignment&gt;();
+ * Between annotation = (Between) sig.getSupplierAnnotation();
+ *
+ * for (int i = annotation.first(); i &lt;= annotation.last(); i++)
+ * list.add(<b>PotentialAssignment</b>.forValue("ints", i));
+ * return list;
+ * }
+ * }
+ * </pre>
+ * </p>
+ *
+ * @see org.junit.experimental.theories.ParametersSuppliedBy
+ * @see org.junit.experimental.theories.Theories
+ * @see org.junit.experimental.theories.FromDataPoints
+ */
public abstract class ParameterSupplier {
- public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig);
+ public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable;
}
diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
index 8f090ef..15b5d95 100644
--- a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
+++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
@@ -1,12 +1,48 @@
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.PARAMETER;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
-
+/**
+ * Annotating a {@link org.junit.experimental.theories.Theory Theory} method
+ * parameter with &#064;ParametersSuppliedBy causes it to be supplied with
+ * values from the named
+ * {@link org.junit.experimental.theories.ParameterSupplier ParameterSupplier}
+ * when run as a theory by the {@link org.junit.experimental.theories.Theories
+ * Theories} runner.
+ *
+ * In addition, annotations themselves can be annotated with
+ * &#064;ParametersSuppliedBy, and then used similarly. ParameterSuppliedBy
+ * annotations on parameters are detected by searching up this heirarchy such
+ * that these act as syntactic sugar, making:
+ *
+ * <pre>
+ * &#064;ParametersSuppliedBy(Supplier.class)
+ * public &#064;interface SpecialParameter { }
+ *
+ * &#064;Theory
+ * public void theoryMethod(&#064;SpecialParameter String param) {
+ * ...
+ * }
+ * </pre>
+ *
+ * equivalent to:
+ *
+ * <pre>
+ * &#064;Theory
+ * public void theoryMethod(&#064;ParametersSuppliedBy(Supplier.class) String param) {
+ * ...
+ * }
+ * </pre>
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target({ ANNOTATION_TYPE, PARAMETER })
public @interface ParametersSuppliedBy {
- Class<? extends ParameterSupplier> value();
+ Class<? extends ParameterSupplier> value();
}
diff --git a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
index 0c008d0..18ca07a 100644
--- a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
+++ b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
@@ -1,31 +1,52 @@
package org.junit.experimental.theories;
+import static java.lang.String.format;
+
public abstract class PotentialAssignment {
- public static class CouldNotGenerateValueException extends Exception {
- private static final long serialVersionUID= 1L;
- }
-
- public static PotentialAssignment forValue(final String name, final Object value) {
- return new PotentialAssignment() {
- @Override
- public Object getValue() throws CouldNotGenerateValueException {
- return value;
- }
-
- @Override
- public String toString() {
- return String.format("[%s]", value);
- }
-
- @Override
- public String getDescription()
- throws CouldNotGenerateValueException {
- return name;
- }
- };
- }
-
- public abstract Object getValue() throws CouldNotGenerateValueException;
-
- public abstract String getDescription() throws CouldNotGenerateValueException;
-}
+ public static class CouldNotGenerateValueException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public CouldNotGenerateValueException() {
+ }
+
+ public CouldNotGenerateValueException(Throwable e) {
+ super(e);
+ }
+ }
+
+ public static PotentialAssignment forValue(final String name, final Object value) {
+ return new PotentialAssignment() {
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return format("[%s]", value);
+ }
+
+ @Override
+ public String getDescription() {
+ String valueString;
+
+ if (value == null) {
+ valueString = "null";
+ } else {
+ try {
+ valueString = format("\"%s\"", value);
+ } catch (Throwable e) {
+ valueString = format("[toString() threw %s: %s]",
+ e.getClass().getSimpleName(), e.getMessage());
+ }
+ }
+
+ return format("%s <from %s>", valueString, name);
+ }
+ };
+ }
+
+ public abstract Object getValue() throws CouldNotGenerateValueException;
+
+ public abstract String getDescription() throws CouldNotGenerateValueException;
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/Theories.java b/src/main/java/org/junit/experimental/theories/Theories.java
index 82ff98b..817f553 100644
--- a/src/main/java/org/junit/experimental/theories/Theories.java
+++ b/src/main/java/org/junit/experimental/theories/Theories.java
@@ -1,16 +1,14 @@
-/**
- *
- */
package org.junit.experimental.theories;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
-import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException;
+import org.junit.Assume;
import org.junit.experimental.theories.internal.Assignments;
import org.junit.experimental.theories.internal.ParameterizedAssertionError;
import org.junit.internal.AssumptionViolatedException;
@@ -20,180 +18,288 @@ import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
+/**
+ * The Theories runner allows to test a certain functionality against a subset of an infinite set of data points.
+ * <p>
+ * A Theory is a piece of functionality (a method) that is executed against several data inputs called data points.
+ * To make a test method a theory you mark it with <b>&#064;Theory</b>. To create a data point you create a public
+ * field in your test class and mark it with <b>&#064;DataPoint</b>. The Theories runner then executes your test
+ * method as many times as the number of data points declared, providing a different data point as
+ * the input argument on each invocation.
+ * </p>
+ * <p>
+ * A Theory differs from standard test method in that it captures some aspect of the intended behavior in possibly
+ * infinite numbers of scenarios which corresponds to the number of data points declared. Using assumptions and
+ * assertions properly together with covering multiple scenarios with different data points can make your tests more
+ * flexible and bring them closer to scientific theories (hence the name).
+ * </p>
+ * <p>
+ * For example:
+ * <pre>
+ *
+ * &#064;RunWith(<b>Theories.class</b>)
+ * public class UserTest {
+ * <b>&#064;DataPoint</b>
+ * public static String GOOD_USERNAME = "optimus";
+ * <b>&#064;DataPoint</b>
+ * public static String USERNAME_WITH_SLASH = "optimus/prime";
+ *
+ * <b>&#064;Theory</b>
+ * public void filenameIncludesUsername(String username) {
+ * assumeThat(username, not(containsString("/")));
+ * assertThat(new User(username).configFileName(), containsString(username));
+ * }
+ * }
+ * </pre>
+ * This makes it clear that the user's filename should be included in the config file name,
+ * only if it doesn't contain a slash. Another test or theory might define what happens when a username does contain
+ * a slash. <code>UserTest</code> will attempt to run <code>filenameIncludesUsername</code> on every compatible data
+ * point defined in the class. If any of the assumptions fail, the data point is silently ignored. If all of the
+ * assumptions pass, but an assertion fails, the test fails.
+ * <p>
+ * Defining general statements as theories allows data point reuse across a bunch of functionality tests and also
+ * allows automated tools to search for new, unexpected data points that expose bugs.
+ * </p>
+ * <p>
+ * The support for Theories has been absorbed from the Popper project, and more complete documentation can be found
+ * from that projects archived documentation.
+ * </p>
+ *
+ * @see <a href="http://web.archive.org/web/20071012143326/popper.tigris.org/tutorial.html">Archived Popper project documentation</a>
+ * @see <a href="http://web.archive.org/web/20110608210825/http://shareandenjoy.saff.net/tdd-specifications.pdf">Paper on Theories</a>
+ */
public class Theories extends BlockJUnit4ClassRunner {
- public Theories(Class<?> klass) throws InitializationError {
- super(klass);
- }
-
- @Override
- protected void collectInitializationErrors(List<Throwable> errors) {
- super.collectInitializationErrors(errors);
- validateDataPointFields(errors);
- }
-
- private void validateDataPointFields(List<Throwable> errors) {
- Field[] fields= getTestClass().getJavaClass().getDeclaredFields();
-
- for (Field each : fields)
- if (each.getAnnotation(DataPoint.class) != null && !Modifier.isStatic(each.getModifiers()))
- errors.add(new Error("DataPoint field " + each.getName() + " must be static"));
- }
-
- @Override
- protected void validateConstructor(List<Throwable> errors) {
- validateOnlyOneConstructor(errors);
- }
-
- @Override
- protected void validateTestMethods(List<Throwable> errors) {
- for (FrameworkMethod each : computeTestMethods())
- if(each.getAnnotation(Theory.class) != null)
- each.validatePublicVoid(false, errors);
- else
- each.validatePublicVoidNoArg(false, errors);
- }
-
- @Override
- protected List<FrameworkMethod> computeTestMethods() {
- List<FrameworkMethod> testMethods= super.computeTestMethods();
- List<FrameworkMethod> theoryMethods= getTestClass().getAnnotatedMethods(Theory.class);
- testMethods.removeAll(theoryMethods);
- testMethods.addAll(theoryMethods);
- return testMethods;
- }
-
- @Override
- public Statement methodBlock(final FrameworkMethod method) {
- return new TheoryAnchor(method, getTestClass());
- }
-
- public static class TheoryAnchor extends Statement {
- private int successes= 0;
-
- private FrameworkMethod fTestMethod;
- private TestClass fTestClass;
-
- private List<AssumptionViolatedException> fInvalidParameters= new ArrayList<AssumptionViolatedException>();
-
- public TheoryAnchor(FrameworkMethod method, TestClass testClass) {
- fTestMethod= method;
- fTestClass= testClass;
- }
+ public Theories(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ super.collectInitializationErrors(errors);
+ validateDataPointFields(errors);
+ validateDataPointMethods(errors);
+ }
+
+ private void validateDataPointFields(List<Throwable> errors) {
+ Field[] fields = getTestClass().getJavaClass().getDeclaredFields();
+
+ for (Field field : fields) {
+ if (field.getAnnotation(DataPoint.class) == null && field.getAnnotation(DataPoints.class) == null) {
+ continue;
+ }
+ if (!Modifier.isStatic(field.getModifiers())) {
+ errors.add(new Error("DataPoint field " + field.getName() + " must be static"));
+ }
+ if (!Modifier.isPublic(field.getModifiers())) {
+ errors.add(new Error("DataPoint field " + field.getName() + " must be public"));
+ }
+ }
+ }
+
+ private void validateDataPointMethods(List<Throwable> errors) {
+ Method[] methods = getTestClass().getJavaClass().getDeclaredMethods();
+
+ for (Method method : methods) {
+ if (method.getAnnotation(DataPoint.class) == null && method.getAnnotation(DataPoints.class) == null) {
+ continue;
+ }
+ if (!Modifier.isStatic(method.getModifiers())) {
+ errors.add(new Error("DataPoint method " + method.getName() + " must be static"));
+ }
+ if (!Modifier.isPublic(method.getModifiers())) {
+ errors.add(new Error("DataPoint method " + method.getName() + " must be public"));
+ }
+ }
+ }
+
+ @Override
+ protected void validateConstructor(List<Throwable> errors) {
+ validateOnlyOneConstructor(errors);
+ }
+
+ @Override
+ protected void validateTestMethods(List<Throwable> errors) {
+ for (FrameworkMethod each : computeTestMethods()) {
+ if (each.getAnnotation(Theory.class) != null) {
+ each.validatePublicVoid(false, errors);
+ each.validateNoTypeParametersOnArgs(errors);
+ } else {
+ each.validatePublicVoidNoArg(false, errors);
+ }
+
+ for (ParameterSignature signature : ParameterSignature.signatures(each.getMethod())) {
+ ParametersSuppliedBy annotation = signature.findDeepAnnotation(ParametersSuppliedBy.class);
+ if (annotation != null) {
+ validateParameterSupplier(annotation.value(), errors);
+ }
+ }
+ }
+ }
+
+ private void validateParameterSupplier(Class<? extends ParameterSupplier> supplierClass, List<Throwable> errors) {
+ Constructor<?>[] constructors = supplierClass.getConstructors();
+
+ if (constructors.length != 1) {
+ errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
+ " must have only one constructor (either empty or taking only a TestClass)"));
+ } else {
+ Class<?>[] paramTypes = constructors[0].getParameterTypes();
+ if (!(paramTypes.length == 0) && !paramTypes[0].equals(TestClass.class)) {
+ errors.add(new Error("ParameterSupplier " + supplierClass.getName() +
+ " constructor must take either nothing or a single TestClass instance"));
+ }
+ }
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> testMethods = new ArrayList<FrameworkMethod>(super.computeTestMethods());
+ List<FrameworkMethod> theoryMethods = getTestClass().getAnnotatedMethods(Theory.class);
+ testMethods.removeAll(theoryMethods);
+ testMethods.addAll(theoryMethods);
+ return testMethods;
+ }
+
+ @Override
+ public Statement methodBlock(final FrameworkMethod method) {
+ return new TheoryAnchor(method, getTestClass());
+ }
+
+ public static class TheoryAnchor extends Statement {
+ private int successes = 0;
+
+ private final FrameworkMethod testMethod;
+ private final TestClass testClass;
+
+ private List<AssumptionViolatedException> fInvalidParameters = new ArrayList<AssumptionViolatedException>();
+
+ public TheoryAnchor(FrameworkMethod testMethod, TestClass testClass) {
+ this.testMethod = testMethod;
+ this.testClass = testClass;
+ }
private TestClass getTestClass() {
- return fTestClass;
+ return testClass;
}
- @Override
- public void evaluate() throws Throwable {
- runWithAssignment(Assignments.allUnassigned(
- fTestMethod.getMethod(), getTestClass()));
-
- if (successes == 0)
- Assert
- .fail("Never found parameters that satisfied method assumptions. Violated assumptions: "
- + fInvalidParameters);
- }
-
- protected void runWithAssignment(Assignments parameterAssignment)
- throws Throwable {
- if (!parameterAssignment.isComplete()) {
- runWithIncompleteAssignment(parameterAssignment);
- } else {
- runWithCompleteAssignment(parameterAssignment);
- }
- }
-
- protected void runWithIncompleteAssignment(Assignments incomplete)
- throws InstantiationException, IllegalAccessException,
- Throwable {
- for (PotentialAssignment source : incomplete
- .potentialsForNextUnassigned()) {
- runWithAssignment(incomplete.assignNext(source));
- }
- }
-
- protected void runWithCompleteAssignment(final Assignments complete)
- throws InstantiationException, IllegalAccessException,
- InvocationTargetException, NoSuchMethodException, Throwable {
- new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
- @Override
- protected void collectInitializationErrors(
- List<Throwable> errors) {
- // do nothing
- }
-
- @Override
- public Statement methodBlock(FrameworkMethod method) {
- final Statement statement= super.methodBlock(method);
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- try {
- statement.evaluate();
- handleDataPointSuccess();
- } catch (AssumptionViolatedException e) {
- handleAssumptionViolation(e);
- } catch (Throwable e) {
- reportParameterizedError(e, complete
- .getArgumentStrings(nullsOk()));
- }
- }
-
- };
- }
-
- @Override
- protected Statement methodInvoker(FrameworkMethod method, Object test) {
- return methodCompletesWithParameters(method, complete, test);
- }
-
- @Override
- public Object createTest() throws Exception {
- return getTestClass().getOnlyConstructor().newInstance(
- complete.getConstructorArguments(nullsOk()));
- }
- }.methodBlock(fTestMethod).evaluate();
- }
-
- private Statement methodCompletesWithParameters(
- final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- try {
- final Object[] values= complete.getMethodArguments(
- nullsOk());
- method.invokeExplosively(freshInstance, values);
- } catch (CouldNotGenerateValueException e) {
- // ignore
- }
- }
- };
- }
-
- protected void handleAssumptionViolation(AssumptionViolatedException e) {
- fInvalidParameters.add(e);
- }
-
- protected void reportParameterizedError(Throwable e, Object... params)
- throws Throwable {
- if (params.length == 0)
- throw e;
- throw new ParameterizedAssertionError(e, fTestMethod.getName(),
- params);
- }
-
- private boolean nullsOk() {
- Theory annotation= fTestMethod.getMethod().getAnnotation(
- Theory.class);
- if (annotation == null)
- return false;
- return annotation.nullsAccepted();
- }
-
- protected void handleDataPointSuccess() {
- successes++;
- }
- }
+ @Override
+ public void evaluate() throws Throwable {
+ runWithAssignment(Assignments.allUnassigned(
+ testMethod.getMethod(), getTestClass()));
+
+ //if this test method is not annotated with Theory, then no successes is a valid case
+ boolean hasTheoryAnnotation = testMethod.getAnnotation(Theory.class) != null;
+ if (successes == 0 && hasTheoryAnnotation) {
+ Assert
+ .fail("Never found parameters that satisfied method assumptions. Violated assumptions: "
+ + fInvalidParameters);
+ }
+ }
+
+ protected void runWithAssignment(Assignments parameterAssignment)
+ throws Throwable {
+ if (!parameterAssignment.isComplete()) {
+ runWithIncompleteAssignment(parameterAssignment);
+ } else {
+ runWithCompleteAssignment(parameterAssignment);
+ }
+ }
+
+ protected void runWithIncompleteAssignment(Assignments incomplete)
+ throws Throwable {
+ for (PotentialAssignment source : incomplete
+ .potentialsForNextUnassigned()) {
+ runWithAssignment(incomplete.assignNext(source));
+ }
+ }
+
+ protected void runWithCompleteAssignment(final Assignments complete)
+ throws Throwable {
+ new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
+ @Override
+ protected void collectInitializationErrors(
+ List<Throwable> errors) {
+ // do nothing
+ }
+
+ @Override
+ public Statement methodBlock(FrameworkMethod method) {
+ final Statement statement = super.methodBlock(method);
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ statement.evaluate();
+ handleDataPointSuccess();
+ } catch (AssumptionViolatedException e) {
+ handleAssumptionViolation(e);
+ } catch (Throwable e) {
+ reportParameterizedError(e, complete
+ .getArgumentStrings(nullsOk()));
+ }
+ }
+
+ };
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test) {
+ return methodCompletesWithParameters(method, complete, test);
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ Object[] params = complete.getConstructorArguments();
+
+ if (!nullsOk()) {
+ Assume.assumeNotNull(params);
+ }
+
+ return getTestClass().getOnlyConstructor().newInstance(params);
+ }
+ }.methodBlock(testMethod).evaluate();
+ }
+
+ private Statement methodCompletesWithParameters(
+ final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ final Object[] values = complete.getMethodArguments();
+
+ if (!nullsOk()) {
+ Assume.assumeNotNull(values);
+ }
+
+ method.invokeExplosively(freshInstance, values);
+ }
+ };
+ }
+
+ protected void handleAssumptionViolation(AssumptionViolatedException e) {
+ fInvalidParameters.add(e);
+ }
+
+ protected void reportParameterizedError(Throwable e, Object... params)
+ throws Throwable {
+ if (params.length == 0) {
+ throw e;
+ }
+ throw new ParameterizedAssertionError(e, testMethod.getName(),
+ params);
+ }
+
+ private boolean nullsOk() {
+ Theory annotation = testMethod.getMethod().getAnnotation(
+ Theory.class);
+ if (annotation == null) {
+ return false;
+ }
+ return annotation.nullsAccepted();
+ }
+
+ protected void handleDataPointSuccess() {
+ successes++;
+ }
+ }
}
diff --git a/src/main/java/org/junit/experimental/theories/Theory.java b/src/main/java/org/junit/experimental/theories/Theory.java
index 134fe9d..0b9f2c4 100644
--- a/src/main/java/org/junit/experimental/theories/Theory.java
+++ b/src/main/java/org/junit/experimental/theories/Theory.java
@@ -1,12 +1,18 @@
-/**
- *
- */
package org.junit.experimental.theories;
+import static java.lang.annotation.ElementType.METHOD;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Marks test methods that should be read as theories by the {@link org.junit.experimental.theories.Theories Theories} runner.
+ *
+ * @see org.junit.experimental.theories.Theories
+ */
@Retention(RetentionPolicy.RUNTIME)
+@Target(METHOD)
public @interface Theory {
- boolean nullsAccepted() default true;
+ boolean nullsAccepted() default true;
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
index 615cc3e..f15fb28 100644
--- a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
@@ -1,19 +1,19 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
+import org.junit.Assume;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.ParameterSignature;
import org.junit.experimental.theories.ParameterSupplier;
import org.junit.experimental.theories.PotentialAssignment;
+import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;
@@ -21,107 +21,184 @@ import org.junit.runners.model.TestClass;
* Supplies Theory parameters based on all public members of the target class.
*/
public class AllMembersSupplier extends ParameterSupplier {
- static class MethodParameterValue extends PotentialAssignment {
- private final FrameworkMethod fMethod;
-
- private MethodParameterValue(FrameworkMethod dataPointMethod) {
- fMethod= dataPointMethod;
- }
-
- @Override
- public Object getValue() throws CouldNotGenerateValueException {
- try {
- return fMethod.invokeExplosively(null);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(
- "unexpected: argument length is checked");
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "unexpected: getMethods returned an inaccessible method");
- } catch (Throwable e) {
- throw new CouldNotGenerateValueException();
- // do nothing, just look for more values
- }
- }
-
- @Override
- public String getDescription() throws CouldNotGenerateValueException {
- return fMethod.getName();
- }
- }
-
- private final TestClass fClass;
-
- /**
- * Constructs a new supplier for {@code type}
- */
- public AllMembersSupplier(TestClass type) {
- fClass= type;
- }
-
- @Override
- public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
- List<PotentialAssignment> list= new ArrayList<PotentialAssignment>();
-
- addFields(sig, list);
- addSinglePointMethods(sig, list);
- addMultiPointMethods(list);
-
- return list;
- }
-
- private void addMultiPointMethods(List<PotentialAssignment> list) {
- for (FrameworkMethod dataPointsMethod : fClass
- .getAnnotatedMethods(DataPoints.class))
- try {
- addArrayValues(dataPointsMethod.getName(), list, dataPointsMethod.invokeExplosively(null));
- } catch (Throwable e) {
- // ignore and move on
- }
- }
-
- @SuppressWarnings("deprecation")
- private void addSinglePointMethods(ParameterSignature sig,
- List<PotentialAssignment> list) {
- for (FrameworkMethod dataPointMethod : fClass
- .getAnnotatedMethods(DataPoint.class)) {
- Class<?> type= sig.getType();
- if ((dataPointMethod.producesType(type)))
- list.add(new MethodParameterValue(dataPointMethod));
- }
- }
-
- private void addFields(ParameterSignature sig,
- List<PotentialAssignment> list) {
- for (final Field field : fClass.getJavaClass().getFields()) {
- if (Modifier.isStatic(field.getModifiers())) {
- Class<?> type= field.getType();
- if (sig.canAcceptArrayType(type)
- && field.getAnnotation(DataPoints.class) != null) {
- addArrayValues(field.getName(), list, getStaticFieldValue(field));
- } else if (sig.canAcceptType(type)
- && field.getAnnotation(DataPoint.class) != null) {
- list.add(PotentialAssignment
- .forValue(field.getName(), getStaticFieldValue(field)));
- }
- }
- }
- }
-
- private void addArrayValues(String name, List<PotentialAssignment> list, Object array) {
- for (int i= 0; i < Array.getLength(array); i++)
- list.add(PotentialAssignment.forValue(name + "[" + i + "]", Array.get(array, i)));
- }
-
- private Object getStaticFieldValue(final Field field) {
- try {
- return field.get(null);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(
- "unexpected: field from getClass doesn't exist on object");
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "unexpected: getFields returned an inaccessible field");
- }
- }
+ static class MethodParameterValue extends PotentialAssignment {
+ private final FrameworkMethod method;
+
+ private MethodParameterValue(FrameworkMethod dataPointMethod) {
+ method = dataPointMethod;
+ }
+
+ @Override
+ public Object getValue() throws CouldNotGenerateValueException {
+ try {
+ return method.invokeExplosively(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: argument length is checked");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getMethods returned an inaccessible method");
+ } catch (Throwable throwable) {
+ DataPoint annotation = method.getAnnotation(DataPoint.class);
+ Assume.assumeTrue(annotation == null || !isAssignableToAnyOf(annotation.ignoredExceptions(), throwable));
+
+ throw new CouldNotGenerateValueException(throwable);
+ }
+ }
+
+ @Override
+ public String getDescription() throws CouldNotGenerateValueException {
+ return method.getName();
+ }
+ }
+
+ private final TestClass clazz;
+
+ /**
+ * Constructs a new supplier for {@code type}
+ */
+ public AllMembersSupplier(TestClass type) {
+ clazz = type;
+ }
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable {
+ List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
+
+ addSinglePointFields(sig, list);
+ addMultiPointFields(sig, list);
+ addSinglePointMethods(sig, list);
+ addMultiPointMethods(sig, list);
+
+ return list;
+ }
+
+ private void addMultiPointMethods(ParameterSignature sig, List<PotentialAssignment> list) throws Throwable {
+ for (FrameworkMethod dataPointsMethod : getDataPointsMethods(sig)) {
+ Class<?> returnType = dataPointsMethod.getReturnType();
+
+ if ((returnType.isArray() && sig.canPotentiallyAcceptType(returnType.getComponentType())) ||
+ Iterable.class.isAssignableFrom(returnType)) {
+ try {
+ addDataPointsValues(returnType, sig, dataPointsMethod.getName(), list,
+ dataPointsMethod.invokeExplosively(null));
+ } catch (Throwable throwable) {
+ DataPoints annotation = dataPointsMethod.getAnnotation(DataPoints.class);
+ if (annotation != null && isAssignableToAnyOf(annotation.ignoredExceptions(), throwable)) {
+ return;
+ } else {
+ throw throwable;
+ }
+ }
+ }
+ }
+ }
+
+ private void addSinglePointMethods(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (FrameworkMethod dataPointMethod : getSingleDataPointMethods(sig)) {
+ if (sig.canAcceptType(dataPointMethod.getType())) {
+ list.add(new MethodParameterValue(dataPointMethod));
+ }
+ }
+ }
+
+ private void addMultiPointFields(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (final Field field : getDataPointsFields(sig)) {
+ Class<?> type = field.getType();
+ addDataPointsValues(type, sig, field.getName(), list, getStaticFieldValue(field));
+ }
+ }
+
+ private void addSinglePointFields(ParameterSignature sig, List<PotentialAssignment> list) {
+ for (final Field field : getSingleDataPointFields(sig)) {
+ Object value = getStaticFieldValue(field);
+
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(field.getName(), value));
+ }
+ }
+ }
+
+ private void addDataPointsValues(Class<?> type, ParameterSignature sig, String name,
+ List<PotentialAssignment> list, Object value) {
+ if (type.isArray()) {
+ addArrayValues(sig, name, list, value);
+ }
+ else if (Iterable.class.isAssignableFrom(type)) {
+ addIterableValues(sig, name, list, (Iterable<?>) value);
+ }
+ }
+
+ private void addArrayValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Object array) {
+ for (int i = 0; i < Array.getLength(array); i++) {
+ Object value = Array.get(array, i);
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(name + "[" + i + "]", value));
+ }
+ }
+ }
+
+ private void addIterableValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Iterable<?> iterable) {
+ Iterator<?> iterator = iterable.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Object value = iterator.next();
+ if (sig.canAcceptValue(value)) {
+ list.add(PotentialAssignment.forValue(name + "[" + i + "]", value));
+ }
+ i += 1;
+ }
+ }
+
+ private Object getStaticFieldValue(final Field field) {
+ try {
+ return field.get(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: field from getClass doesn't exist on object");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getFields returned an inaccessible field");
+ }
+ }
+
+ private static boolean isAssignableToAnyOf(Class<?>[] typeArray, Object target) {
+ for (Class<?> type : typeArray) {
+ if (type.isAssignableFrom(target.getClass())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) {
+ return clazz.getAnnotatedMethods(DataPoints.class);
+ }
+
+ protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) {
+ List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoint.class);
+ Collection<Field> validFields = new ArrayList<Field>();
+
+ for (FrameworkField frameworkField : fields) {
+ validFields.add(frameworkField.getField());
+ }
+
+ return validFields;
+ }
+
+ protected Collection<Field> getDataPointsFields(ParameterSignature sig) {
+ List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoints.class);
+ Collection<Field> validFields = new ArrayList<Field>();
+
+ for (FrameworkField frameworkField : fields) {
+ validFields.add(frameworkField.getField());
+ }
+
+ return validFields;
+ }
+
+ protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) {
+ return clazz.getAnnotatedMethods(DataPoint.class);
+ }
+
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/Assignments.java b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
index bd94f00..a94c8a5 100644
--- a/src/main/java/org/junit/experimental/theories/internal/Assignments.java
+++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
@@ -1,8 +1,8 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
+import static java.util.Collections.emptyList;
+
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -19,115 +19,136 @@ import org.junit.runners.model.TestClass;
* parameters
*/
public class Assignments {
- private List<PotentialAssignment> fAssigned;
-
- private final List<ParameterSignature> fUnassigned;
-
- private final TestClass fClass;
-
- private Assignments(List<PotentialAssignment> assigned,
- List<ParameterSignature> unassigned, TestClass testClass) {
- fUnassigned= unassigned;
- fAssigned= assigned;
- fClass= testClass;
- }
-
- /**
- * Returns a new assignment list for {@code testMethod}, with no params
- * assigned.
- */
- public static Assignments allUnassigned(Method testMethod,
- TestClass testClass) throws Exception {
- List<ParameterSignature> signatures;
- signatures= ParameterSignature.signatures(testClass
- .getOnlyConstructor());
- signatures.addAll(ParameterSignature.signatures(testMethod));
- return new Assignments(new ArrayList<PotentialAssignment>(),
- signatures, testClass);
- }
-
- public boolean isComplete() {
- return fUnassigned.size() == 0;
- }
-
- public ParameterSignature nextUnassigned() {
- return fUnassigned.get(0);
- }
-
- public Assignments assignNext(PotentialAssignment source) {
- List<PotentialAssignment> assigned= new ArrayList<PotentialAssignment>(
- fAssigned);
- assigned.add(source);
-
- return new Assignments(assigned, fUnassigned.subList(1, fUnassigned
- .size()), fClass);
- }
-
- public Object[] getActualValues(int start, int stop, boolean nullsOk)
- throws CouldNotGenerateValueException {
- Object[] values= new Object[stop - start];
- for (int i= start; i < stop; i++) {
- Object value= fAssigned.get(i).getValue();
- if (value == null && !nullsOk)
- throw new CouldNotGenerateValueException();
- values[i - start]= value;
- }
- return values;
- }
-
- public List<PotentialAssignment> potentialsForNextUnassigned()
- throws InstantiationException, IllegalAccessException {
- ParameterSignature unassigned= nextUnassigned();
- return getSupplier(unassigned).getValueSources(unassigned);
- }
-
- public ParameterSupplier getSupplier(ParameterSignature unassigned)
- throws InstantiationException, IllegalAccessException {
- ParameterSupplier supplier= getAnnotatedSupplier(unassigned);
- if (supplier != null)
- return supplier;
-
- return new AllMembersSupplier(fClass);
- }
-
- public ParameterSupplier getAnnotatedSupplier(ParameterSignature unassigned)
- throws InstantiationException, IllegalAccessException {
- ParametersSuppliedBy annotation= unassigned
- .findDeepAnnotation(ParametersSuppliedBy.class);
- if (annotation == null)
- return null;
- return annotation.value().newInstance();
- }
-
- public Object[] getConstructorArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(0, getConstructorParameterCount(), nullsOk);
- }
-
- public Object[] getMethodArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(getConstructorParameterCount(),
- fAssigned.size(), nullsOk);
- }
-
- public Object[] getAllArguments(boolean nullsOk)
- throws CouldNotGenerateValueException {
- return getActualValues(0, fAssigned.size(), nullsOk);
- }
-
- private int getConstructorParameterCount() {
- List<ParameterSignature> signatures= ParameterSignature
- .signatures(fClass.getOnlyConstructor());
- int constructorParameterCount= signatures.size();
- return constructorParameterCount;
- }
-
- public Object[] getArgumentStrings(boolean nullsOk)
- throws CouldNotGenerateValueException {
- Object[] values= new Object[fAssigned.size()];
- for (int i= 0; i < values.length; i++) {
- values[i]= fAssigned.get(i).getDescription();
- }
- return values;
- }
+ private final List<PotentialAssignment> assigned;
+
+ private final List<ParameterSignature> unassigned;
+
+ private final TestClass clazz;
+
+ private Assignments(List<PotentialAssignment> assigned,
+ List<ParameterSignature> unassigned, TestClass clazz) {
+ this.unassigned = unassigned;
+ this.assigned = assigned;
+ this.clazz = clazz;
+ }
+
+ /**
+ * Returns a new assignment list for {@code testMethod}, with no params
+ * assigned.
+ */
+ public static Assignments allUnassigned(Method testMethod,
+ TestClass testClass) {
+ List<ParameterSignature> signatures;
+ signatures = ParameterSignature.signatures(testClass
+ .getOnlyConstructor());
+ signatures.addAll(ParameterSignature.signatures(testMethod));
+ return new Assignments(new ArrayList<PotentialAssignment>(),
+ signatures, testClass);
+ }
+
+ public boolean isComplete() {
+ return unassigned.size() == 0;
+ }
+
+ public ParameterSignature nextUnassigned() {
+ return unassigned.get(0);
+ }
+
+ public Assignments assignNext(PotentialAssignment source) {
+ List<PotentialAssignment> assigned = new ArrayList<PotentialAssignment>(
+ this.assigned);
+ assigned.add(source);
+
+ return new Assignments(assigned, unassigned.subList(1,
+ unassigned.size()), clazz);
+ }
+
+ public Object[] getActualValues(int start, int stop)
+ throws CouldNotGenerateValueException {
+ Object[] values = new Object[stop - start];
+ for (int i = start; i < stop; i++) {
+ values[i - start] = assigned.get(i).getValue();
+ }
+ return values;
+ }
+
+ public List<PotentialAssignment> potentialsForNextUnassigned()
+ throws Throwable {
+ ParameterSignature unassigned = nextUnassigned();
+ List<PotentialAssignment> assignments = getSupplier(unassigned).getValueSources(unassigned);
+
+ if (assignments.size() == 0) {
+ assignments = generateAssignmentsFromTypeAlone(unassigned);
+ }
+
+ return assignments;
+ }
+
+ private List<PotentialAssignment> generateAssignmentsFromTypeAlone(ParameterSignature unassigned) {
+ Class<?> paramType = unassigned.getType();
+
+ if (paramType.isEnum()) {
+ return new EnumSupplier(paramType).getValueSources(unassigned);
+ } else if (paramType.equals(Boolean.class) || paramType.equals(boolean.class)) {
+ return new BooleanSupplier().getValueSources(unassigned);
+ } else {
+ return emptyList();
+ }
+ }
+
+ private ParameterSupplier getSupplier(ParameterSignature unassigned)
+ throws Exception {
+ ParametersSuppliedBy annotation = unassigned
+ .findDeepAnnotation(ParametersSuppliedBy.class);
+
+ if (annotation != null) {
+ return buildParameterSupplierFromClass(annotation.value());
+ } else {
+ return new AllMembersSupplier(clazz);
+ }
+ }
+
+ private ParameterSupplier buildParameterSupplierFromClass(
+ Class<? extends ParameterSupplier> cls) throws Exception {
+ Constructor<?>[] supplierConstructors = cls.getConstructors();
+
+ for (Constructor<?> constructor : supplierConstructors) {
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ if (parameterTypes.length == 1
+ && parameterTypes[0].equals(TestClass.class)) {
+ return (ParameterSupplier) constructor.newInstance(clazz);
+ }
+ }
+
+ return cls.newInstance();
+ }
+
+ public Object[] getConstructorArguments()
+ throws CouldNotGenerateValueException {
+ return getActualValues(0, getConstructorParameterCount());
+ }
+
+ public Object[] getMethodArguments() throws CouldNotGenerateValueException {
+ return getActualValues(getConstructorParameterCount(), assigned.size());
+ }
+
+ public Object[] getAllArguments() throws CouldNotGenerateValueException {
+ return getActualValues(0, assigned.size());
+ }
+
+ private int getConstructorParameterCount() {
+ List<ParameterSignature> signatures = ParameterSignature
+ .signatures(clazz.getOnlyConstructor());
+ int constructorParameterCount = signatures.size();
+ return constructorParameterCount;
+ }
+
+ public Object[] getArgumentStrings(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ Object[] values = new Object[assigned.size()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = assigned.get(i).getDescription();
+ }
+ return values;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java
new file mode 100644
index 0000000..5f7032f
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java
@@ -0,0 +1,18 @@
+package org.junit.experimental.theories.internal;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+
+public class BooleanSupplier extends ParameterSupplier {
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ return Arrays.asList(PotentialAssignment.forValue("true", true),
+ PotentialAssignment.forValue("false", false));
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java
new file mode 100644
index 0000000..1f3ab90
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java
@@ -0,0 +1,30 @@
+package org.junit.experimental.theories.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+
+public class EnumSupplier extends ParameterSupplier {
+
+ private Class<?> enumType;
+
+ public EnumSupplier(Class<?> enumType) {
+ this.enumType = enumType;
+ }
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ Object[] enumValues = enumType.getEnumConstants();
+
+ List<PotentialAssignment> assignments = new ArrayList<PotentialAssignment>();
+ for (Object value : enumValues) {
+ assignments.add(PotentialAssignment.forValue(value.toString(), value));
+ }
+
+ return assignments;
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
index 285bc3a..5b9e947 100644
--- a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
+++ b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
@@ -1,49 +1,50 @@
-/**
- *
- */
package org.junit.experimental.theories.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
-
-public class ParameterizedAssertionError extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public ParameterizedAssertionError(Throwable targetException,
- String methodName, Object... params) {
- super(String.format("%s(%s)", methodName, join(", ", params)),
- targetException);
- }
-
- @Override public boolean equals(Object obj) {
- return toString().equals(obj.toString());
- }
-
- public static String join(String delimiter, Object... params) {
- return join(delimiter, Arrays.asList(params));
- }
-
- public static String join(String delimiter,
- Collection<Object> values) {
- StringBuffer buffer = new StringBuffer();
- Iterator<Object> iter = values.iterator();
- while (iter.hasNext()) {
- Object next = iter.next();
- buffer.append(stringValueOf(next));
- if (iter.hasNext()) {
- buffer.append(delimiter);
- }
- }
- return buffer.toString();
- }
-
- private static String stringValueOf(Object next) {
- try {
- return String.valueOf(next);
- } catch (Throwable e) {
- return "[toString failed]";
- }
- }
+public class ParameterizedAssertionError extends AssertionError {
+ private static final long serialVersionUID = 1L;
+
+ public ParameterizedAssertionError(Throwable targetException,
+ String methodName, Object... params) {
+ super(String.format("%s(%s)", methodName, join(", ", params)));
+ this.initCause(targetException);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ParameterizedAssertionError && toString().equals(obj.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ public static String join(String delimiter, Object... params) {
+ return join(delimiter, Arrays.asList(params));
+ }
+
+ public static String join(String delimiter, Collection<Object> values) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<Object> iter = values.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ sb.append(stringValueOf(next));
+ if (iter.hasNext()) {
+ sb.append(delimiter);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String stringValueOf(Object next) {
+ try {
+ return String.valueOf(next);
+ } catch (Throwable e) {
+ return "[toString failed]";
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java
new file mode 100644
index 0000000..7b571e3
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java
@@ -0,0 +1,90 @@
+package org.junit.experimental.theories.internal;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+public class SpecificDataPointsSupplier extends AllMembersSupplier {
+
+ public SpecificDataPointsSupplier(TestClass testClass) {
+ super(testClass);
+ }
+
+ @Override
+ protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) {
+ Collection<Field> fields = super.getSingleDataPointFields(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<Field> fieldsWithMatchingNames = new ArrayList<Field>();
+
+ for (Field field : fields) {
+ String[] fieldNames = field.getAnnotation(DataPoint.class).value();
+ if (Arrays.asList(fieldNames).contains(requestedName)) {
+ fieldsWithMatchingNames.add(field);
+ }
+ }
+
+ return fieldsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<Field> getDataPointsFields(ParameterSignature sig) {
+ Collection<Field> fields = super.getDataPointsFields(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<Field> fieldsWithMatchingNames = new ArrayList<Field>();
+
+ for (Field field : fields) {
+ String[] fieldNames = field.getAnnotation(DataPoints.class).value();
+ if (Arrays.asList(fieldNames).contains(requestedName)) {
+ fieldsWithMatchingNames.add(field);
+ }
+ }
+
+ return fieldsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) {
+ Collection<FrameworkMethod> methods = super.getSingleDataPointMethods(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>();
+
+ for (FrameworkMethod method : methods) {
+ String[] methodNames = method.getAnnotation(DataPoint.class).value();
+ if (Arrays.asList(methodNames).contains(requestedName)) {
+ methodsWithMatchingNames.add(method);
+ }
+ }
+
+ return methodsWithMatchingNames;
+ }
+
+ @Override
+ protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) {
+ Collection<FrameworkMethod> methods = super.getDataPointsMethods(sig);
+ String requestedName = sig.getAnnotation(FromDataPoints.class).value();
+
+ List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>();
+
+ for (FrameworkMethod method : methods) {
+ String[] methodNames = method.getAnnotation(DataPoints.class).value();
+ if (Arrays.asList(methodNames).contains(requestedName)) {
+ methodsWithMatchingNames.add(method);
+ }
+ }
+
+ return methodsWithMatchingNames;
+ }
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
index d6ede64..a19f20a 100644
--- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
@@ -1,13 +1,31 @@
package org.junit.experimental.theories.suppliers;
+import static java.lang.annotation.ElementType.PARAMETER;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import org.junit.experimental.theories.ParametersSuppliedBy;
-
+/**
+ * Annotating a {@link org.junit.experimental.theories.Theory Theory} method int
+ * parameter with &#064;TestedOn causes it to be supplied with values from the
+ * ints array given when run as a theory by the
+ * {@link org.junit.experimental.theories.Theories Theories} runner. For
+ * example, the below method would be called three times by the Theories runner,
+ * once with each of the int parameters specified.
+ *
+ * <pre>
+ * &#064;Theory
+ * public void shouldPassForSomeInts(&#064;TestedOn(ints={1, 2, 3}) int param) {
+ * ...
+ * }
+ * </pre>
+ */
@ParametersSuppliedBy(TestedOnSupplier.class)
@Retention(RetentionPolicy.RUNTIME)
+@Target(PARAMETER)
public @interface TestedOn {
- int[] ints();
+ int[] ints();
}
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
index f80298f..dc3d0c9 100644
--- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
@@ -1,23 +1,25 @@
package org.junit.experimental.theories.suppliers;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import org.junit.experimental.theories.ParameterSignature;
import org.junit.experimental.theories.ParameterSupplier;
import org.junit.experimental.theories.PotentialAssignment;
-
-
+/**
+ * @see org.junit.experimental.theories.suppliers.TestedOn
+ * @see org.junit.experimental.theories.ParameterSupplier
+ */
public class TestedOnSupplier extends ParameterSupplier {
- @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
- List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
- TestedOn testedOn = sig.getAnnotation(TestedOn.class);
- int[] ints = testedOn.ints();
- for (final int i : ints) {
- list.add(PotentialAssignment.forValue(Arrays.asList(ints).toString(), i));
- }
- return list;
- }
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
+ TestedOn testedOn = sig.getAnnotation(TestedOn.class);
+ int[] ints = testedOn.ints();
+ for (final int i : ints) {
+ list.add(PotentialAssignment.forValue("ints", i));
+ }
+ return list;
+ }
}
diff --git a/src/main/java/org/junit/internal/ArrayComparisonFailure.java b/src/main/java/org/junit/internal/ArrayComparisonFailure.java
index 08851de..8627d6e 100644
--- a/src/main/java/org/junit/internal/ArrayComparisonFailure.java
+++ b/src/main/java/org/junit/internal/ArrayComparisonFailure.java
@@ -7,53 +7,61 @@ import org.junit.Assert;
/**
* Thrown when two array elements differ
+ *
* @see Assert#assertArrayEquals(String, Object[], Object[])
*/
public class ArrayComparisonFailure extends AssertionError {
- private static final long serialVersionUID= 1L;
-
- private List<Integer> fIndices= new ArrayList<Integer>();
- private final String fMessage;
- private final AssertionError fCause;
-
- /**
- * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's
- * dimension that was not equal
- * @param cause the exception that caused the array's content to fail the assertion test
- * @param index the array position of the objects that are not equal.
- * @see Assert#assertArrayEquals(String, Object[], Object[])
- */
- public ArrayComparisonFailure(String message, AssertionError cause, int index) {
- fMessage= message;
- fCause= cause;
- addDimension(index);
- }
-
- public void addDimension(int index) {
- fIndices.add(0, index);
- }
-
- @Override
- public String getMessage() {
- StringBuilder builder= new StringBuilder();
- if (fMessage != null)
- builder.append(fMessage);
- builder.append("arrays first differed at element ");
- for (int each : fIndices) {
- builder.append("[");
- builder.append(each);
- builder.append("]");
- }
- builder.append("; ");
- builder.append(fCause.getMessage());
- return builder.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override public String toString() {
- return getMessage();
- }
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final List<Integer> fIndices = new ArrayList<Integer>();
+ private final String fMessage;
+
+ /**
+ * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's
+ * dimension that was not equal
+ *
+ * @param cause the exception that caused the array's content to fail the assertion test
+ * @param index the array position of the objects that are not equal.
+ * @see Assert#assertArrayEquals(String, Object[], Object[])
+ */
+ public ArrayComparisonFailure(String message, AssertionError cause, int index) {
+ this.fMessage = message;
+ initCause(cause);
+ addDimension(index);
+ }
+
+ public void addDimension(int index) {
+ fIndices.add(0, index);
+ }
+
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+ if (fMessage != null) {
+ sb.append(fMessage);
+ }
+ sb.append("arrays first differed at element ");
+ for (int each : fIndices) {
+ sb.append("[");
+ sb.append(each);
+ sb.append("]");
+ }
+ sb.append("; ");
+ sb.append(getCause().getMessage());
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return getMessage();
+ }
}
diff --git a/src/main/java/org/junit/internal/AssumptionViolatedException.java b/src/main/java/org/junit/internal/AssumptionViolatedException.java
index 8e11268..880d73f 100644
--- a/src/main/java/org/junit/internal/AssumptionViolatedException.java
+++ b/src/main/java/org/junit/internal/AssumptionViolatedException.java
@@ -5,36 +5,107 @@ import org.hamcrest.Matcher;
import org.hamcrest.SelfDescribing;
import org.hamcrest.StringDescription;
+/**
+ * An exception class used to implement <i>assumptions</i> (state in which a given test
+ * is meaningful and should or should not be executed). A test for which an assumption
+ * fails should not generate a test case failure.
+ *
+ * @see org.junit.Assume
+ */
public class AssumptionViolatedException extends RuntimeException implements SelfDescribing {
- private static final long serialVersionUID= 1L;
-
- private final Object fValue;
-
- private final Matcher<?> fMatcher;
-
- public AssumptionViolatedException(Object value, Matcher<?> matcher) {
- super(value instanceof Throwable ? (Throwable) value : null);
- fValue= value;
- fMatcher= matcher;
- }
-
- public AssumptionViolatedException(String assumption) {
- this(assumption, null);
- }
-
- @Override
- public String getMessage() {
- return StringDescription.asString(this);
- }
-
- public void describeTo(Description description) {
- if (fMatcher != null) {
- description.appendText("got: ");
- description.appendValue(fValue);
- description.appendText(", expected: ");
- description.appendDescriptionOf(fMatcher);
- } else {
- description.appendText("failed assumption: " + fValue);
- }
- }
-} \ No newline at end of file
+ private static final long serialVersionUID = 2L;
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final String fAssumption;
+ private final boolean fValueMatcher;
+ private final Object fValue;
+ private final Matcher<?> fMatcher;
+
+ /**
+ * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead.
+ */
+ @Deprecated
+ public AssumptionViolatedException(String assumption, boolean hasValue, Object value, Matcher<?> matcher) {
+ this.fAssumption = assumption;
+ this.fValue = value;
+ this.fMatcher = matcher;
+ this.fValueMatcher = hasValue;
+
+ if (value instanceof Throwable) {
+ initCause((Throwable) value);
+ }
+ }
+
+ /**
+ * An assumption exception with the given <i>value</i> (String or
+ * Throwable) and an additional failing {@link Matcher}.
+ *
+ * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead.
+ */
+ @Deprecated
+ public AssumptionViolatedException(Object value, Matcher<?> matcher) {
+ this(null, true, value, matcher);
+ }
+
+ /**
+ * An assumption exception with the given <i>value</i> (String or
+ * Throwable) and an additional failing {@link Matcher}.
+ *
+ * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead.
+ */
+ @Deprecated
+ public AssumptionViolatedException(String assumption, Object value, Matcher<?> matcher) {
+ this(assumption, true, value, matcher);
+ }
+
+ /**
+ * An assumption exception with the given message only.
+ *
+ * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead.
+ */
+ @Deprecated
+ public AssumptionViolatedException(String assumption) {
+ this(assumption, false, null, null);
+ }
+
+ /**
+ * An assumption exception with the given message and a cause.
+ *
+ * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead.
+ */
+ @Deprecated
+ public AssumptionViolatedException(String assumption, Throwable e) {
+ this(assumption, false, null, null);
+ initCause(e);
+ }
+
+ @Override
+ public String getMessage() {
+ return StringDescription.asString(this);
+ }
+
+ public void describeTo(Description description) {
+ if (fAssumption != null) {
+ description.appendText(fAssumption);
+ }
+
+ if (fValueMatcher) {
+ // a value was passed in when this instance was constructed; print it
+ if (fAssumption != null) {
+ description.appendText(": ");
+ }
+
+ description.appendText("got: ");
+ description.appendValue(fValue);
+
+ if (fMatcher != null) {
+ description.appendText(", expected: ");
+ description.appendDescriptionOf(fMatcher);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/junit/internal/Classes.java b/src/main/java/org/junit/internal/Classes.java
new file mode 100644
index 0000000..154603d
--- /dev/null
+++ b/src/main/java/org/junit/internal/Classes.java
@@ -0,0 +1,18 @@
+package org.junit.internal;
+
+import static java.lang.Thread.currentThread;
+
+/**
+ * Miscellaneous functions dealing with classes.
+ */
+public class Classes {
+ /**
+ * Returns Class.forName for {@code className} using the current thread's class loader.
+ *
+ * @param className Name of the class.
+ * @throws ClassNotFoundException
+ */
+ public static Class<?> getClass(String className) throws ClassNotFoundException {
+ return Class.forName(className, true, currentThread().getContextClassLoader());
+ }
+}
diff --git a/src/main/java/org/junit/internal/ComparisonCriteria.java b/src/main/java/org/junit/internal/ComparisonCriteria.java
index e97011d..e6d49a4 100644
--- a/src/main/java/org/junit/internal/ComparisonCriteria.java
+++ b/src/main/java/org/junit/internal/ComparisonCriteria.java
@@ -1,6 +1,7 @@
package org.junit.internal;
import java.lang.reflect.Array;
+import java.util.Arrays;
import org.junit.Assert;
@@ -9,68 +10,74 @@ import org.junit.Assert;
* may demand exact equality, or, for example, equality within a given delta.
*/
public abstract class ComparisonCriteria {
- /**
- * Asserts that two arrays are equal, according to the criteria defined by
- * the concrete subclass. If they are not, an {@link AssertionError} is
- * thrown with the given message. If <code>expecteds</code> and
- * <code>actuals</code> are <code>null</code>, they are considered equal.
- *
- * @param message
- * the identifying message for the {@link AssertionError} (
- * <code>null</code> okay)
- * @param expecteds
- * Object array or array of arrays (multi-dimensional array) with
- * expected values.
- * @param actuals
- * Object array or array of arrays (multi-dimensional array) with
- * actual values
- */
- public void arrayEquals(String message, Object expecteds, Object actuals)
- throws ArrayComparisonFailure {
- if (expecteds == actuals)
- return;
- String header= message == null ? "" : message + ": ";
+ /**
+ * Asserts that two arrays are equal, according to the criteria defined by
+ * the concrete subclass. If they are not, an {@link AssertionError} is
+ * thrown with the given message. If <code>expecteds</code> and
+ * <code>actuals</code> are <code>null</code>, they are considered equal.
+ *
+ * @param message the identifying message for the {@link AssertionError} (
+ * <code>null</code> okay)
+ * @param expecteds Object array or array of arrays (multi-dimensional array) with
+ * expected values.
+ * @param actuals Object array or array of arrays (multi-dimensional array) with
+ * actual values
+ */
+ public void arrayEquals(String message, Object expecteds, Object actuals)
+ throws ArrayComparisonFailure {
+ if (expecteds == actuals
+ || Arrays.deepEquals(new Object[] {expecteds}, new Object[] {actuals})) {
+ // The reflection-based loop below is potentially very slow, especially for primitive
+ // arrays. The deepEquals check allows us to circumvent it in the usual case where
+ // the arrays are exactly equal.
+ return;
+ }
+ String header = message == null ? "" : message + ": ";
- int expectedsLength= assertArraysAreSameLength(expecteds,
- actuals, header);
+ int expectedsLength = assertArraysAreSameLength(expecteds,
+ actuals, header);
- for (int i= 0; i < expectedsLength; i++) {
- Object expected= Array.get(expecteds, i);
- Object actual= Array.get(actuals, i);
+ for (int i = 0; i < expectedsLength; i++) {
+ Object expected = Array.get(expecteds, i);
+ Object actual = Array.get(actuals, i);
- if (isArray(expected) && isArray(actual)) {
- try {
- arrayEquals(message, expected, actual);
- } catch (ArrayComparisonFailure e) {
- e.addDimension(i);
- throw e;
- }
- } else
- try {
- assertElementsEqual(expected, actual);
- } catch (AssertionError e) {
- throw new ArrayComparisonFailure(header, e, i);
- }
- }
- }
+ if (isArray(expected) && isArray(actual)) {
+ try {
+ arrayEquals(message, expected, actual);
+ } catch (ArrayComparisonFailure e) {
+ e.addDimension(i);
+ throw e;
+ }
+ } else {
+ try {
+ assertElementsEqual(expected, actual);
+ } catch (AssertionError e) {
+ throw new ArrayComparisonFailure(header, e, i);
+ }
+ }
+ }
+ }
- private boolean isArray(Object expected) {
- return expected != null && expected.getClass().isArray();
- }
+ private boolean isArray(Object expected) {
+ return expected != null && expected.getClass().isArray();
+ }
- private int assertArraysAreSameLength(Object expecteds,
- Object actuals, String header) {
- if (expecteds == null)
- Assert.fail(header + "expected array was null");
- if (actuals == null)
- Assert.fail(header + "actual array was null");
- int actualsLength= Array.getLength(actuals);
- int expectedsLength= Array.getLength(expecteds);
- if (actualsLength != expectedsLength)
- Assert.fail(header + "array lengths differed, expected.length="
- + expectedsLength + " actual.length=" + actualsLength);
- return expectedsLength;
- }
+ private int assertArraysAreSameLength(Object expecteds,
+ Object actuals, String header) {
+ if (expecteds == null) {
+ Assert.fail(header + "expected array was null");
+ }
+ if (actuals == null) {
+ Assert.fail(header + "actual array was null");
+ }
+ int actualsLength = Array.getLength(actuals);
+ int expectedsLength = Array.getLength(expecteds);
+ if (actualsLength != expectedsLength) {
+ Assert.fail(header + "array lengths differed, expected.length="
+ + expectedsLength + " actual.length=" + actualsLength);
+ }
+ return expectedsLength;
+ }
- protected abstract void assertElementsEqual(Object expected, Object actual);
+ protected abstract void assertElementsEqual(Object expected, Object actual);
}
diff --git a/src/main/java/org/junit/internal/ExactComparisonCriteria.java b/src/main/java/org/junit/internal/ExactComparisonCriteria.java
index 0a632ff..a267f7f 100644
--- a/src/main/java/org/junit/internal/ExactComparisonCriteria.java
+++ b/src/main/java/org/junit/internal/ExactComparisonCriteria.java
@@ -3,8 +3,8 @@ package org.junit.internal;
import org.junit.Assert;
public class ExactComparisonCriteria extends ComparisonCriteria {
- @Override
- protected void assertElementsEqual(Object expected, Object actual) {
- Assert.assertEquals(expected, actual);
- }
+ @Override
+ protected void assertElementsEqual(Object expected, Object actual) {
+ Assert.assertEquals(expected, actual);
+ }
}
diff --git a/src/main/java/org/junit/internal/InexactComparisonCriteria.java b/src/main/java/org/junit/internal/InexactComparisonCriteria.java
index ef3d7ff..16e804b 100644
--- a/src/main/java/org/junit/internal/InexactComparisonCriteria.java
+++ b/src/main/java/org/junit/internal/InexactComparisonCriteria.java
@@ -3,17 +3,22 @@ package org.junit.internal;
import org.junit.Assert;
public class InexactComparisonCriteria extends ComparisonCriteria {
- public double fDelta;
+ public Object fDelta;
- public InexactComparisonCriteria(double delta) {
- fDelta= delta;
- }
+ public InexactComparisonCriteria(double delta) {
+ fDelta = delta;
+ }
- @Override
- protected void assertElementsEqual(Object expected, Object actual) {
- if (expected instanceof Double)
- Assert.assertEquals((Double)expected, (Double)actual, fDelta);
- else
- Assert.assertEquals((Float)expected, (Float)actual, fDelta);
- }
+ public InexactComparisonCriteria(float delta) {
+ fDelta = delta;
+ }
+
+ @Override
+ protected void assertElementsEqual(Object expected, Object actual) {
+ if (expected instanceof Double) {
+ Assert.assertEquals((Double) expected, (Double) actual, (Double) fDelta);
+ } else {
+ Assert.assertEquals((Float) expected, (Float) actual, (Float) fDelta);
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/JUnitSystem.java b/src/main/java/org/junit/internal/JUnitSystem.java
index 6d9c242..cf0f2c0 100644
--- a/src/main/java/org/junit/internal/JUnitSystem.java
+++ b/src/main/java/org/junit/internal/JUnitSystem.java
@@ -3,6 +3,12 @@ package org.junit.internal;
import java.io.PrintStream;
public interface JUnitSystem {
- void exit(int i);
- PrintStream out();
+
+ /**
+ * Will be removed in the next major release
+ */
+ @Deprecated
+ void exit(int code);
+
+ PrintStream out();
}
diff --git a/src/main/java/org/junit/internal/MethodSorter.java b/src/main/java/org/junit/internal/MethodSorter.java
new file mode 100644
index 0000000..d8e661a
--- /dev/null
+++ b/src/main/java/org/junit/internal/MethodSorter.java
@@ -0,0 +1,72 @@
+package org.junit.internal;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.junit.FixMethodOrder;
+
+public class MethodSorter {
+ /**
+ * DEFAULT sort order
+ */
+ public static final Comparator<Method> DEFAULT = new Comparator<Method>() {
+ public int compare(Method m1, Method m2) {
+ int i1 = m1.getName().hashCode();
+ int i2 = m2.getName().hashCode();
+ if (i1 != i2) {
+ return i1 < i2 ? -1 : 1;
+ }
+ return NAME_ASCENDING.compare(m1, m2);
+ }
+ };
+
+ /**
+ * Method name ascending lexicographic sort order, with {@link Method#toString()} as a tiebreaker
+ */
+ public static final Comparator<Method> NAME_ASCENDING = new Comparator<Method>() {
+ public int compare(Method m1, Method m2) {
+ final int comparison = m1.getName().compareTo(m2.getName());
+ if (comparison != 0) {
+ return comparison;
+ }
+ return m1.toString().compareTo(m2.toString());
+ }
+ };
+
+ /**
+ * Gets declared methods of a class in a predictable order, unless @FixMethodOrder(MethodSorters.JVM) is specified.
+ *
+ * Using the JVM order is unwise since the Java platform does not
+ * specify any particular order, and in fact JDK 7 returns a more or less
+ * random order; well-written test code would not assume any order, but some
+ * does, and a predictable failure is better than a random failure on
+ * certain platforms. By default, uses an unspecified but deterministic order.
+ *
+ * @param clazz a class
+ * @return same as {@link Class#getDeclaredMethods} but sorted
+ * @see <a href="http://bugs.sun.com/view_bug.do?bug_id=7023180">JDK
+ * (non-)bug #7023180</a>
+ */
+ public static Method[] getDeclaredMethods(Class<?> clazz) {
+ Comparator<Method> comparator = getSorter(clazz.getAnnotation(FixMethodOrder.class));
+
+ Method[] methods = clazz.getDeclaredMethods();
+ if (comparator != null) {
+ Arrays.sort(methods, comparator);
+ }
+
+ return methods;
+ }
+
+ private MethodSorter() {
+ }
+
+ private static Comparator<Method> getSorter(FixMethodOrder fixMethodOrder) {
+ if (fixMethodOrder == null) {
+ return DEFAULT;
+ }
+
+ return fixMethodOrder.value().getComparator();
+ }
+}
diff --git a/src/main/java/org/junit/internal/RealSystem.java b/src/main/java/org/junit/internal/RealSystem.java
index 1067c6d..e64e1fe 100644
--- a/src/main/java/org/junit/internal/RealSystem.java
+++ b/src/main/java/org/junit/internal/RealSystem.java
@@ -4,12 +4,16 @@ import java.io.PrintStream;
public class RealSystem implements JUnitSystem {
- public void exit(int code) {
- System.exit(code);
- }
+ /**
+ * Will be removed in the next major release
+ */
+ @Deprecated
+ public void exit(int code) {
+ System.exit(code);
+ }
- public PrintStream out() {
- return System.out;
- }
+ public PrintStream out() {
+ return System.out;
+ }
}
diff --git a/src/main/java/org/junit/internal/TextListener.java b/src/main/java/org/junit/internal/TextListener.java
index 2b1c679..9aa56c7 100644
--- a/src/main/java/org/junit/internal/TextListener.java
+++ b/src/main/java/org/junit/internal/TextListener.java
@@ -11,88 +11,91 @@ import org.junit.runner.notification.RunListener;
public class TextListener extends RunListener {
- private final PrintStream fWriter;
-
- public TextListener(JUnitSystem system) {
- this(system.out());
- }
-
- public TextListener(PrintStream writer) {
- this.fWriter= writer;
- }
-
- @Override
- public void testRunFinished(Result result) {
- printHeader(result.getRunTime());
- printFailures(result);
- printFooter(result);
- }
-
- @Override
- public void testStarted(Description description) {
- fWriter.append('.');
- }
-
- @Override
- public void testFailure(Failure failure) {
- fWriter.append('E');
- }
-
- @Override
- public void testIgnored(Description description) {
- fWriter.append('I');
- }
-
- /*
- * Internal methods
- */
-
- private PrintStream getWriter() {
- return fWriter;
- }
-
- protected void printHeader(long runTime) {
- getWriter().println();
- getWriter().println("Time: " + elapsedTimeAsString(runTime));
- }
-
- protected void printFailures(Result result) {
- List<Failure> failures= result.getFailures();
- if (failures.size() == 0)
- return;
- if (failures.size() == 1)
- getWriter().println("There was " + failures.size() + " failure:");
- else
- getWriter().println("There were " + failures.size() + " failures:");
- int i= 1;
- for (Failure each : failures)
- printFailure(each, "" + i++);
- }
-
- protected void printFailure(Failure each, String prefix) {
- getWriter().println(prefix + ") " + each.getTestHeader());
- getWriter().print(each.getTrace());
- }
-
- protected void printFooter(Result result) {
- if (result.wasSuccessful()) {
- getWriter().println();
- getWriter().print("OK");
- getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")");
-
- } else {
- getWriter().println();
- getWriter().println("FAILURES!!!");
- getWriter().println("Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount());
- }
- getWriter().println();
- }
-
- /**
- * Returns the formatted string of the elapsed time. Duplicated from
- * BaseTestRunner. Fix it.
- */
- protected String elapsedTimeAsString(long runTime) {
- return NumberFormat.getInstance().format((double) runTime / 1000);
- }
+ private final PrintStream writer;
+
+ public TextListener(JUnitSystem system) {
+ this(system.out());
+ }
+
+ public TextListener(PrintStream writer) {
+ this.writer = writer;
+ }
+
+ @Override
+ public void testRunFinished(Result result) {
+ printHeader(result.getRunTime());
+ printFailures(result);
+ printFooter(result);
+ }
+
+ @Override
+ public void testStarted(Description description) {
+ writer.append('.');
+ }
+
+ @Override
+ public void testFailure(Failure failure) {
+ writer.append('E');
+ }
+
+ @Override
+ public void testIgnored(Description description) {
+ writer.append('I');
+ }
+
+ /*
+ * Internal methods
+ */
+
+ private PrintStream getWriter() {
+ return writer;
+ }
+
+ protected void printHeader(long runTime) {
+ getWriter().println();
+ getWriter().println("Time: " + elapsedTimeAsString(runTime));
+ }
+
+ protected void printFailures(Result result) {
+ List<Failure> failures = result.getFailures();
+ if (failures.size() == 0) {
+ return;
+ }
+ if (failures.size() == 1) {
+ getWriter().println("There was " + failures.size() + " failure:");
+ } else {
+ getWriter().println("There were " + failures.size() + " failures:");
+ }
+ int i = 1;
+ for (Failure each : failures) {
+ printFailure(each, "" + i++);
+ }
+ }
+
+ protected void printFailure(Failure each, String prefix) {
+ getWriter().println(prefix + ") " + each.getTestHeader());
+ getWriter().print(each.getTrace());
+ }
+
+ protected void printFooter(Result result) {
+ if (result.wasSuccessful()) {
+ getWriter().println();
+ getWriter().print("OK");
+ getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")");
+
+ } else {
+ getWriter().println();
+ getWriter().println("FAILURES!!!");
+ getWriter().println("Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount());
+ }
+ getWriter().println();
+ }
+
+ /**
+ * Returns the formatted string of the elapsed time. Duplicated from
+ * BaseTestRunner. Fix it.
+ */
+ protected String elapsedTimeAsString(long runTime) {
+ return NumberFormat.getInstance().format((double) runTime / 1000);
+ }
}
diff --git a/src/main/java/org/junit/internal/Throwables.java b/src/main/java/org/junit/internal/Throwables.java
new file mode 100644
index 0000000..86dceef
--- /dev/null
+++ b/src/main/java/org/junit/internal/Throwables.java
@@ -0,0 +1,42 @@
+package org.junit.internal;
+
+/**
+ * Miscellaneous functions dealing with {@code Throwable}.
+ *
+ * @author kcooney@google.com (Kevin Cooney)
+ * @since 4.12
+ */
+public final class Throwables {
+
+ private Throwables() {
+ }
+
+ /**
+ * Rethrows the given {@code Throwable}, allowing the caller to
+ * declare that it throws {@code Exception}. This is useful when
+ * your callers have nothing reasonable they can do when a
+ * {@code Throwable} is thrown. This is declared to return {@code Exception}
+ * so it can be used in a {@code throw} clause:
+ * <pre>
+ * try {
+ * doSomething();
+ * } catch (Throwable e} {
+ * throw Throwables.rethrowAsException(e);
+ * }
+ * doSomethingLater();
+ * </pre>
+ *
+ * @param e exception to rethrow
+ * @return does not return anything
+ * @since 4.12
+ */
+ public static Exception rethrowAsException(Throwable e) throws Exception {
+ Throwables.<Exception>rethrow(e);
+ return null; // we never get here
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> void rethrow(Throwable e) throws T {
+ throw (T) e;
+ }
+}
diff --git a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
index d3bd50a..d86ec95 100644
--- a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
+++ b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import java.util.Arrays;
@@ -10,48 +7,50 @@ import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class AllDefaultPossibilitiesBuilder extends RunnerBuilder {
- private final boolean fCanUseSuiteMethod;
-
- public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
- fCanUseSuiteMethod= canUseSuiteMethod;
- }
-
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- List<RunnerBuilder> builders= Arrays.asList(
- ignoredBuilder(),
- annotatedBuilder(),
- suiteMethodBuilder(),
- junit3Builder(),
- junit4Builder());
-
- for (RunnerBuilder each : builders) {
- Runner runner= each.safeRunnerForClass(testClass);
- if (runner != null)
- return runner;
- }
- return null;
- }
-
- protected JUnit4Builder junit4Builder() {
- return new JUnit4Builder();
- }
-
- protected JUnit3Builder junit3Builder() {
- return new JUnit3Builder();
- }
-
- protected AnnotatedBuilder annotatedBuilder() {
- return new AnnotatedBuilder(this);
- }
-
- protected IgnoredBuilder ignoredBuilder() {
- return new IgnoredBuilder();
- }
-
- protected RunnerBuilder suiteMethodBuilder() {
- if (fCanUseSuiteMethod)
- return new SuiteMethodBuilder();
- return new NullBuilder();
- }
+ private final boolean canUseSuiteMethod;
+
+ public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
+ this.canUseSuiteMethod = canUseSuiteMethod;
+ }
+
+ @Override
+ public Runner runnerForClass(Class<?> testClass) throws Throwable {
+ List<RunnerBuilder> builders = Arrays.asList(
+ ignoredBuilder(),
+ annotatedBuilder(),
+ suiteMethodBuilder(),
+ junit3Builder(),
+ junit4Builder());
+
+ for (RunnerBuilder each : builders) {
+ Runner runner = each.safeRunnerForClass(testClass);
+ if (runner != null) {
+ return runner;
+ }
+ }
+ return null;
+ }
+
+ protected JUnit4Builder junit4Builder() {
+ return new JUnit4Builder();
+ }
+
+ protected JUnit3Builder junit3Builder() {
+ return new JUnit3Builder();
+ }
+
+ protected AnnotatedBuilder annotatedBuilder() {
+ return new AnnotatedBuilder(this);
+ }
+
+ protected IgnoredBuilder ignoredBuilder() {
+ return new IgnoredBuilder();
+ }
+
+ protected RunnerBuilder suiteMethodBuilder() {
+ if (canUseSuiteMethod) {
+ return new SuiteMethodBuilder();
+ }
+ return new NullBuilder();
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java b/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java
index 8ed9ca7..04d7a68 100644
--- a/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java
+++ b/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.runner.RunWith;
@@ -8,38 +5,112 @@ import org.junit.runner.Runner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * The {@code AnnotatedBuilder} is a strategy for constructing runners for test class that have been annotated with the
+ * {@code @RunWith} annotation. All tests within this class will be executed using the runner that was specified within
+ * the annotation.
+ * <p>
+ * If a runner supports inner member classes, the member classes will inherit the runner from the enclosing class, e.g.:
+ * <pre>
+ * &#064;RunWith(MyRunner.class)
+ * public class MyTest {
+ * // some tests might go here
+ *
+ * public class MyMemberClass {
+ * &#064;Test
+ * public void thisTestRunsWith_MyRunner() {
+ * // some test logic
+ * }
+ *
+ * // some more tests might go here
+ * }
+ *
+ * &#064;RunWith(AnotherRunner.class)
+ * public class AnotherMemberClass {
+ * // some tests might go here
+ *
+ * public class DeepInnerClass {
+ * &#064;Test
+ * public void thisTestRunsWith_AnotherRunner() {
+ * // some test logic
+ * }
+ * }
+ *
+ * public class DeepInheritedClass extends SuperTest {
+ * &#064;Test
+ * public void thisTestRunsWith_SuperRunner() {
+ * // some test logic
+ * }
+ * }
+ * }
+ * }
+ *
+ * &#064;RunWith(SuperRunner.class)
+ * public class SuperTest {
+ * // some tests might go here
+ * }
+ * </pre>
+ * The key points to note here are:
+ * <ul>
+ * <li>If there is no RunWith annotation, no runner will be created.</li>
+ * <li>The resolve step is inside-out, e.g. the closest RunWith annotation wins</li>
+ * <li>RunWith annotations are inherited and work as if the class was annotated itself.</li>
+ * <li>The default JUnit runner does not support inner member classes,
+ * so this is only valid for custom runners that support inner member classes.</li>
+ * <li>Custom runners with support for inner classes may or may not support RunWith annotations for member
+ * classes. Please refer to the custom runner documentation.</li>
+ * </ul>
+ *
+ * @see org.junit.runners.model.RunnerBuilder
+ * @see org.junit.runner.RunWith
+ * @since 4.0
+ */
public class AnnotatedBuilder extends RunnerBuilder {
- private static final String CONSTRUCTOR_ERROR_FORMAT= "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
-
- private RunnerBuilder fSuiteBuilder;
-
- public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
- fSuiteBuilder= suiteBuilder;
- }
-
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Exception {
- RunWith annotation= testClass.getAnnotation(RunWith.class);
- if (annotation != null)
- return buildRunner(annotation.value(), testClass);
- return null;
- }
-
- public Runner buildRunner(Class<? extends Runner> runnerClass,
- Class<?> testClass) throws Exception {
- try {
- return runnerClass.getConstructor(Class.class).newInstance(
- new Object[] { testClass });
- } catch (NoSuchMethodException e) {
- try {
- return runnerClass.getConstructor(Class.class,
- RunnerBuilder.class).newInstance(
- new Object[] { testClass, fSuiteBuilder });
- } catch (NoSuchMethodException e2) {
- String simpleName= runnerClass.getSimpleName();
- throw new InitializationError(String.format(
- CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
- }
- }
- }
+ private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
+
+ private final RunnerBuilder suiteBuilder;
+
+ public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
+ this.suiteBuilder = suiteBuilder;
+ }
+
+ @Override
+ public Runner runnerForClass(Class<?> testClass) throws Exception {
+ for (Class<?> currentTestClass = testClass; currentTestClass != null;
+ currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
+ RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
+ if (annotation != null) {
+ return buildRunner(annotation.value(), testClass);
+ }
+ }
+
+ return null;
+ }
+
+ private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) {
+ if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) {
+ return currentTestClass.getEnclosingClass();
+ } else {
+ return null;
+ }
+ }
+
+ public Runner buildRunner(Class<? extends Runner> runnerClass,
+ Class<?> testClass) throws Exception {
+ try {
+ return runnerClass.getConstructor(Class.class).newInstance(testClass);
+ } catch (NoSuchMethodException e) {
+ try {
+ return runnerClass.getConstructor(Class.class,
+ RunnerBuilder.class).newInstance(testClass, suiteBuilder);
+ } catch (NoSuchMethodException e2) {
+ String simpleName = runnerClass.getSimpleName();
+ throw new InitializationError(String.format(
+ CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/IgnoredBuilder.java b/src/main/java/org/junit/internal/builders/IgnoredBuilder.java
index 6be342c..71940c8 100644
--- a/src/main/java/org/junit/internal/builders/IgnoredBuilder.java
+++ b/src/main/java/org/junit/internal/builders/IgnoredBuilder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.Ignore;
@@ -8,10 +5,11 @@ import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class IgnoredBuilder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> testClass) {
- if (testClass.getAnnotation(Ignore.class) != null)
- return new IgnoredClassRunner(testClass);
- return null;
- }
+ @Override
+ public Runner runnerForClass(Class<?> testClass) {
+ if (testClass.getAnnotation(Ignore.class) != null) {
+ return new IgnoredClassRunner(testClass);
+ }
+ return null;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java b/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java
index b4200a8..7c8926b 100644
--- a/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java
+++ b/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.runner.Description;
@@ -8,19 +5,19 @@ import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
public class IgnoredClassRunner extends Runner {
- private final Class<?> fTestClass;
+ private final Class<?> clazz;
- public IgnoredClassRunner(Class<?> testClass) {
- fTestClass= testClass;
- }
+ public IgnoredClassRunner(Class<?> testClass) {
+ clazz = testClass;
+ }
- @Override
- public void run(RunNotifier notifier) {
- notifier.fireTestIgnored(getDescription());
- }
+ @Override
+ public void run(RunNotifier notifier) {
+ notifier.fireTestIgnored(getDescription());
+ }
- @Override
- public Description getDescription() {
- return Description.createSuiteDescription(fTestClass);
- }
+ @Override
+ public Description getDescription() {
+ return Description.createSuiteDescription(clazz);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/JUnit3Builder.java b/src/main/java/org/junit/internal/builders/JUnit3Builder.java
index ddb070b..8b6b371 100644
--- a/src/main/java/org/junit/internal/builders/JUnit3Builder.java
+++ b/src/main/java/org/junit/internal/builders/JUnit3Builder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.internal.runners.JUnit38ClassRunner;
@@ -8,14 +5,15 @@ import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class JUnit3Builder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- if (isPre4Test(testClass))
- return new JUnit38ClassRunner(testClass);
- return null;
- }
+ @Override
+ public Runner runnerForClass(Class<?> testClass) throws Throwable {
+ if (isPre4Test(testClass)) {
+ return new JUnit38ClassRunner(testClass);
+ }
+ return null;
+ }
- boolean isPre4Test(Class<?> testClass) {
- return junit.framework.TestCase.class.isAssignableFrom(testClass);
- }
+ boolean isPre4Test(Class<?> testClass) {
+ return junit.framework.TestCase.class.isAssignableFrom(testClass);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/JUnit4Builder.java b/src/main/java/org/junit/internal/builders/JUnit4Builder.java
index 4380db7..6a00678 100644
--- a/src/main/java/org/junit/internal/builders/JUnit4Builder.java
+++ b/src/main/java/org/junit/internal/builders/JUnit4Builder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.runner.Runner;
@@ -8,8 +5,8 @@ import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.RunnerBuilder;
public class JUnit4Builder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- return new BlockJUnit4ClassRunner(testClass);
- }
+ @Override
+ public Runner runnerForClass(Class<?> testClass) throws Throwable {
+ return new BlockJUnit4ClassRunner(testClass);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/NullBuilder.java b/src/main/java/org/junit/internal/builders/NullBuilder.java
index 9d43d69..c8d306e 100644
--- a/src/main/java/org/junit/internal/builders/NullBuilder.java
+++ b/src/main/java/org/junit/internal/builders/NullBuilder.java
@@ -1,14 +1,11 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class NullBuilder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> each) throws Throwable {
- return null;
- }
+ @Override
+ public Runner runnerForClass(Class<?> each) throws Throwable {
+ return null;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java b/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java
index 659bf31..953e6cf 100644
--- a/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java
+++ b/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.builders;
import org.junit.internal.runners.SuiteMethod;
@@ -8,19 +5,20 @@ import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class SuiteMethodBuilder extends RunnerBuilder {
- @Override
- public Runner runnerForClass(Class<?> each) throws Throwable {
- if (hasSuiteMethod(each))
- return new SuiteMethod(each);
- return null;
- }
+ @Override
+ public Runner runnerForClass(Class<?> each) throws Throwable {
+ if (hasSuiteMethod(each)) {
+ return new SuiteMethod(each);
+ }
+ return null;
+ }
- public boolean hasSuiteMethod(Class<?> testClass) {
- try {
- testClass.getMethod("suite");
- } catch (NoSuchMethodException e) {
- return false;
- }
- return true;
- }
+ public boolean hasSuiteMethod(Class<?> testClass) {
+ try {
+ testClass.getMethod("suite");
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ return true;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java b/src/main/java/org/junit/internal/matchers/CombinableMatcher.java
deleted file mode 100644
index e9e6947..0000000
--- a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.anyOf;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-
-public class CombinableMatcher<T> extends BaseMatcher<T> {
-
- private final Matcher<? extends T> fMatcher;
-
- public CombinableMatcher(Matcher<? extends T> matcher) {
- fMatcher= matcher;
- }
-
- public boolean matches(Object item) {
- return fMatcher.matches(item);
- }
-
- public void describeTo(Description description) {
- description.appendDescriptionOf(fMatcher);
- }
-
- @SuppressWarnings("unchecked")
- public CombinableMatcher<T> and(Matcher<? extends T> matcher) {
- return new CombinableMatcher<T>(allOf(matcher, fMatcher));
- }
-
- @SuppressWarnings("unchecked")
- public CombinableMatcher<T> or(Matcher<? extends T> matcher) {
- return new CombinableMatcher<T>(anyOf(matcher, fMatcher));
- }
-} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/Each.java b/src/main/java/org/junit/internal/matchers/Each.java
deleted file mode 100644
index 527db3b..0000000
--- a/src/main/java/org/junit/internal/matchers/Each.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-
-public class Each {
- public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) {
- final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual)));
-
- return new BaseMatcher<Iterable<T>>() {
- public boolean matches(Object item) {
- return allItemsAre.matches(item);
- }
-
- public void describeTo(Description description) {
- description.appendText("each ");
- individual.describeTo(description);
- }
- };
- }
-}
diff --git a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java b/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java
deleted file mode 100644
index 4436a83..0000000
--- a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.core.AllOf.allOf;
-import static org.hamcrest.core.IsEqual.equalTo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.hamcrest.Description;
-import org.hamcrest.Factory;
-import org.hamcrest.Matcher;
-
-// Copied (hopefully temporarily) from hamcrest-library
-public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> {
- private final Matcher<? extends T> elementMatcher;
-
- public IsCollectionContaining(Matcher<? extends T> elementMatcher) {
- this.elementMatcher = elementMatcher;
- }
-
- @Override
- public boolean matchesSafely(Iterable<T> collection) {
- for (T item : collection) {
- if (elementMatcher.matches(item)){
- return true;
- }
- }
- return false;
- }
-
- public void describeTo(Description description) {
- description
- .appendText("a collection containing ")
- .appendDescriptionOf(elementMatcher);
- }
-
- @Factory
- public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) {
- return new IsCollectionContaining<T>(elementMatcher);
- }
-
- @Factory
- public static <T> Matcher<Iterable<T>> hasItem(T element) {
- return hasItem(equalTo(element));
- }
-
- @Factory
- public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T>... elementMatchers) {
- Collection<Matcher<? extends Iterable<T>>> all
- = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length);
- for (Matcher<? extends T> elementMatcher : elementMatchers) {
- all.add(hasItem(elementMatcher));
- }
- return allOf(all);
- }
-
- @Factory
- public static <T> Matcher<Iterable<T>> hasItems(T... elements) {
- Collection<Matcher<? extends Iterable<T>>> all
- = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length);
- for (T element : elements) {
- all.add(hasItem(element));
- }
- return allOf(all);
- }
-
-}
diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
new file mode 100644
index 0000000..5d45ba3
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
@@ -0,0 +1,56 @@
+package org.junit.internal.matchers;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+/**
+ * A matcher that delegates to throwableMatcher and in addition appends the
+ * stacktrace of the actual Throwable in case of a mismatch.
+ */
+public class StacktracePrintingMatcher<T extends Throwable> extends
+ org.hamcrest.TypeSafeMatcher<T> {
+
+ private final Matcher<T> throwableMatcher;
+
+ public StacktracePrintingMatcher(Matcher<T> throwableMatcher) {
+ this.throwableMatcher = throwableMatcher;
+ }
+
+ public void describeTo(Description description) {
+ throwableMatcher.describeTo(description);
+ }
+
+ @Override
+ protected boolean matchesSafely(T item) {
+ return throwableMatcher.matches(item);
+ }
+
+ @Override
+ protected void describeMismatchSafely(T item, Description description) {
+ throwableMatcher.describeMismatch(item, description);
+ description.appendText("\nStacktrace was: ");
+ description.appendText(readStacktrace(item));
+ }
+
+ private String readStacktrace(Throwable throwable) {
+ StringWriter stringWriter = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(stringWriter));
+ return stringWriter.toString();
+ }
+
+ @Factory
+ public static <T extends Throwable> Matcher<T> isThrowable(
+ Matcher<T> throwableMatcher) {
+ return new StacktracePrintingMatcher<T>(throwableMatcher);
+ }
+
+ @Factory
+ public static <T extends Exception> Matcher<T> isException(
+ Matcher<T> exceptionMatcher) {
+ return new StacktracePrintingMatcher<T>(exceptionMatcher);
+ }
+}
diff --git a/src/main/java/org/junit/internal/matchers/StringContains.java b/src/main/java/org/junit/internal/matchers/StringContains.java
deleted file mode 100644
index e5f5334..0000000
--- a/src/main/java/org/junit/internal/matchers/StringContains.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (c) 2000-2006 hamcrest.org
- */
-package org.junit.internal.matchers;
-
-import org.hamcrest.Factory;
-import org.hamcrest.Matcher;
-
-/**
- * Tests if the argument is a string that contains a substring.
- */
-public class StringContains extends SubstringMatcher {
- public StringContains(String substring) {
- super(substring);
- }
-
- @Override
- protected boolean evalSubstringOf(String s) {
- return s.indexOf(substring) >= 0;
- }
-
- @Override
- protected String relationship() {
- return "containing";
- }
-
- @Factory
- public static Matcher<String> containsString(String substring) {
- return new StringContains(substring);
- }
-
-} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java b/src/main/java/org/junit/internal/matchers/SubstringMatcher.java
deleted file mode 100644
index 1c65240..0000000
--- a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.junit.internal.matchers;
-
-import org.hamcrest.Description;
-
-public abstract class SubstringMatcher extends TypeSafeMatcher<String> {
-
- protected final String substring;
-
- protected SubstringMatcher(final String substring) {
- this.substring = substring;
- }
-
- @Override
- public boolean matchesSafely(String item) {
- return evalSubstringOf(item);
- }
-
- public void describeTo(Description description) {
- description.appendText("a string ")
- .appendText(relationship())
- .appendText(" ")
- .appendValue(substring);
- }
-
- protected abstract boolean evalSubstringOf(String string);
-
- protected abstract String relationship();
-} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
new file mode 100644
index 0000000..22ce8bd
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
@@ -0,0 +1,50 @@
+package org.junit.internal.matchers;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * A matcher that applies a delegate matcher to the cause of the current Throwable, returning the result of that
+ * match.
+ *
+ * @param <T> the type of the throwable being matched
+ */
+public class ThrowableCauseMatcher<T extends Throwable> extends
+ TypeSafeMatcher<T> {
+
+ private final Matcher<? extends Throwable> causeMatcher;
+
+ public ThrowableCauseMatcher(Matcher<? extends Throwable> causeMatcher) {
+ this.causeMatcher = causeMatcher;
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("exception with cause ");
+ description.appendDescriptionOf(causeMatcher);
+ }
+
+ @Override
+ protected boolean matchesSafely(T item) {
+ return causeMatcher.matches(item.getCause());
+ }
+
+ @Override
+ protected void describeMismatchSafely(T item, Description description) {
+ description.appendText("cause ");
+ causeMatcher.describeMismatch(item.getCause(), description);
+ }
+
+ /**
+ * Returns a matcher that verifies that the outer exception has a cause for which the supplied matcher
+ * evaluates to true.
+ *
+ * @param matcher to apply to the cause of the outer exception
+ * @param <T> type of the outer exception
+ */
+ @Factory
+ public static <T extends Throwable> Matcher<T> hasCause(final Matcher<? extends Throwable> matcher) {
+ return new ThrowableCauseMatcher<T>(matcher);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java
new file mode 100644
index 0000000..74386a8
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java
@@ -0,0 +1,37 @@
+package org.junit.internal.matchers;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class ThrowableMessageMatcher<T extends Throwable> extends
+ TypeSafeMatcher<T> {
+
+ private final Matcher<String> matcher;
+
+ public ThrowableMessageMatcher(Matcher<String> matcher) {
+ this.matcher = matcher;
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("exception with message ");
+ description.appendDescriptionOf(matcher);
+ }
+
+ @Override
+ protected boolean matchesSafely(T item) {
+ return matcher.matches(item.getMessage());
+ }
+
+ @Override
+ protected void describeMismatchSafely(T item, Description description) {
+ description.appendText("message ");
+ matcher.describeMismatch(item.getMessage(), description);
+ }
+
+ @Factory
+ public static <T extends Throwable> Matcher<T> hasMessage(final Matcher<String> matcher) {
+ return new ThrowableMessageMatcher<T>(matcher);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
index 794a174..4e2cc12 100644
--- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
+++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
@@ -3,13 +3,16 @@ package org.junit.internal.matchers;
import java.lang.reflect.Method;
import org.hamcrest.BaseMatcher;
+import org.junit.internal.MethodSorter;
/**
* Convenient base class for Matchers that require a non-null value of a specific type.
* This simply implements the null check, checks the type and then casts.
*
* @author Joe Walnes
+ * @deprecated Please use {@link org.hamcrest.TypeSafeMatcher}.
*/
+@Deprecated
public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> {
private Class<?> expectedType;
@@ -23,27 +26,27 @@ public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> {
protected TypeSafeMatcher() {
expectedType = findExpectedType(getClass());
}
-
+
private static Class<?> findExpectedType(Class<?> fromClass) {
for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
- for (Method method : c.getDeclaredMethods()) {
+ for (Method method : MethodSorter.getDeclaredMethods(c)) {
if (isMatchesSafelyMethod(method)) {
return method.getParameterTypes()[0];
}
}
}
-
+
throw new Error("Cannot determine correct type for matchesSafely() method.");
}
-
+
private static boolean isMatchesSafelyMethod(Method method) {
- return method.getName().equals("matchesSafely")
- && method.getParameterTypes().length == 1
- && !method.isSynthetic();
+ return method.getName().equals("matchesSafely")
+ && method.getParameterTypes().length == 1
+ && !method.isSynthetic();
}
-
+
protected TypeSafeMatcher(Class<T> expectedType) {
- this.expectedType = expectedType;
+ this.expectedType = expectedType;
}
/**
diff --git a/src/main/java/org/junit/internal/requests/ClassRequest.java b/src/main/java/org/junit/internal/requests/ClassRequest.java
index 53bf520..3d6b100 100644
--- a/src/main/java/org/junit/internal/requests/ClassRequest.java
+++ b/src/main/java/org/junit/internal/requests/ClassRequest.java
@@ -1,26 +1,39 @@
package org.junit.internal.requests;
-
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.runner.Request;
import org.junit.runner.Runner;
public class ClassRequest extends Request {
- private final Class<?> fTestClass;
+ private final Object runnerLock = new Object();
- private boolean fCanUseSuiteMethod;
+ /*
+ * We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses
+ * reflection to access this field. See
+ * https://github.com/junit-team/junit/issues/960
+ */
+ private final Class<?> fTestClass;
+ private final boolean canUseSuiteMethod;
+ private volatile Runner runner;
- public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) {
- fTestClass= testClass;
- fCanUseSuiteMethod= canUseSuiteMethod;
- }
+ public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) {
+ this.fTestClass = testClass;
+ this.canUseSuiteMethod = canUseSuiteMethod;
+ }
- public ClassRequest(Class<?> testClass) {
- this(testClass, true);
- }
+ public ClassRequest(Class<?> testClass) {
+ this(testClass, true);
+ }
- @Override
- public Runner getRunner() {
- return new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass);
- }
+ @Override
+ public Runner getRunner() {
+ if (runner == null) {
+ synchronized (runnerLock) {
+ if (runner == null) {
+ runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);
+ }
+ }
+ }
+ return runner;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/requests/FilterRequest.java b/src/main/java/org/junit/internal/requests/FilterRequest.java
index e5d98d1..066cba3 100644
--- a/src/main/java/org/junit/internal/requests/FilterRequest.java
+++ b/src/main/java/org/junit/internal/requests/FilterRequest.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.requests;
import org.junit.internal.runners.ErrorReportingRunner;
@@ -13,30 +10,36 @@ import org.junit.runner.manipulation.NoTestsRemainException;
* A filtered {@link Request}.
*/
public final class FilterRequest extends Request {
- private final Request fRequest;
- private final Filter fFilter;
+ private final Request request;
+ /*
+ * We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses
+ * reflection to access this field. See
+ * https://github.com/junit-team/junit/issues/960
+ */
+ private final Filter fFilter;
- /**
- * Creates a filtered Request
- * @param classRequest a {@link Request} describing your Tests
- * @param filter {@link Filter} to apply to the Tests described in
- * <code>classRequest</code>
- */
- public FilterRequest(Request classRequest, Filter filter) {
- fRequest= classRequest;
- fFilter= filter;
- }
+ /**
+ * Creates a filtered Request
+ *
+ * @param request a {@link Request} describing your Tests
+ * @param filter {@link Filter} to apply to the Tests described in
+ * <code>request</code>
+ */
+ public FilterRequest(Request request, Filter filter) {
+ this.request = request;
+ this.fFilter = filter;
+ }
- @Override
- public Runner getRunner() {
- try {
- Runner runner= fRequest.getRunner();
- fFilter.apply(runner);
- return runner;
- } catch (NoTestsRemainException e) {
- return new ErrorReportingRunner(Filter.class, new Exception(String
- .format("No tests found matching %s from %s", fFilter
- .describe(), fRequest.toString())));
- }
- }
+ @Override
+ public Runner getRunner() {
+ try {
+ Runner runner = request.getRunner();
+ fFilter.apply(runner);
+ return runner;
+ } catch (NoTestsRemainException e) {
+ return new ErrorReportingRunner(Filter.class, new Exception(String
+ .format("No tests found matching %s from %s", fFilter
+ .describe(), request.toString())));
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/requests/SortingRequest.java b/src/main/java/org/junit/internal/requests/SortingRequest.java
index 3c6f4f5..77061da 100644
--- a/src/main/java/org/junit/internal/requests/SortingRequest.java
+++ b/src/main/java/org/junit/internal/requests/SortingRequest.java
@@ -8,18 +8,18 @@ import org.junit.runner.Runner;
import org.junit.runner.manipulation.Sorter;
public class SortingRequest extends Request {
- private final Request fRequest;
- private final Comparator<Description> fComparator;
+ private final Request request;
+ private final Comparator<Description> comparator;
- public SortingRequest(Request request, Comparator<Description> comparator) {
- fRequest= request;
- fComparator= comparator;
- }
+ public SortingRequest(Request request, Comparator<Description> comparator) {
+ this.request = request;
+ this.comparator = comparator;
+ }
- @Override
- public Runner getRunner() {
- Runner runner= fRequest.getRunner();
- new Sorter(fComparator).apply(runner);
- return runner;
- }
+ @Override
+ public Runner getRunner() {
+ Runner runner = request.getRunner();
+ new Sorter(comparator).apply(runner);
+ return runner;
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/ClassRoadie.java b/src/main/java/org/junit/internal/runners/ClassRoadie.java
index 1f77d37..df1b453 100644
--- a/src/main/java/org/junit/internal/runners/ClassRoadie.java
+++ b/src/main/java/org/junit/internal/runners/ClassRoadie.java
@@ -4,6 +4,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
+import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
@@ -11,69 +12,70 @@ import org.junit.runners.BlockJUnit4ClassRunner;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
-public
-class ClassRoadie {
- private RunNotifier fNotifier;
- private TestClass fTestClass;
- private Description fDescription;
- private final Runnable fRunnable;
-
- public ClassRoadie(RunNotifier notifier, TestClass testClass,
- Description description, Runnable runnable) {
- fNotifier= notifier;
- fTestClass= testClass;
- fDescription= description;
- fRunnable= runnable;
- }
+public class ClassRoadie {
+ private RunNotifier notifier;
+ private TestClass testClass;
+ private Description description;
+ private final Runnable runnable;
- protected void runUnprotected() {
- fRunnable.run();
- };
+ public ClassRoadie(RunNotifier notifier, TestClass testClass,
+ Description description, Runnable runnable) {
+ this.notifier = notifier;
+ this.testClass = testClass;
+ this.description = description;
+ this.runnable = runnable;
+ }
- protected void addFailure(Throwable targetException) {
- fNotifier.fireTestFailure(new Failure(fDescription, targetException));
- }
+ protected void runUnprotected() {
+ runnable.run();
+ }
- public void runProtected() {
- try {
- runBefores();
- runUnprotected();
- } catch (FailedBefore e) {
- } finally {
- runAfters();
- }
- }
+ protected void addFailure(Throwable targetException) {
+ notifier.fireTestFailure(new Failure(description, targetException));
+ }
- private void runBefores() throws FailedBefore {
- try {
- try {
- List<Method> befores= fTestClass.getBefores();
- for (Method before : befores)
- before.invoke(null);
- } catch (InvocationTargetException e) {
- throw e.getTargetException();
- }
- } catch (org.junit.internal.AssumptionViolatedException e) {
- throw new FailedBefore();
- } catch (Throwable e) {
- addFailure(e);
- throw new FailedBefore();
- }
- }
+ public void runProtected() {
+ try {
+ runBefores();
+ runUnprotected();
+ } catch (FailedBefore e) {
+ } finally {
+ runAfters();
+ }
+ }
- private void runAfters() {
- List<Method> afters= fTestClass.getAfters();
- for (Method after : afters)
- try {
- after.invoke(null);
- } catch (InvocationTargetException e) {
- addFailure(e.getTargetException());
- } catch (Throwable e) {
- addFailure(e); // Untested, but seems impossible
- }
- }
-} \ No newline at end of file
+ private void runBefores() throws FailedBefore {
+ try {
+ try {
+ List<Method> befores = testClass.getBefores();
+ for (Method before : befores) {
+ before.invoke(null);
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ } catch (AssumptionViolatedException e) {
+ throw new FailedBefore();
+ } catch (Throwable e) {
+ addFailure(e);
+ throw new FailedBefore();
+ }
+ }
+
+ private void runAfters() {
+ List<Method> afters = testClass.getAfters();
+ for (Method after : afters) {
+ try {
+ after.invoke(null);
+ } catch (InvocationTargetException e) {
+ addFailure(e.getTargetException());
+ } catch (Throwable e) {
+ addFailure(e); // Untested, but seems impossible
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
index 200b6f0..1d32beb 100644
--- a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
+++ b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
@@ -11,50 +11,58 @@ import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
public class ErrorReportingRunner extends Runner {
- private final List<Throwable> fCauses;
-
- private final Class<?> fTestClass;
-
- public ErrorReportingRunner(Class<?> testClass, Throwable cause) {
- fTestClass= testClass;
- fCauses= getCauses(cause);
- }
-
- @Override
- public Description getDescription() {
- Description description= Description.createSuiteDescription(fTestClass);
- for (Throwable each : fCauses)
- description.addChild(describeCause(each));
- return description;
- }
-
- @Override
- public void run(RunNotifier notifier) {
- for (Throwable each : fCauses)
- runCause(each, notifier);
- }
-
- @SuppressWarnings("deprecation")
- private List<Throwable> getCauses(Throwable cause) {
- if (cause instanceof InvocationTargetException)
- return getCauses(cause.getCause());
- if (cause instanceof InitializationError)
- return ((InitializationError) cause).getCauses();
- if (cause instanceof org.junit.internal.runners.InitializationError)
- return ((org.junit.internal.runners.InitializationError) cause)
- .getCauses();
- return Arrays.asList(cause);
- }
-
- private Description describeCause(Throwable child) {
- return Description.createTestDescription(fTestClass,
- "initializationError");
- }
-
- private void runCause(Throwable child, RunNotifier notifier) {
- Description description= describeCause(child);
- notifier.fireTestStarted(description);
- notifier.fireTestFailure(new Failure(description, child));
- notifier.fireTestFinished(description);
- }
-} \ No newline at end of file
+ private final List<Throwable> causes;
+
+ private final Class<?> testClass;
+
+ public ErrorReportingRunner(Class<?> testClass, Throwable cause) {
+ if (testClass == null) {
+ throw new NullPointerException("Test class cannot be null");
+ }
+ this.testClass = testClass;
+ causes = getCauses(cause);
+ }
+
+ @Override
+ public Description getDescription() {
+ Description description = Description.createSuiteDescription(testClass);
+ for (Throwable each : causes) {
+ description.addChild(describeCause(each));
+ }
+ return description;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ for (Throwable each : causes) {
+ runCause(each, notifier);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private List<Throwable> getCauses(Throwable cause) {
+ if (cause instanceof InvocationTargetException) {
+ return getCauses(cause.getCause());
+ }
+ if (cause instanceof InitializationError) {
+ return ((InitializationError) cause).getCauses();
+ }
+ if (cause instanceof org.junit.internal.runners.InitializationError) {
+ return ((org.junit.internal.runners.InitializationError) cause)
+ .getCauses();
+ }
+ return Arrays.asList(cause);
+ }
+
+ private Description describeCause(Throwable child) {
+ return Description.createTestDescription(testClass,
+ "initializationError");
+ }
+
+ private void runCause(Throwable child, RunNotifier notifier) {
+ Description description = describeCause(child);
+ notifier.fireTestStarted(description);
+ notifier.fireTestFailure(new Failure(description, child));
+ notifier.fireTestFinished(description);
+ }
+}
diff --git a/src/main/java/org/junit/internal/runners/FailedBefore.java b/src/main/java/org/junit/internal/runners/FailedBefore.java
index 29dcba4..1036cb6 100644
--- a/src/main/java/org/junit/internal/runners/FailedBefore.java
+++ b/src/main/java/org/junit/internal/runners/FailedBefore.java
@@ -2,13 +2,12 @@ package org.junit.internal.runners;
import org.junit.runners.BlockJUnit4ClassRunner;
-
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
class FailedBefore extends Exception {
- private static final long serialVersionUID= 1L;
+ private static final long serialVersionUID = 1L;
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/InitializationError.java b/src/main/java/org/junit/internal/runners/InitializationError.java
index 5715ec5..52065ec 100644
--- a/src/main/java/org/junit/internal/runners/InitializationError.java
+++ b/src/main/java/org/junit/internal/runners/InitializationError.java
@@ -3,28 +3,35 @@ package org.junit.internal.runners;
import java.util.Arrays;
import java.util.List;
-@Deprecated
/**
- * Use the published version: {@link org.junit.runners.InitializationError}
+ * Use the published version:
+ * {@link org.junit.runners.model.InitializationError}
* This may disappear as soon as 1 April 2009
*/
+@Deprecated
public class InitializationError extends Exception {
- private static final long serialVersionUID= 1L;
- private final List<Throwable> fErrors;
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final List<Throwable> fErrors;
+
+ public InitializationError(List<Throwable> errors) {
+ this.fErrors = errors;
+ }
- public InitializationError(List<Throwable> errors) {
- fErrors= errors;
- }
+ public InitializationError(Throwable... errors) {
+ this(Arrays.asList(errors));
+ }
- public InitializationError(Throwable... errors) {
- this(Arrays.asList(errors));
- }
-
- public InitializationError(String string) {
- this(new Exception(string));
- }
+ public InitializationError(String string) {
+ this(new Exception(string));
+ }
- public List<Throwable> getCauses() {
- return fErrors;
- }
+ public List<Throwable> getCauses() {
+ return fErrors;
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
index 0028d0c..631fcf2 100644
--- a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
+++ b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
@@ -17,142 +17,164 @@ import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
public class JUnit38ClassRunner extends Runner implements Filterable, Sortable {
- private final class OldTestClassAdaptingListener implements
- TestListener {
- private final RunNotifier fNotifier;
-
- private OldTestClassAdaptingListener(RunNotifier notifier) {
- fNotifier= notifier;
- }
-
- public void endTest(Test test) {
- fNotifier.fireTestFinished(asDescription(test));
- }
-
- public void startTest(Test test) {
- fNotifier.fireTestStarted(asDescription(test));
- }
-
- // Implement junit.framework.TestListener
- public void addError(Test test, Throwable t) {
- Failure failure= new Failure(asDescription(test), t);
- fNotifier.fireTestFailure(failure);
- }
-
- private Description asDescription(Test test) {
- if (test instanceof Describable) {
- Describable facade= (Describable) test;
- return facade.getDescription();
- }
- return Description.createTestDescription(getEffectiveClass(test), getName(test));
- }
-
- private Class<? extends Test> getEffectiveClass(Test test) {
- return test.getClass();
- }
-
- private String getName(Test test) {
- if (test instanceof TestCase)
- return ((TestCase) test).getName();
- else
- return test.toString();
- }
-
- public void addFailure(Test test, AssertionFailedError t) {
- addError(test, t);
- }
- }
-
- private Test fTest;
-
- public JUnit38ClassRunner(Class<?> klass) {
- this(new TestSuite(klass.asSubclass(TestCase.class)));
- }
-
- public JUnit38ClassRunner(Test test) {
- super();
- setTest(test);
- }
-
- @Override
- public void run(RunNotifier notifier) {
- TestResult result= new TestResult();
- result.addListener(createAdaptingListener(notifier));
- getTest().run(result);
- }
-
- public TestListener createAdaptingListener(final RunNotifier notifier) {
- return new OldTestClassAdaptingListener(notifier);
- }
-
- @Override
- public Description getDescription() {
- return makeDescription(getTest());
- }
-
- private static Description makeDescription(Test test) {
- if (test instanceof TestCase) {
- TestCase tc= (TestCase) test;
- return Description.createTestDescription(tc.getClass(), tc.getName());
- } else if (test instanceof TestSuite) {
- TestSuite ts= (TestSuite) test;
- String name= ts.getName() == null ? createSuiteDescription(ts) : ts.getName();
- Description description= Description.createSuiteDescription(name);
- int n= ts.testCount();
- for (int i= 0; i < n; i++) {
- Description made= makeDescription(ts.testAt(i));
- description.addChild(made);
- }
- return description;
- } else if (test instanceof Describable) {
- Describable adapter= (Describable) test;
- return adapter.getDescription();
- } else if (test instanceof TestDecorator) {
- TestDecorator decorator= (TestDecorator) test;
- return makeDescription(decorator.getTest());
- } else {
- // This is the best we can do in this case
- return Description.createSuiteDescription(test.getClass());
- }
- }
-
- private static String createSuiteDescription(TestSuite ts) {
- int count= ts.countTestCases();
- String example = count == 0 ? "" : String.format(" [example: %s]", ts.testAt(0));
- return String.format("TestSuite with %s tests%s", count, example);
- }
-
- public void filter(Filter filter) throws NoTestsRemainException {
- if (getTest() instanceof Filterable) {
- Filterable adapter= (Filterable) getTest();
- adapter.filter(filter);
- } else if (getTest() instanceof TestSuite) {
- TestSuite suite= (TestSuite) getTest();
- TestSuite filtered= new TestSuite(suite.getName());
- int n= suite.testCount();
- for (int i= 0; i < n; i++) {
- Test test= suite.testAt(i);
- if (filter.shouldRun(makeDescription(test)))
- filtered.addTest(test);
- }
- setTest(filtered);
- }
- }
-
- public void sort(Sorter sorter) {
- if (getTest() instanceof Sortable) {
- Sortable adapter= (Sortable) getTest();
- adapter.sort(sorter);
- }
- }
-
- private void setTest(Test test) {
- fTest = test;
- }
-
- private Test getTest() {
- return fTest;
- }
+ private static final class OldTestClassAdaptingListener implements
+ TestListener {
+ private final RunNotifier notifier;
+
+ private OldTestClassAdaptingListener(RunNotifier notifier) {
+ this.notifier = notifier;
+ }
+
+ public void endTest(Test test) {
+ notifier.fireTestFinished(asDescription(test));
+ }
+
+ public void startTest(Test test) {
+ notifier.fireTestStarted(asDescription(test));
+ }
+
+ // Implement junit.framework.TestListener
+ public void addError(Test test, Throwable e) {
+ Failure failure = new Failure(asDescription(test), e);
+ notifier.fireTestFailure(failure);
+ }
+
+ private Description asDescription(Test test) {
+ if (test instanceof Describable) {
+ Describable facade = (Describable) test;
+ return facade.getDescription();
+ }
+ return Description.createTestDescription(getEffectiveClass(test), getName(test));
+ }
+
+ private Class<? extends Test> getEffectiveClass(Test test) {
+ return test.getClass();
+ }
+
+ private String getName(Test test) {
+ if (test instanceof TestCase) {
+ return ((TestCase) test).getName();
+ } else {
+ return test.toString();
+ }
+ }
+
+ public void addFailure(Test test, AssertionFailedError t) {
+ addError(test, t);
+ }
+ }
+
+ private volatile Test test;
+
+ public JUnit38ClassRunner(Class<?> klass) {
+ this(new TestSuite(klass.asSubclass(TestCase.class)));
+ }
+
+ public JUnit38ClassRunner(Test test) {
+ super();
+ setTest(test);
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ TestResult result = new TestResult();
+ result.addListener(createAdaptingListener(notifier));
+ getTest().run(result);
+ }
+
+ public TestListener createAdaptingListener(final RunNotifier notifier) {
+ return new OldTestClassAdaptingListener(notifier);
+ }
+
+ @Override
+ public Description getDescription() {
+ return makeDescription(getTest());
+ }
+
+ private static Description makeDescription(Test test) {
+ if (test instanceof TestCase) {
+ TestCase tc = (TestCase) test;
+ return Description.createTestDescription(tc.getClass(), tc.getName(),
+ getAnnotations(tc));
+ } else if (test instanceof TestSuite) {
+ TestSuite ts = (TestSuite) test;
+ String name = ts.getName() == null ? createSuiteDescription(ts) : ts.getName();
+ Description description = Description.createSuiteDescription(name);
+ int n = ts.testCount();
+ for (int i = 0; i < n; i++) {
+ Description made = makeDescription(ts.testAt(i));
+ description.addChild(made);
+ }
+ return description;
+ } else if (test instanceof Describable) {
+ Describable adapter = (Describable) test;
+ return adapter.getDescription();
+ } else if (test instanceof TestDecorator) {
+ TestDecorator decorator = (TestDecorator) test;
+ return makeDescription(decorator.getTest());
+ } else {
+ // This is the best we can do in this case
+ return Description.createSuiteDescription(test.getClass());
+ }
+ }
+
+ /**
+ * Get the annotations associated with given TestCase.
+ * @param test the TestCase.
+ */
+ private static Annotation[] getAnnotations(TestCase test) {
+ try {
+ Method m = test.getClass().getMethod(test.getName());
+ return m.getDeclaredAnnotations();
+ } catch (SecurityException e) {
+ } catch (NoSuchMethodException e) {
+ }
+ return new Annotation[0];
+ }
+
+ private static String createSuiteDescription(TestSuite ts) {
+ int count = ts.countTestCases();
+ String example = count == 0 ? "" : String.format(" [example: %s]", ts.testAt(0));
+ return String.format("TestSuite with %s tests%s", count, example);
+ }
+
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (getTest() instanceof Filterable) {
+ Filterable adapter = (Filterable) getTest();
+ adapter.filter(filter);
+ } else if (getTest() instanceof TestSuite) {
+ TestSuite suite = (TestSuite) getTest();
+ TestSuite filtered = new TestSuite(suite.getName());
+ int n = suite.testCount();
+ for (int i = 0; i < n; i++) {
+ Test test = suite.testAt(i);
+ if (filter.shouldRun(makeDescription(test))) {
+ filtered.addTest(test);
+ }
+ }
+ setTest(filtered);
+ if (filtered.testCount() == 0) {
+ throw new NoTestsRemainException();
+ }
+ }
+ }
+
+ public void sort(Sorter sorter) {
+ if (getTest() instanceof Sortable) {
+ Sortable adapter = (Sortable) getTest();
+ adapter.sort(sorter);
+ }
+ }
+
+ private void setTest(Test test) {
+ this.test = test;
+ }
+
+ private Test getTest() {
+ return test;
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java
index d732880..69a23c4 100644
--- a/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java
+++ b/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java
@@ -21,125 +21,127 @@ import org.junit.runners.BlockJUnit4ClassRunner;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
- *
- * This may disappear as soon as 1 April 2009
*/
@Deprecated
public class JUnit4ClassRunner extends Runner implements Filterable, Sortable {
- private final List<Method> fTestMethods;
- private TestClass fTestClass;
-
- public JUnit4ClassRunner(Class<?> klass) throws InitializationError {
- fTestClass= new TestClass(klass);
- fTestMethods= getTestMethods();
- validate();
- }
-
- protected List<Method> getTestMethods() {
- return fTestClass.getTestMethods();
- }
-
- protected void validate() throws InitializationError {
- MethodValidator methodValidator= new MethodValidator(fTestClass);
- methodValidator.validateMethodsForDefaultRunner();
- methodValidator.assertValid();
- }
-
- @Override
- public void run(final RunNotifier notifier) {
- new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() {
- public void run() {
- runMethods(notifier);
- }
- }).runProtected();
- }
-
- protected void runMethods(final RunNotifier notifier) {
- for (Method method : fTestMethods)
- invokeTestMethod(method, notifier);
- }
-
- @Override
- public Description getDescription() {
- Description spec= Description.createSuiteDescription(getName(), classAnnotations());
- List<Method> testMethods= fTestMethods;
- for (Method method : testMethods)
- spec.addChild(methodDescription(method));
- return spec;
- }
-
- protected Annotation[] classAnnotations() {
- return fTestClass.getJavaClass().getAnnotations();
- }
-
- protected String getName() {
- return getTestClass().getName();
- }
-
- protected Object createTest() throws Exception {
- return getTestClass().getConstructor().newInstance();
- }
-
- protected void invokeTestMethod(Method method, RunNotifier notifier) {
- Description description= methodDescription(method);
- Object test;
- try {
- test= createTest();
- } catch (InvocationTargetException e) {
- testAborted(notifier, description, e.getCause());
- return;
- } catch (Exception e) {
- testAborted(notifier, description, e);
- return;
- }
- TestMethod testMethod= wrapMethod(method);
- new MethodRoadie(test, testMethod, notifier, description).run();
- }
-
- private void testAborted(RunNotifier notifier, Description description,
- Throwable e) {
- notifier.fireTestStarted(description);
- notifier.fireTestFailure(new Failure(description, e));
- notifier.fireTestFinished(description);
- }
-
- protected TestMethod wrapMethod(Method method) {
- return new TestMethod(method, fTestClass);
- }
-
- protected String testName(Method method) {
- return method.getName();
- }
-
- protected Description methodDescription(Method method) {
- return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method));
- }
-
- protected Annotation[] testAnnotations(Method method) {
- return method.getAnnotations();
- }
-
- public void filter(Filter filter) throws NoTestsRemainException {
- for (Iterator<Method> iter= fTestMethods.iterator(); iter.hasNext();) {
- Method method= iter.next();
- if (!filter.shouldRun(methodDescription(method)))
- iter.remove();
- }
- if (fTestMethods.isEmpty())
- throw new NoTestsRemainException();
- }
-
- public void sort(final Sorter sorter) {
- Collections.sort(fTestMethods, new Comparator<Method>() {
- public int compare(Method o1, Method o2) {
- return sorter.compare(methodDescription(o1), methodDescription(o2));
- }
- });
- }
-
- protected TestClass getTestClass() {
- return fTestClass;
- }
+ private final List<Method> testMethods;
+ private TestClass testClass;
+
+ public JUnit4ClassRunner(Class<?> klass) throws InitializationError {
+ testClass = new TestClass(klass);
+ testMethods = getTestMethods();
+ validate();
+ }
+
+ protected List<Method> getTestMethods() {
+ return testClass.getTestMethods();
+ }
+
+ protected void validate() throws InitializationError {
+ MethodValidator methodValidator = new MethodValidator(testClass);
+ methodValidator.validateMethodsForDefaultRunner();
+ methodValidator.assertValid();
+ }
+
+ @Override
+ public void run(final RunNotifier notifier) {
+ new ClassRoadie(notifier, testClass, getDescription(), new Runnable() {
+ public void run() {
+ runMethods(notifier);
+ }
+ }).runProtected();
+ }
+
+ protected void runMethods(final RunNotifier notifier) {
+ for (Method method : testMethods) {
+ invokeTestMethod(method, notifier);
+ }
+ }
+
+ @Override
+ public Description getDescription() {
+ Description spec = Description.createSuiteDescription(getName(), classAnnotations());
+ List<Method> testMethods = this.testMethods;
+ for (Method method : testMethods) {
+ spec.addChild(methodDescription(method));
+ }
+ return spec;
+ }
+
+ protected Annotation[] classAnnotations() {
+ return testClass.getJavaClass().getAnnotations();
+ }
+
+ protected String getName() {
+ return getTestClass().getName();
+ }
+
+ protected Object createTest() throws Exception {
+ return getTestClass().getConstructor().newInstance();
+ }
+
+ protected void invokeTestMethod(Method method, RunNotifier notifier) {
+ Description description = methodDescription(method);
+ Object test;
+ try {
+ test = createTest();
+ } catch (InvocationTargetException e) {
+ testAborted(notifier, description, e.getCause());
+ return;
+ } catch (Exception e) {
+ testAborted(notifier, description, e);
+ return;
+ }
+ TestMethod testMethod = wrapMethod(method);
+ new MethodRoadie(test, testMethod, notifier, description).run();
+ }
+
+ private void testAborted(RunNotifier notifier, Description description,
+ Throwable e) {
+ notifier.fireTestStarted(description);
+ notifier.fireTestFailure(new Failure(description, e));
+ notifier.fireTestFinished(description);
+ }
+
+ protected TestMethod wrapMethod(Method method) {
+ return new TestMethod(method, testClass);
+ }
+
+ protected String testName(Method method) {
+ return method.getName();
+ }
+
+ protected Description methodDescription(Method method) {
+ return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method));
+ }
+
+ protected Annotation[] testAnnotations(Method method) {
+ return method.getAnnotations();
+ }
+
+ public void filter(Filter filter) throws NoTestsRemainException {
+ for (Iterator<Method> iter = testMethods.iterator(); iter.hasNext(); ) {
+ Method method = iter.next();
+ if (!filter.shouldRun(methodDescription(method))) {
+ iter.remove();
+ }
+ }
+ if (testMethods.isEmpty()) {
+ throw new NoTestsRemainException();
+ }
+ }
+
+ public void sort(final Sorter sorter) {
+ Collections.sort(testMethods, new Comparator<Method>() {
+ public int compare(Method o1, Method o2) {
+ return sorter.compare(methodDescription(o1), methodDescription(o2));
+ }
+ });
+ }
+
+ protected TestClass getTestClass() {
+ return testClass;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/MethodRoadie.java b/src/main/java/org/junit/internal/runners/MethodRoadie.java
index 4407821..01a476b 100644
--- a/src/main/java/org/junit/internal/runners/MethodRoadie.java
+++ b/src/main/java/org/junit/internal/runners/MethodRoadie.java
@@ -15,143 +15,149 @@ import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.TestTimedOutException;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
public class MethodRoadie {
- private final Object fTest;
- private final RunNotifier fNotifier;
- private final Description fDescription;
- private TestMethod fTestMethod;
+ private final Object test;
+ private final RunNotifier notifier;
+ private final Description description;
+ private TestMethod testMethod;
- public MethodRoadie(Object test, TestMethod method, RunNotifier notifier, Description description) {
- fTest= test;
- fNotifier= notifier;
- fDescription= description;
- fTestMethod= method;
- }
+ public MethodRoadie(Object test, TestMethod method, RunNotifier notifier, Description description) {
+ this.test = test;
+ this.notifier = notifier;
+ this.description = description;
+ testMethod = method;
+ }
- public void run() {
- if (fTestMethod.isIgnored()) {
- fNotifier.fireTestIgnored(fDescription);
- return;
- }
- fNotifier.fireTestStarted(fDescription);
- try {
- long timeout= fTestMethod.getTimeout();
- if (timeout > 0)
- runWithTimeout(timeout);
- else
- runTest();
- } finally {
- fNotifier.fireTestFinished(fDescription);
- }
- }
+ public void run() {
+ if (testMethod.isIgnored()) {
+ notifier.fireTestIgnored(description);
+ return;
+ }
+ notifier.fireTestStarted(description);
+ try {
+ long timeout = testMethod.getTimeout();
+ if (timeout > 0) {
+ runWithTimeout(timeout);
+ } else {
+ runTest();
+ }
+ } finally {
+ notifier.fireTestFinished(description);
+ }
+ }
- private void runWithTimeout(final long timeout) {
- runBeforesThenTestThenAfters(new Runnable() {
-
- public void run() {
- ExecutorService service= Executors.newSingleThreadExecutor();
- Callable<Object> callable= new Callable<Object>() {
- public Object call() throws Exception {
- runTestMethod();
- return null;
- }
- };
- Future<Object> result= service.submit(callable);
- service.shutdown();
- try {
- boolean terminated= service.awaitTermination(timeout,
- TimeUnit.MILLISECONDS);
- if (!terminated)
- service.shutdownNow();
- result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation
- } catch (TimeoutException e) {
- addFailure(new Exception(String.format("test timed out after %d milliseconds", timeout)));
- } catch (Exception e) {
- addFailure(e);
- }
- }
- });
- }
-
- public void runTest() {
- runBeforesThenTestThenAfters(new Runnable() {
- public void run() {
- runTestMethod();
- }
- });
- }
+ private void runWithTimeout(final long timeout) {
+ runBeforesThenTestThenAfters(new Runnable() {
- public void runBeforesThenTestThenAfters(Runnable test) {
- try {
- runBefores();
- test.run();
- } catch (FailedBefore e) {
- } catch (Exception e) {
- throw new RuntimeException("test should never throw an exception to this level");
- } finally {
- runAfters();
- }
- }
-
- protected void runTestMethod() {
- try {
- fTestMethod.invoke(fTest);
- if (fTestMethod.expectsException())
- addFailure(new AssertionError("Expected exception: " + fTestMethod.getExpectedException().getName()));
- } catch (InvocationTargetException e) {
- Throwable actual= e.getTargetException();
- if (actual instanceof AssumptionViolatedException)
- return;
- else if (!fTestMethod.expectsException())
- addFailure(actual);
- else if (fTestMethod.isUnexpected(actual)) {
- String message= "Unexpected exception, expected<" + fTestMethod.getExpectedException().getName() + "> but was<"
- + actual.getClass().getName() + ">";
- addFailure(new Exception(message, actual));
- }
- } catch (Throwable e) {
- addFailure(e);
- }
- }
-
- private void runBefores() throws FailedBefore {
- try {
- try {
- List<Method> befores= fTestMethod.getBefores();
- for (Method before : befores)
- before.invoke(fTest);
- } catch (InvocationTargetException e) {
- throw e.getTargetException();
- }
- } catch (AssumptionViolatedException e) {
- throw new FailedBefore();
- } catch (Throwable e) {
- addFailure(e);
- throw new FailedBefore();
- }
- }
+ public void run() {
+ ExecutorService service = Executors.newSingleThreadExecutor();
+ Callable<Object> callable = new Callable<Object>() {
+ public Object call() throws Exception {
+ runTestMethod();
+ return null;
+ }
+ };
+ Future<Object> result = service.submit(callable);
+ service.shutdown();
+ try {
+ boolean terminated = service.awaitTermination(timeout,
+ TimeUnit.MILLISECONDS);
+ if (!terminated) {
+ service.shutdownNow();
+ }
+ result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation
+ } catch (TimeoutException e) {
+ addFailure(new TestTimedOutException(timeout, TimeUnit.MILLISECONDS));
+ } catch (Exception e) {
+ addFailure(e);
+ }
+ }
+ });
+ }
- private void runAfters() {
- List<Method> afters= fTestMethod.getAfters();
- for (Method after : afters)
- try {
- after.invoke(fTest);
- } catch (InvocationTargetException e) {
- addFailure(e.getTargetException());
- } catch (Throwable e) {
- addFailure(e); // Untested, but seems impossible
- }
- }
+ public void runTest() {
+ runBeforesThenTestThenAfters(new Runnable() {
+ public void run() {
+ runTestMethod();
+ }
+ });
+ }
- protected void addFailure(Throwable e) {
- fNotifier.fireTestFailure(new Failure(fDescription, e));
- }
+ public void runBeforesThenTestThenAfters(Runnable test) {
+ try {
+ runBefores();
+ test.run();
+ } catch (FailedBefore e) {
+ } catch (Exception e) {
+ throw new RuntimeException("test should never throw an exception to this level");
+ } finally {
+ runAfters();
+ }
+ }
+
+ protected void runTestMethod() {
+ try {
+ testMethod.invoke(test);
+ if (testMethod.expectsException()) {
+ addFailure(new AssertionError("Expected exception: " + testMethod.getExpectedException().getName()));
+ }
+ } catch (InvocationTargetException e) {
+ Throwable actual = e.getTargetException();
+ if (actual instanceof AssumptionViolatedException) {
+ return;
+ } else if (!testMethod.expectsException()) {
+ addFailure(actual);
+ } else if (testMethod.isUnexpected(actual)) {
+ String message = "Unexpected exception, expected<" + testMethod.getExpectedException().getName() + "> but was<"
+ + actual.getClass().getName() + ">";
+ addFailure(new Exception(message, actual));
+ }
+ } catch (Throwable e) {
+ addFailure(e);
+ }
+ }
+
+ private void runBefores() throws FailedBefore {
+ try {
+ try {
+ List<Method> befores = testMethod.getBefores();
+ for (Method before : befores) {
+ before.invoke(test);
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ } catch (AssumptionViolatedException e) {
+ throw new FailedBefore();
+ } catch (Throwable e) {
+ addFailure(e);
+ throw new FailedBefore();
+ }
+ }
+
+ private void runAfters() {
+ List<Method> afters = testMethod.getAfters();
+ for (Method after : afters) {
+ try {
+ after.invoke(test);
+ } catch (InvocationTargetException e) {
+ addFailure(e.getTargetException());
+ } catch (Throwable e) {
+ addFailure(e); // Untested, but seems impossible
+ }
+ }
+ }
+
+ protected void addFailure(Throwable e) {
+ notifier.fireTestFailure(new Failure(description, e));
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/MethodValidator.java b/src/main/java/org/junit/internal/runners/MethodValidator.java
index cadc93f..ba9c9d1 100644
--- a/src/main/java/org/junit/internal/runners/MethodValidator.java
+++ b/src/main/java/org/junit/internal/runners/MethodValidator.java
@@ -15,77 +15,83 @@ import org.junit.runners.BlockJUnit4ClassRunner;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
public class MethodValidator {
- private final List<Throwable> fErrors= new ArrayList<Throwable>();
+ private final List<Throwable> errors = new ArrayList<Throwable>();
- private TestClass fTestClass;
+ private TestClass testClass;
- public MethodValidator(TestClass testClass) {
- fTestClass = testClass;
- }
+ public MethodValidator(TestClass testClass) {
+ this.testClass = testClass;
+ }
- public void validateInstanceMethods() {
- validateTestMethods(After.class, false);
- validateTestMethods(Before.class, false);
- validateTestMethods(Test.class, false);
-
- List<Method> methods= fTestClass.getAnnotatedMethods(Test.class);
- if (methods.size() == 0)
- fErrors.add(new Exception("No runnable methods"));
- }
+ public void validateInstanceMethods() {
+ validateTestMethods(After.class, false);
+ validateTestMethods(Before.class, false);
+ validateTestMethods(Test.class, false);
- public void validateStaticMethods() {
- validateTestMethods(BeforeClass.class, true);
- validateTestMethods(AfterClass.class, true);
- }
-
- public List<Throwable> validateMethodsForDefaultRunner() {
- validateNoArgConstructor();
- validateStaticMethods();
- validateInstanceMethods();
- return fErrors;
- }
-
- public void assertValid() throws InitializationError {
- if (!fErrors.isEmpty())
- throw new InitializationError(fErrors);
- }
+ List<Method> methods = testClass.getAnnotatedMethods(Test.class);
+ if (methods.size() == 0) {
+ errors.add(new Exception("No runnable methods"));
+ }
+ }
- public void validateNoArgConstructor() {
- try {
- fTestClass.getConstructor();
- } catch (Exception e) {
- fErrors.add(new Exception("Test class should have public zero-argument constructor", e));
- }
- }
+ public void validateStaticMethods() {
+ validateTestMethods(BeforeClass.class, true);
+ validateTestMethods(AfterClass.class, true);
+ }
- private void validateTestMethods(Class<? extends Annotation> annotation,
- boolean isStatic) {
- List<Method> methods= fTestClass.getAnnotatedMethods(annotation);
-
- for (Method each : methods) {
- if (Modifier.isStatic(each.getModifiers()) != isStatic) {
- String state= isStatic ? "should" : "should not";
- fErrors.add(new Exception("Method " + each.getName() + "() "
+ public List<Throwable> validateMethodsForDefaultRunner() {
+ validateNoArgConstructor();
+ validateStaticMethods();
+ validateInstanceMethods();
+ return errors;
+ }
+
+ public void assertValid() throws InitializationError {
+ if (!errors.isEmpty()) {
+ throw new InitializationError(errors);
+ }
+ }
+
+ public void validateNoArgConstructor() {
+ try {
+ testClass.getConstructor();
+ } catch (Exception e) {
+ errors.add(new Exception("Test class should have public zero-argument constructor", e));
+ }
+ }
+
+ private void validateTestMethods(Class<? extends Annotation> annotation,
+ boolean isStatic) {
+ List<Method> methods = testClass.getAnnotatedMethods(annotation);
+
+ for (Method each : methods) {
+ if (Modifier.isStatic(each.getModifiers()) != isStatic) {
+ String state = isStatic ? "should" : "should not";
+ errors.add(new Exception("Method " + each.getName() + "() "
+ state + " be static"));
- }
- if (!Modifier.isPublic(each.getDeclaringClass().getModifiers()))
- fErrors.add(new Exception("Class " + each.getDeclaringClass().getName()
+ }
+ if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) {
+ errors.add(new Exception("Class " + each.getDeclaringClass().getName()
+ " should be public"));
- if (!Modifier.isPublic(each.getModifiers()))
- fErrors.add(new Exception("Method " + each.getName()
+ }
+ if (!Modifier.isPublic(each.getModifiers())) {
+ errors.add(new Exception("Method " + each.getName()
+ " should be public"));
- if (each.getReturnType() != Void.TYPE)
- fErrors.add(new Exception("Method " + each.getName()
+ }
+ if (each.getReturnType() != Void.TYPE) {
+ errors.add(new Exception("Method " + each.getName()
+ " should be void"));
- if (each.getParameterTypes().length != 0)
- fErrors.add(new Exception("Method " + each.getName()
+ }
+ if (each.getParameterTypes().length != 0) {
+ errors.add(new Exception("Method " + each.getName()
+ " should have no parameters"));
- }
- }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/SuiteMethod.java b/src/main/java/org/junit/internal/runners/SuiteMethod.java
index 4e8bebc..e336983 100644
--- a/src/main/java/org/junit/internal/runners/SuiteMethod.java
+++ b/src/main/java/org/junit/internal/runners/SuiteMethod.java
@@ -6,7 +6,8 @@ import java.lang.reflect.Modifier;
import junit.framework.Test;
-/** Runner for use with JUnit 3.8.x-style AllTests classes
+/**
+ * Runner for use with JUnit 3.8.x-style AllTests classes
* (those that only implement a static <code>suite()</code>
* method). For example:
* <pre>
@@ -19,22 +20,22 @@ import junit.framework.Test;
* </pre>
*/
public class SuiteMethod extends JUnit38ClassRunner {
- public SuiteMethod(Class<?> klass) throws Throwable {
- super(testFromSuiteMethod(klass));
- }
+ public SuiteMethod(Class<?> klass) throws Throwable {
+ super(testFromSuiteMethod(klass));
+ }
- public static Test testFromSuiteMethod(Class<?> klass) throws Throwable {
- Method suiteMethod= null;
- Test suite= null;
- try {
- suiteMethod= klass.getMethod("suite");
- if (! Modifier.isStatic(suiteMethod.getModifiers())) {
- throw new Exception(klass.getName() + ".suite() must be static");
- }
- suite= (Test) suiteMethod.invoke(null); // static method
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- return suite;
- }
+ public static Test testFromSuiteMethod(Class<?> klass) throws Throwable {
+ Method suiteMethod = null;
+ Test suite = null;
+ try {
+ suiteMethod = klass.getMethod("suite");
+ if (!Modifier.isStatic(suiteMethod.getModifiers())) {
+ throw new Exception(klass.getName() + ".suite() must be static");
+ }
+ suite = (Test) suiteMethod.invoke(null); // static method
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ return suite;
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/TestClass.java b/src/main/java/org/junit/internal/runners/TestClass.java
index 1ca2b9d..1abaeea 100644
--- a/src/main/java/org/junit/internal/runners/TestClass.java
+++ b/src/main/java/org/junit/internal/runners/TestClass.java
@@ -11,92 +11,99 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.internal.MethodSorter;
import org.junit.runners.BlockJUnit4ClassRunner;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
public class TestClass {
- private final Class<?> fClass;
-
- public TestClass(Class<?> klass) {
- fClass= klass;
- }
-
- public List<Method> getTestMethods() {
- return getAnnotatedMethods(Test.class);
- }
-
- List<Method> getBefores() {
- return getAnnotatedMethods(BeforeClass.class);
- }
-
- List<Method> getAfters() {
- return getAnnotatedMethods(AfterClass.class);
- }
-
- public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) {
- List<Method> results= new ArrayList<Method>();
- for (Class<?> eachClass : getSuperClasses(fClass)) {
- Method[] methods= eachClass.getDeclaredMethods();
- for (Method eachMethod : methods) {
- Annotation annotation= eachMethod.getAnnotation(annotationClass);
- if (annotation != null && ! isShadowed(eachMethod, results))
- results.add(eachMethod);
- }
- }
- if (runsTopToBottom(annotationClass))
- Collections.reverse(results);
- return results;
- }
-
- private boolean runsTopToBottom(Class< ? extends Annotation> annotation) {
- return annotation.equals(Before.class) || annotation.equals(BeforeClass.class);
- }
-
- private boolean isShadowed(Method method, List<Method> results) {
- for (Method each : results) {
- if (isShadowed(method, each))
- return true;
- }
- return false;
- }
-
- private boolean isShadowed(Method current, Method previous) {
- if (! previous.getName().equals(current.getName()))
- return false;
- if (previous.getParameterTypes().length != current.getParameterTypes().length)
- return false;
- for (int i= 0; i < previous.getParameterTypes().length; i++) {
- if (! previous.getParameterTypes()[i].equals(current.getParameterTypes()[i]))
- return false;
- }
- return true;
- }
-
- 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;
- }
-
- public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException {
- return fClass.getConstructor();
- }
-
- public Class<?> getJavaClass() {
- return fClass;
- }
-
- public String getName() {
- return fClass.getName();
- }
+ private final Class<?> klass;
+
+ public TestClass(Class<?> klass) {
+ this.klass = klass;
+ }
+
+ public List<Method> getTestMethods() {
+ return getAnnotatedMethods(Test.class);
+ }
+
+ List<Method> getBefores() {
+ return getAnnotatedMethods(BeforeClass.class);
+ }
+
+ List<Method> getAfters() {
+ return getAnnotatedMethods(AfterClass.class);
+ }
+
+ public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) {
+ List<Method> results = new ArrayList<Method>();
+ for (Class<?> eachClass : getSuperClasses(klass)) {
+ Method[] methods = MethodSorter.getDeclaredMethods(eachClass);
+ for (Method eachMethod : methods) {
+ Annotation annotation = eachMethod.getAnnotation(annotationClass);
+ if (annotation != null && !isShadowed(eachMethod, results)) {
+ results.add(eachMethod);
+ }
+ }
+ }
+ if (runsTopToBottom(annotationClass)) {
+ Collections.reverse(results);
+ }
+ return results;
+ }
+
+ private boolean runsTopToBottom(Class<? extends Annotation> annotation) {
+ return annotation.equals(Before.class) || annotation.equals(BeforeClass.class);
+ }
+
+ private boolean isShadowed(Method method, List<Method> results) {
+ for (Method each : results) {
+ if (isShadowed(method, each)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isShadowed(Method current, Method previous) {
+ if (!previous.getName().equals(current.getName())) {
+ return false;
+ }
+ if (previous.getParameterTypes().length != current.getParameterTypes().length) {
+ return false;
+ }
+ for (int i = 0; i < previous.getParameterTypes().length; i++) {
+ if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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;
+ }
+
+ public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException {
+ return klass.getConstructor();
+ }
+
+ public Class<?> getJavaClass() {
+ return klass;
+ }
+
+ public String getName() {
+ return klass.getName();
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/TestMethod.java b/src/main/java/org/junit/internal/runners/TestMethod.java
index a06213c..821e193 100644
--- a/src/main/java/org/junit/internal/runners/TestMethod.java
+++ b/src/main/java/org/junit/internal/runners/TestMethod.java
@@ -13,57 +13,59 @@ import org.junit.runners.BlockJUnit4ClassRunner;
/**
* @deprecated Included for backwards compatibility with JUnit 4.4. Will be
- * removed in the next release. Please use
+ * removed in the next major release. Please use
* {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}.
*/
@Deprecated
public class TestMethod {
- private final Method fMethod;
- private TestClass fTestClass;
+ private final Method method;
+ private TestClass testClass;
- public TestMethod(Method method, TestClass testClass) {
- fMethod= method;
- fTestClass= testClass;
- }
+ public TestMethod(Method method, TestClass testClass) {
+ this.method = method;
+ this.testClass = testClass;
+ }
- public boolean isIgnored() {
- return fMethod.getAnnotation(Ignore.class) != null;
- }
+ public boolean isIgnored() {
+ return method.getAnnotation(Ignore.class) != null;
+ }
- public long getTimeout() {
- Test annotation= fMethod.getAnnotation(Test.class);
- if (annotation == null)
- return 0;
- long timeout= annotation.timeout();
- return timeout;
- }
+ public long getTimeout() {
+ Test annotation = method.getAnnotation(Test.class);
+ if (annotation == null) {
+ return 0;
+ }
+ long timeout = annotation.timeout();
+ return timeout;
+ }
- protected Class<? extends Throwable> getExpectedException() {
- Test annotation= fMethod.getAnnotation(Test.class);
- if (annotation == null || annotation.expected() == None.class)
- return null;
- else
- return annotation.expected();
- }
+ protected Class<? extends Throwable> getExpectedException() {
+ Test annotation = method.getAnnotation(Test.class);
+ if (annotation == null || annotation.expected() == None.class) {
+ return null;
+ } else {
+ return annotation.expected();
+ }
+ }
- boolean isUnexpected(Throwable exception) {
- return ! getExpectedException().isAssignableFrom(exception.getClass());
- }
+ boolean isUnexpected(Throwable exception) {
+ return !getExpectedException().isAssignableFrom(exception.getClass());
+ }
- boolean expectsException() {
- return getExpectedException() != null;
- }
+ boolean expectsException() {
+ return getExpectedException() != null;
+ }
- List<Method> getBefores() {
- return fTestClass.getAnnotatedMethods(Before.class);
- }
+ List<Method> getBefores() {
+ return testClass.getAnnotatedMethods(Before.class);
+ }
- List<Method> getAfters() {
- return fTestClass.getAnnotatedMethods(After.class);
- }
+ List<Method> getAfters() {
+ return testClass.getAnnotatedMethods(After.class);
+ }
- public void invoke(Object test) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
- fMethod.invoke(test);
- }
+ public void invoke(Object test) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ method.invoke(test);
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
index a7d534c..e094809 100644
--- a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
+++ b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.runners.model;
import org.junit.internal.AssumptionViolatedException;
@@ -10,42 +7,42 @@ import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.MultipleFailureException;
public class EachTestNotifier {
- private final RunNotifier fNotifier;
-
- private final Description fDescription;
-
- public EachTestNotifier(RunNotifier notifier, Description description) {
- fNotifier= notifier;
- fDescription= description;
- }
-
- public void addFailure(Throwable targetException) {
- if (targetException instanceof MultipleFailureException) {
- addMultipleFailureException((MultipleFailureException) targetException);
- } else {
- fNotifier
- .fireTestFailure(new Failure(fDescription, targetException));
- }
- }
-
- private void addMultipleFailureException(MultipleFailureException mfe) {
- for (Throwable each : mfe.getFailures())
- addFailure(each);
- }
-
- public void addFailedAssumption(AssumptionViolatedException e) {
- fNotifier.fireTestAssumptionFailed(new Failure(fDescription, e));
- }
-
- public void fireTestFinished() {
- fNotifier.fireTestFinished(fDescription);
- }
-
- public void fireTestStarted() {
- fNotifier.fireTestStarted(fDescription);
- }
-
- public void fireTestIgnored() {
- fNotifier.fireTestIgnored(fDescription);
- }
+ private final RunNotifier notifier;
+
+ private final Description description;
+
+ public EachTestNotifier(RunNotifier notifier, Description description) {
+ this.notifier = notifier;
+ this.description = description;
+ }
+
+ public void addFailure(Throwable targetException) {
+ if (targetException instanceof MultipleFailureException) {
+ addMultipleFailureException((MultipleFailureException) targetException);
+ } else {
+ notifier.fireTestFailure(new Failure(description, targetException));
+ }
+ }
+
+ private void addMultipleFailureException(MultipleFailureException mfe) {
+ for (Throwable each : mfe.getFailures()) {
+ addFailure(each);
+ }
+ }
+
+ public void addFailedAssumption(AssumptionViolatedException e) {
+ notifier.fireTestAssumptionFailed(new Failure(description, e));
+ }
+
+ public void fireTestFinished() {
+ notifier.fireTestFinished(description);
+ }
+
+ public void fireTestStarted() {
+ notifier.fireTestStarted(description);
+ }
+
+ public void fireTestIgnored() {
+ notifier.fireTestIgnored(description);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java b/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java
index 3316806..054f042 100644
--- a/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java
+++ b/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java
@@ -4,9 +4,9 @@ import java.util.List;
@Deprecated
public class MultipleFailureException extends org.junit.runners.model.MultipleFailureException {
- private static final long serialVersionUID= 1L;
+ private static final long serialVersionUID = 1L;
- public MultipleFailureException(List<Throwable> errors) {
- super(errors);
- }
+ public MultipleFailureException(List<Throwable> errors) {
+ super(errors);
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java b/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java
index 9150d90..79d5c05 100644
--- a/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java
+++ b/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.runners.model;
import java.lang.reflect.InvocationTargetException;
@@ -10,13 +7,13 @@ import java.lang.reflect.InvocationTargetException;
* wrapping it in an InvocationTargetException.
*/
public abstract class ReflectiveCallable {
- public Object run() throws Throwable {
- try {
- return runReflectiveCall();
- } catch (InvocationTargetException e) {
- throw e.getTargetException();
- }
- }
+ public Object run() throws Throwable {
+ try {
+ return runReflectiveCall();
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
- protected abstract Object runReflectiveCall() throws Throwable;
+ protected abstract Object runReflectiveCall() throws Throwable;
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java
deleted file mode 100644
index e7df8bf..0000000
--- a/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.junit.internal.runners.rules;
-
-import java.lang.annotation.Annotation;
-import java.util.List;
-
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.FrameworkField;
-import org.junit.runners.model.TestClass;
-
-/**
- * A RuleFieldValidator validates the rule fields of a
- * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the
- * {@code TestClass} are written to a list of errors.
- *
- * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
- * validates fields with a {@link ClassRule} annotation and the
- * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.
- */
-public enum RuleFieldValidator {
- /**
- * Validates fields with a {@link ClassRule} annotation.
- */
- CLASS_RULE_VALIDATOR(ClassRule.class, true),
- /**
- * Validates fields with a {@link Rule} annotation.
- */
- RULE_VALIDATOR(Rule.class, false);
-
- private final Class<? extends Annotation> fAnnotation;
-
- private final boolean fOnlyStaticFields;
-
- private RuleFieldValidator(Class<? extends Annotation> annotation,
- boolean onlyStaticFields) {
- this.fAnnotation= annotation;
- this.fOnlyStaticFields= onlyStaticFields;
- }
-
- /**
- * Validate the {@link org.junit.runners.model.TestClass} and adds reasons
- * for rejecting the class to a list of errors.
- * @param target the {@code TestClass} to validate.
- * @param errors the list of errors.
- */
- public void validate(TestClass target, List<Throwable> errors) {
- List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation);
- for (FrameworkField each : fields)
- validateField(each, errors);
- }
-
- private void validateField(FrameworkField field, List<Throwable> errors) {
- optionallyValidateStatic(field, errors);
- validatePublic(field, errors);
- validateTestRuleOrMethodRule(field, errors);
- }
-
- private void optionallyValidateStatic(FrameworkField field,
- List<Throwable> errors) {
- if (fOnlyStaticFields && !field.isStatic())
- addError(errors, field, "must be static.");
- }
-
- private void validatePublic(FrameworkField field, List<Throwable> errors) {
- if (!field.isPublic())
- addError(errors, field, "must be public.");
- }
-
- private void validateTestRuleOrMethodRule(FrameworkField field,
- List<Throwable> errors) {
- if (!isMethodRule(field) && !isTestRule(field))
- addError(errors, field, "must implement MethodRule or TestRule.");
- }
-
- private boolean isTestRule(FrameworkField target) {
- return TestRule.class.isAssignableFrom(target.getType());
- }
-
- @SuppressWarnings("deprecation")
- private boolean isMethodRule(FrameworkField target) {
- return org.junit.rules.MethodRule.class.isAssignableFrom(target
- .getType());
- }
-
- private void addError(List<Throwable> errors, FrameworkField field,
- String suffix) {
- String message= "The @" + fAnnotation.getSimpleName() + " '"
- + field.getName() + "' " + suffix;
- errors.add(new Exception(message));
- }
-}
diff --git a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
index 23f921b..36de4f1 100644
--- a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
+++ b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
@@ -1,91 +1,279 @@
package org.junit.internal.runners.rules;
-import java.lang.annotation.Annotation;
-import java.util.List;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.rules.MethodRule;
import org.junit.rules.TestRule;
-import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMember;
import org.junit.runners.model.TestClass;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
/**
- * A RuleFieldValidator validates the rule fields of a
- * {@link TestClass}. All reasons for rejecting the
+ * A RuleMemberValidator validates the rule fields/methods of a
+ * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the
* {@code TestClass} are written to a list of errors.
*
- * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
+ * <p>There are four slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
* validates fields with a {@link ClassRule} annotation and the
- * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.
+ * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.</p>
+ *
+ * <p>The {@link #CLASS_RULE_METHOD_VALIDATOR}
+ * validates methods with a {@link ClassRule} annotation and the
+ * {@link #RULE_METHOD_VALIDATOR} validates methods with a {@link Rule} annotation.</p>
*/
-public enum RuleMemberValidator {
- /**
- * Validates fields with a {@link ClassRule} annotation.
- */
- CLASS_RULE_VALIDATOR(ClassRule.class, true),
- /**
- * Validates fields with a {@link Rule} annotation.
- */
- RULE_VALIDATOR(Rule.class, false);
-
- private final Class<? extends Annotation> fAnnotation;
-
- private final boolean fOnlyStaticFields;
-
- private RuleMemberValidator(Class<? extends Annotation> annotation,
- boolean onlyStaticFields) {
- this.fAnnotation= annotation;
- this.fOnlyStaticFields= onlyStaticFields;
- }
-
- /**
- * Validate the {@link TestClass} and adds reasons
- * for rejecting the class to a list of errors.
- * @param target the {@code TestClass} to validate.
- * @param errors the list of errors.
- */
- public void validate(TestClass target, List<Throwable> errors) {
- List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation);
- for (FrameworkField each : fields)
- validateField(each, errors);
- }
-
- private void validateField(FrameworkField field, List<Throwable> errors) {
- optionallyValidateStatic(field, errors);
- validatePublic(field, errors);
- validateTestRuleOrMethodRule(field, errors);
- }
-
- private void optionallyValidateStatic(FrameworkField field,
- List<Throwable> errors) {
- if (fOnlyStaticFields && !field.isStatic())
- addError(errors, field, "must be static.");
- }
-
- private void validatePublic(FrameworkField field, List<Throwable> errors) {
- if (!field.isPublic())
- addError(errors, field, "must be public.");
- }
-
- private void validateTestRuleOrMethodRule(FrameworkField field,
- List<Throwable> errors) {
- if (!isMethodRule(field) && !isTestRule(field))
- addError(errors, field, "must implement MethodRule or TestRule.");
- }
-
- private boolean isTestRule(FrameworkField target) {
- return TestRule.class.isAssignableFrom(target.getType());
- }
-
- @SuppressWarnings("deprecation")
- private boolean isMethodRule(FrameworkField target) {
- return org.junit.rules.MethodRule.class.isAssignableFrom(target
- .getType());
- }
-
- private void addError(List<Throwable> errors, FrameworkField field,
- String suffix) {
- String message= "The @" + fAnnotation.getSimpleName() + " '"
- + field.getName() + "' " + suffix;
- errors.add(new Exception(message));
- }
+public class RuleMemberValidator {
+ /**
+ * Validates fields with a {@link ClassRule} annotation.
+ */
+ public static final RuleMemberValidator CLASS_RULE_VALIDATOR =
+ classRuleValidatorBuilder()
+ .withValidator(new DeclaringClassMustBePublic())
+ .withValidator(new MemberMustBeStatic())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new FieldMustBeATestRule())
+ .build();
+ /**
+ * Validates fields with a {@link Rule} annotation.
+ */
+ public static final RuleMemberValidator RULE_VALIDATOR =
+ testRuleValidatorBuilder()
+ .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new FieldMustBeARule())
+ .build();
+ /**
+ * Validates methods with a {@link ClassRule} annotation.
+ */
+ public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR =
+ classRuleValidatorBuilder()
+ .forMethods()
+ .withValidator(new DeclaringClassMustBePublic())
+ .withValidator(new MemberMustBeStatic())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new MethodMustBeATestRule())
+ .build();
+
+ /**
+ * Validates methods with a {@link Rule} annotation.
+ */
+ public static final RuleMemberValidator RULE_METHOD_VALIDATOR =
+ testRuleValidatorBuilder()
+ .forMethods()
+ .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new MethodMustBeARule())
+ .build();
+
+ private final Class<? extends Annotation> annotation;
+ private final boolean methods;
+ private final List<RuleValidator> validatorStrategies;
+
+ RuleMemberValidator(Builder builder) {
+ this.annotation = builder.annotation;
+ this.methods = builder.methods;
+ this.validatorStrategies = builder.validators;
+ }
+
+ /**
+ * Validate the {@link org.junit.runners.model.TestClass} and adds reasons
+ * for rejecting the class to a list of errors.
+ *
+ * @param target the {@code TestClass} to validate.
+ * @param errors the list of errors.
+ */
+ public void validate(TestClass target, List<Throwable> errors) {
+ List<? extends FrameworkMember<?>> members = methods ? target.getAnnotatedMethods(annotation)
+ : target.getAnnotatedFields(annotation);
+
+ for (FrameworkMember<?> each : members) {
+ validateMember(each, errors);
+ }
+ }
+
+ private void validateMember(FrameworkMember<?> member, List<Throwable> errors) {
+ for (RuleValidator strategy : validatorStrategies) {
+ strategy.validate(member, annotation, errors);
+ }
+ }
+
+ private static Builder classRuleValidatorBuilder() {
+ return new Builder(ClassRule.class);
+ }
+
+ private static Builder testRuleValidatorBuilder() {
+ return new Builder(Rule.class);
+ }
+
+ private static class Builder {
+ private final Class<? extends Annotation> annotation;
+ private boolean methods;
+ private final List<RuleValidator> validators;
+
+ private Builder(Class<? extends Annotation> annotation) {
+ this.annotation = annotation;
+ this.methods = false;
+ this.validators = new ArrayList<RuleValidator>();
+ }
+
+ Builder forMethods() {
+ methods = true;
+ return this;
+ }
+
+ Builder withValidator(RuleValidator validator) {
+ validators.add(validator);
+ return this;
+ }
+
+ RuleMemberValidator build() {
+ return new RuleMemberValidator(this);
+ }
+ }
+
+ private static boolean isRuleType(FrameworkMember<?> member) {
+ return isMethodRule(member) || isTestRule(member);
+ }
+
+ private static boolean isTestRule(FrameworkMember<?> member) {
+ return TestRule.class.isAssignableFrom(member.getType());
+ }
+
+ private static boolean isMethodRule(FrameworkMember<?> member) {
+ return MethodRule.class.isAssignableFrom(member.getType());
+ }
+
+ /**
+ * Encapsulates a single piece of validation logic, used to determine if {@link org.junit.Rule} and
+ * {@link org.junit.ClassRule} annotations have been used correctly
+ */
+ interface RuleValidator {
+ /**
+ * Examine the given member and add any violations of the strategy's validation logic to the given list of errors
+ * @param member The member (field or member) to examine
+ * @param annotation The type of rule annotation on the member
+ * @param errors The list of errors to add validation violations to
+ */
+ void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors);
+ }
+
+ /**
+ * Requires the validated member to be non-static
+ */
+ private static final class MemberMustBeNonStaticOrAlsoClassRule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ boolean isMethodRuleMember = isMethodRule(member);
+ boolean isClassRuleAnnotated = (member.getAnnotation(ClassRule.class) != null);
+
+ // We disallow:
+ // - static MethodRule members
+ // - static @Rule annotated members
+ // - UNLESS they're also @ClassRule annotated
+ // Note that MethodRule cannot be annotated with @ClassRule
+ if (member.isStatic() && (isMethodRuleMember || !isClassRuleAnnotated)) {
+ String message;
+ if (isMethodRule(member)) {
+ message = "must not be static.";
+ } else {
+ message = "must not be static or it must be annotated with @ClassRule.";
+ }
+ errors.add(new ValidationError(member, annotation, message));
+ }
+ }
+ }
+
+ /**
+ * Requires the member to be static
+ */
+ private static final class MemberMustBeStatic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!member.isStatic()) {
+ errors.add(new ValidationError(member, annotation,
+ "must be static."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member's declaring class to be public
+ */
+ private static final class DeclaringClassMustBePublic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isDeclaringClassPublic(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must be declared in a public class."));
+ }
+ }
+
+ private boolean isDeclaringClassPublic(FrameworkMember<?> member) {
+ return Modifier.isPublic(member.getDeclaringClass().getModifiers());
+ }
+ }
+
+ /**
+ * Requires the member to be public
+ */
+ private static final class MemberMustBePublic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!member.isPublic()) {
+ errors.add(new ValidationError(member, annotation,
+ "must be public."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member is a field implementing {@link org.junit.rules.MethodRule} or {@link org.junit.rules.TestRule}
+ */
+ private static final class FieldMustBeARule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isRuleType(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must implement MethodRule or TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Require the member to return an implementation of {@link org.junit.rules.MethodRule} or
+ * {@link org.junit.rules.TestRule}
+ */
+ private static final class MethodMustBeARule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isRuleType(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must return an implementation of MethodRule or TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Require the member to return an implementation of {@link org.junit.rules.TestRule}
+ */
+ private static final class MethodMustBeATestRule implements RuleValidator {
+ public void validate(FrameworkMember<?> member,
+ Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isTestRule(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must return an implementation of TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member is a field implementing {@link org.junit.rules.TestRule}
+ */
+ private static final class FieldMustBeATestRule implements RuleValidator {
+
+ public void validate(FrameworkMember<?> member,
+ Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isTestRule(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must implement TestRule."));
+ }
+ }
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/rules/ValidationError.java b/src/main/java/org/junit/internal/runners/rules/ValidationError.java
new file mode 100644
index 0000000..d1af8ae
--- /dev/null
+++ b/src/main/java/org/junit/internal/runners/rules/ValidationError.java
@@ -0,0 +1,11 @@
+package org.junit.internal.runners.rules;
+
+import org.junit.runners.model.FrameworkMember;
+
+import java.lang.annotation.Annotation;
+
+class ValidationError extends Exception {
+ public ValidationError(FrameworkMember<?> member, Class<? extends Annotation> annotation, String suffix) {
+ super(String.format("The @%s '%s' %s", annotation.getSimpleName(), member.getName(), suffix));
+ }
+}
diff --git a/src/main/java/org/junit/internal/runners/statements/ExpectException.java b/src/main/java/org/junit/internal/runners/statements/ExpectException.java
index ddfef07..d0636bd 100644
--- a/src/main/java/org/junit/internal/runners/statements/ExpectException.java
+++ b/src/main/java/org/junit/internal/runners/statements/ExpectException.java
@@ -1,38 +1,36 @@
-/**
- *
- */
package org.junit.internal.runners.statements;
import org.junit.internal.AssumptionViolatedException;
import org.junit.runners.model.Statement;
public class ExpectException extends Statement {
- private Statement fNext;
- private final Class<? extends Throwable> fExpected;
-
- public ExpectException(Statement next, Class<? extends Throwable> expected) {
- fNext= next;
- fExpected= expected;
- }
-
- @Override
- public void evaluate() throws Exception {
- boolean complete = false;
- try {
- fNext.evaluate();
- complete = true;
- } catch (AssumptionViolatedException e) {
- throw e;
- } catch (Throwable e) {
- if (!fExpected.isAssignableFrom(e.getClass())) {
- String message= "Unexpected exception, expected<"
- + fExpected.getName() + "> but was<"
- + e.getClass().getName() + ">";
- throw new Exception(message, e);
- }
- }
- if (complete)
- throw new AssertionError("Expected exception: "
- + fExpected.getName());
- }
+ private final Statement next;
+ private final Class<? extends Throwable> expected;
+
+ public ExpectException(Statement next, Class<? extends Throwable> expected) {
+ this.next = next;
+ this.expected = expected;
+ }
+
+ @Override
+ public void evaluate() throws Exception {
+ boolean complete = false;
+ try {
+ next.evaluate();
+ complete = true;
+ } catch (AssumptionViolatedException e) {
+ throw e;
+ } catch (Throwable e) {
+ if (!expected.isAssignableFrom(e.getClass())) {
+ String message = "Unexpected exception, expected<"
+ + expected.getName() + "> but was<"
+ + e.getClass().getName() + ">";
+ throw new Exception(message, e);
+ }
+ }
+ if (complete) {
+ throw new AssertionError("Expected exception: "
+ + expected.getName());
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/statements/Fail.java b/src/main/java/org/junit/internal/runners/statements/Fail.java
index e7d0d5c..e55875c 100644
--- a/src/main/java/org/junit/internal/runners/statements/Fail.java
+++ b/src/main/java/org/junit/internal/runners/statements/Fail.java
@@ -2,16 +2,15 @@ package org.junit.internal.runners.statements;
import org.junit.runners.model.Statement;
-
public class Fail extends Statement {
- private final Throwable fError;
+ private final Throwable error;
- public Fail(Throwable e) {
- fError= e;
- }
+ public Fail(Throwable e) {
+ error = e;
+ }
- @Override
- public void evaluate() throws Throwable {
- throw fError;
- }
+ @Override
+ public void evaluate() throws Throwable {
+ throw error;
+ }
}
diff --git a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
index bff7c72..7f4f0d5 100644
--- a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
+++ b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
@@ -1,71 +1,311 @@
-/**
- *
- */
package org.junit.internal.runners.statements;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestTimedOutException;
public class FailOnTimeout extends Statement {
- private final Statement fOriginalStatement;
-
- private final long fTimeout;
-
- public FailOnTimeout(Statement originalStatement, long timeout) {
- fOriginalStatement= originalStatement;
- fTimeout= timeout;
- }
-
- @Override
- public void evaluate() throws Throwable {
- StatementThread thread= evaluateStatement();
- if (!thread.fFinished)
- throwExceptionForUnfinishedThread(thread);
- }
-
- private StatementThread evaluateStatement() throws InterruptedException {
- StatementThread thread= new StatementThread(fOriginalStatement);
- thread.start();
- thread.join(fTimeout);
- thread.interrupt();
- return thread;
- }
-
- private void throwExceptionForUnfinishedThread(StatementThread thread)
- throws Throwable {
- if (thread.fExceptionThrownByOriginalStatement != null)
- throw thread.fExceptionThrownByOriginalStatement;
- else
- throwTimeoutException(thread);
- }
-
- private void throwTimeoutException(StatementThread thread) throws Exception {
- Exception exception= new Exception(String.format(
- "test timed out after %d milliseconds", fTimeout));
- exception.setStackTrace(thread.getStackTrace());
- throw exception;
- }
-
- private static class StatementThread extends Thread {
- private final Statement fStatement;
-
- private boolean fFinished= false;
-
- private Throwable fExceptionThrownByOriginalStatement= null;
-
- public StatementThread(Statement statement) {
- fStatement= statement;
- }
-
- @Override
- public void run() {
- try {
- fStatement.evaluate();
- fFinished= true;
- } catch (InterruptedException e) {
- //don't log the InterruptedException
- } catch (Throwable e) {
- fExceptionThrownByOriginalStatement= e;
- }
- }
- }
-} \ No newline at end of file
+ private final Statement originalStatement;
+ private final TimeUnit timeUnit;
+ private final long timeout;
+ private final boolean lookForStuckThread;
+ private volatile ThreadGroup threadGroup = null;
+
+ /**
+ * Returns a new builder for building an instance.
+ *
+ * @since 4.12
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates an instance wrapping the given statement with the given timeout in milliseconds.
+ *
+ * @param statement the statement to wrap
+ * @param timeoutMillis the timeout in milliseconds
+ * @deprecated use {@link #builder()} instead.
+ */
+ @Deprecated
+ public FailOnTimeout(Statement statement, long timeoutMillis) {
+ this(builder().withTimeout(timeoutMillis, TimeUnit.MILLISECONDS), statement);
+ }
+
+ private FailOnTimeout(Builder builder, Statement statement) {
+ originalStatement = statement;
+ timeout = builder.timeout;
+ timeUnit = builder.unit;
+ lookForStuckThread = builder.lookForStuckThread;
+ }
+
+ /**
+ * Builder for {@link FailOnTimeout}.
+ *
+ * @since 4.12
+ */
+ public static class Builder {
+ private boolean lookForStuckThread = false;
+ private long timeout = 0;
+ private TimeUnit unit = TimeUnit.SECONDS;
+
+ private Builder() {
+ }
+
+ /**
+ * Specifies the time to wait before timing out the test.
+ *
+ * <p>If this is not called, or is called with a {@code timeout} of
+ * {@code 0}, the returned {@code Statement} will wait forever for the
+ * test to complete, however the test will still launch from a separate
+ * thread. This can be useful for disabling timeouts in environments
+ * where they are dynamically set based on some property.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the {@code timeout} argument
+ * @return {@code this} for method chaining.
+ */
+ public Builder withTimeout(long timeout, TimeUnit unit) {
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout must be non-negative");
+ }
+ if (unit == null) {
+ throw new NullPointerException("TimeUnit cannot be null");
+ }
+ this.timeout = timeout;
+ this.unit = unit;
+ return this;
+ }
+
+ /**
+ * Specifies whether to look for a stuck thread. If a timeout occurs and this
+ * feature is enabled, the test will look for a thread that appears to be stuck
+ * and dump its backtrace. This feature is experimental. Behavior may change
+ * after the 4.12 release in response to feedback.
+ *
+ * @param enable {@code true} to enable the feature
+ * @return {@code this} for method chaining.
+ */
+ public Builder withLookingForStuckThread(boolean enable) {
+ this.lookForStuckThread = enable;
+ return this;
+ }
+
+ /**
+ * Builds a {@link FailOnTimeout} instance using the values in this builder,
+ * wrapping the given statement.
+ *
+ * @param statement
+ */
+ public FailOnTimeout build(Statement statement) {
+ if (statement == null) {
+ throw new NullPointerException("statement cannot be null");
+ }
+ return new FailOnTimeout(this, statement);
+ }
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ CallableStatement callable = new CallableStatement();
+ FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
+ threadGroup = new ThreadGroup("FailOnTimeoutGroup");
+ Thread thread = new Thread(threadGroup, task, "Time-limited test");
+ thread.setDaemon(true);
+ thread.start();
+ callable.awaitStarted();
+ Throwable throwable = getResult(task, thread);
+ if (throwable != null) {
+ throw throwable;
+ }
+ }
+
+ /**
+ * Wait for the test task, returning the exception thrown by the test if the
+ * test failed, an exception indicating a timeout if the test timed out, or
+ * {@code null} if the test passed.
+ */
+ private Throwable getResult(FutureTask<Throwable> task, Thread thread) {
+ try {
+ if (timeout > 0) {
+ return task.get(timeout, timeUnit);
+ } else {
+ return task.get();
+ }
+ } catch (InterruptedException e) {
+ return e; // caller will re-throw; no need to call Thread.interrupt()
+ } catch (ExecutionException e) {
+ // test failed; have caller re-throw the exception thrown by the test
+ return e.getCause();
+ } catch (TimeoutException e) {
+ return createTimeoutException(thread);
+ }
+ }
+
+ private Exception createTimeoutException(Thread thread) {
+ StackTraceElement[] stackTrace = thread.getStackTrace();
+ final Thread stuckThread = lookForStuckThread ? getStuckThread(thread) : null;
+ Exception currThreadException = new TestTimedOutException(timeout, timeUnit);
+ if (stackTrace != null) {
+ currThreadException.setStackTrace(stackTrace);
+ thread.interrupt();
+ }
+ if (stuckThread != null) {
+ Exception stuckThreadException =
+ new Exception ("Appears to be stuck in thread " +
+ stuckThread.getName());
+ stuckThreadException.setStackTrace(getStackTrace(stuckThread));
+ return new MultipleFailureException(
+ Arrays.<Throwable>asList(currThreadException, stuckThreadException));
+ } else {
+ return currThreadException;
+ }
+ }
+
+ /**
+ * Retrieves the stack trace for a given thread.
+ * @param thread The thread whose stack is to be retrieved.
+ * @return The stack trace; returns a zero-length array if the thread has
+ * terminated or the stack cannot be retrieved for some other reason.
+ */
+ private StackTraceElement[] getStackTrace(Thread thread) {
+ try {
+ return thread.getStackTrace();
+ } catch (SecurityException e) {
+ return new StackTraceElement[0];
+ }
+ }
+
+ /**
+ * Determines whether the test appears to be stuck in some thread other than
+ * the "main thread" (the one created to run the test). This feature is experimental.
+ * Behavior may change after the 4.12 release in response to feedback.
+ * @param mainThread The main thread created by {@code evaluate()}
+ * @return The thread which appears to be causing the problem, if different from
+ * {@code mainThread}, or {@code null} if the main thread appears to be the
+ * problem or if the thread cannot be determined. The return value is never equal
+ * to {@code mainThread}.
+ */
+ private Thread getStuckThread(Thread mainThread) {
+ if (threadGroup == null) {
+ return null;
+ }
+ Thread[] threadsInGroup = getThreadArray(threadGroup);
+ if (threadsInGroup == null) {
+ return null;
+ }
+
+ // Now that we have all the threads in the test's thread group: Assume that
+ // any thread we're "stuck" in is RUNNABLE. Look for all RUNNABLE threads.
+ // If just one, we return that (unless it equals threadMain). If there's more
+ // than one, pick the one that's using the most CPU time, if this feature is
+ // supported.
+ Thread stuckThread = null;
+ long maxCpuTime = 0;
+ for (Thread thread : threadsInGroup) {
+ if (thread.getState() == Thread.State.RUNNABLE) {
+ long threadCpuTime = cpuTime(thread);
+ if (stuckThread == null || threadCpuTime > maxCpuTime) {
+ stuckThread = thread;
+ maxCpuTime = threadCpuTime;
+ }
+ }
+ }
+ return (stuckThread == mainThread) ? null : stuckThread;
+ }
+
+ /**
+ * Returns all active threads belonging to a thread group.
+ * @param group The thread group.
+ * @return The active threads in the thread group. The result should be a
+ * complete list of the active threads at some point in time. Returns {@code null}
+ * if this cannot be determined, e.g. because new threads are being created at an
+ * extremely fast rate.
+ */
+ private Thread[] getThreadArray(ThreadGroup group) {
+ final int count = group.activeCount(); // this is just an estimate
+ int enumSize = Math.max(count * 2, 100);
+ int enumCount;
+ Thread[] threads;
+ int loopCount = 0;
+ while (true) {
+ threads = new Thread[enumSize];
+ enumCount = group.enumerate(threads);
+ if (enumCount < enumSize) {
+ break;
+ }
+ // if there are too many threads to fit into the array, enumerate's result
+ // is >= the array's length; therefore we can't trust that it returned all
+ // the threads. Try again.
+ enumSize += 100;
+ if (++loopCount >= 5) {
+ return null;
+ }
+ // threads are proliferating too fast for us. Bail before we get into
+ // trouble.
+ }
+ return copyThreads(threads, enumCount);
+ }
+
+ /**
+ * Returns an array of the first {@code count} Threads in {@code threads}.
+ * (Use instead of Arrays.copyOf to maintain compatibility with Java 1.5.)
+ * @param threads The source array.
+ * @param count The maximum length of the result array.
+ * @return The first {@count} (at most) elements of {@code threads}.
+ */
+ private Thread[] copyThreads(Thread[] threads, int count) {
+ int length = Math.min(count, threads.length);
+ Thread[] result = new Thread[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = threads[i];
+ }
+ return result;
+ }
+
+ /**
+ * Returns the CPU time used by a thread, if possible.
+ * @param thr The thread to query.
+ * @return The CPU time used by {@code thr}, or 0 if it cannot be determined.
+ */
+ private long cpuTime (Thread thr) {
+ ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
+ if (mxBean.isThreadCpuTimeSupported()) {
+ try {
+ return mxBean.getThreadCpuTime(thr.getId());
+ } catch (UnsupportedOperationException e) {
+ }
+ }
+ return 0;
+ }
+
+ private class CallableStatement implements Callable<Throwable> {
+ private final CountDownLatch startLatch = new CountDownLatch(1);
+
+ public Throwable call() throws Exception {
+ try {
+ startLatch.countDown();
+ originalStatement.evaluate();
+ } catch (Exception e) {
+ throw e;
+ } catch (Throwable e) {
+ return e;
+ }
+ return null;
+ }
+
+ public void awaitStarted() throws InterruptedException {
+ startLatch.await();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java b/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java
index e2e81e1..68c0545 100644
--- a/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java
+++ b/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java
@@ -1,22 +1,19 @@
-/**
- *
- */
package org.junit.internal.runners.statements;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
public class InvokeMethod extends Statement {
- private final FrameworkMethod fTestMethod;
- private Object fTarget;
-
- public InvokeMethod(FrameworkMethod testMethod, Object target) {
- fTestMethod= testMethod;
- fTarget= target;
- }
-
- @Override
- public void evaluate() throws Throwable {
- fTestMethod.invokeExplosively(fTarget);
- }
+ private final FrameworkMethod testMethod;
+ private final Object target;
+
+ public InvokeMethod(FrameworkMethod testMethod, Object target) {
+ this.testMethod = testMethod;
+ this.target = target;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ testMethod.invokeExplosively(target);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/statements/RunAfters.java b/src/main/java/org/junit/internal/runners/statements/RunAfters.java
index 475ec72..7512a7d 100644
--- a/src/main/java/org/junit/internal/runners/statements/RunAfters.java
+++ b/src/main/java/org/junit/internal/runners/statements/RunAfters.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.runners.statements;
import java.util.ArrayList;
@@ -11,33 +8,34 @@ import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
public class RunAfters extends Statement {
- private final Statement fNext;
+ private final Statement next;
- private final Object fTarget;
+ private final Object target;
- private final List<FrameworkMethod> fAfters;
-
- public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
- fNext= next;
- fAfters= afters;
- fTarget= target;
- }
+ private final List<FrameworkMethod> afters;
- @Override
- public void evaluate() throws Throwable {
- List<Throwable> errors = new ArrayList<Throwable>();
- try {
- fNext.evaluate();
- } catch (Throwable e) {
- errors.add(e);
- } finally {
- for (FrameworkMethod each : fAfters)
- try {
- each.invokeExplosively(fTarget);
- } catch (Throwable e) {
- errors.add(e);
- }
- }
- MultipleFailureException.assertEmpty(errors);
- }
+ public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
+ this.next = next;
+ this.afters = afters;
+ this.target = target;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ try {
+ next.evaluate();
+ } catch (Throwable e) {
+ errors.add(e);
+ } finally {
+ for (FrameworkMethod each : afters) {
+ try {
+ each.invokeExplosively(target);
+ } catch (Throwable e) {
+ errors.add(e);
+ }
+ }
+ }
+ MultipleFailureException.assertEmpty(errors);
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/statements/RunBefores.java b/src/main/java/org/junit/internal/runners/statements/RunBefores.java
index 66a34e1..238fbe7 100644
--- a/src/main/java/org/junit/internal/runners/statements/RunBefores.java
+++ b/src/main/java/org/junit/internal/runners/statements/RunBefores.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.internal.runners.statements;
import java.util.List;
@@ -9,22 +6,23 @@ import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
public class RunBefores extends Statement {
- private final Statement fNext;
+ private final Statement next;
- private final Object fTarget;
+ private final Object target;
- private final List<FrameworkMethod> fBefores;
+ private final List<FrameworkMethod> befores;
- public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
- fNext= next;
- fBefores= befores;
- fTarget= target;
- }
+ public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
+ this.next = next;
+ this.befores = befores;
+ this.target = target;
+ }
- @Override
- public void evaluate() throws Throwable {
- for (FrameworkMethod before : fBefores)
- before.invokeExplosively(fTarget);
- fNext.evaluate();
- }
+ @Override
+ public void evaluate() throws Throwable {
+ for (FrameworkMethod before : befores) {
+ before.invokeExplosively(target);
+ }
+ next.evaluate();
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/matchers/JUnitMatchers.java b/src/main/java/org/junit/matchers/JUnitMatchers.java
index 837ed33..13407cc 100644
--- a/src/main/java/org/junit/matchers/JUnitMatchers.java
+++ b/src/main/java/org/junit/matchers/JUnitMatchers.java
@@ -1,83 +1,113 @@
package org.junit.matchers;
+import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
-import org.junit.internal.matchers.CombinableMatcher;
-import org.junit.internal.matchers.Each;
-import org.junit.internal.matchers.IsCollectionContaining;
-import org.junit.internal.matchers.StringContains;
+import org.hamcrest.core.CombinableMatcher.CombinableBothMatcher;
+import org.hamcrest.core.CombinableMatcher.CombinableEitherMatcher;
+import org.junit.internal.matchers.StacktracePrintingMatcher;
/**
* Convenience import class: these are useful matchers for use with the assertThat method, but they are
* not currently included in the basic CoreMatchers class from hamcrest.
+ *
+ * @since 4.4
*/
public class JUnitMatchers {
- /**
- * @param element
- * @return A matcher matching any collection containing element
- */
- public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(T element) {
- return IsCollectionContaining.hasItem(element);
- }
+ /**
+ * @return A matcher matching any collection containing element
+ * @deprecated Please use {@link CoreMatchers#hasItem(Object)} instead.
+ */
+ @Deprecated
+ public static <T> Matcher<Iterable<? super T>> hasItem(T element) {
+ return CoreMatchers.hasItem(element);
+ }
- /**
- * @param elementMatcher
- * @return A matcher matching any collection containing an element matching elementMatcher
- */
- public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(org.hamcrest.Matcher<? extends T> elementMatcher) {
- return IsCollectionContaining.<T>hasItem(elementMatcher);
- }
+ /**
+ * @return A matcher matching any collection containing an element matching elementMatcher
+ * @deprecated Please use {@link CoreMatchers#hasItem(Matcher)} instead.
+ */
+ @Deprecated
+ public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> elementMatcher) {
+ return CoreMatchers.<T>hasItem(elementMatcher);
+ }
- /**
- * @param elements
- * @return A matcher matching any collection containing every element in elements
- */
- public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... elements) {
- return IsCollectionContaining.hasItems(elements);
- }
+ /**
+ * @return A matcher matching any collection containing every element in elements
+ * @deprecated Please use {@link CoreMatchers#hasItems(Object...)} instead.
+ */
+ @Deprecated
+ public static <T> Matcher<Iterable<T>> hasItems(T... elements) {
+ return CoreMatchers.hasItems(elements);
+ }
- /**
- * @param elementMatchers
- * @return A matcher matching any collection containing at least one element that matches
- * each matcher in elementMatcher (this may be one element matching all matchers,
- * or different elements matching each matcher)
- */
- public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? extends T>... elementMatchers) {
- return IsCollectionContaining.<T>hasItems(elementMatchers);
- }
+ /**
+ * @return A matcher matching any collection containing at least one element that matches
+ * each matcher in elementMatcher (this may be one element matching all matchers,
+ * or different elements matching each matcher)
+ * @deprecated Please use {@link CoreMatchers#hasItems(Matcher...)} instead.
+ */
+ @Deprecated
+ public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... elementMatchers) {
+ return CoreMatchers.hasItems(elementMatchers);
+ }
- /**
- * @param elementMatcher
- * @return A matcher matching any collection in which every element matches elementMatcher
- */
- public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) {
- return Each.each(elementMatcher);
- }
+ /**
+ * @return A matcher matching any collection in which every element matches elementMatcher
+ * @deprecated Please use {@link CoreMatchers#everyItem(Matcher)} instead.
+ */
+ @Deprecated
+ public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) {
+ return CoreMatchers.everyItem(elementMatcher);
+ }
- /**
- * @param substring
- * @return a matcher matching any string that contains substring
- */
- public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) {
- return StringContains.containsString(substring);
- }
-
- /**
- * This is useful for fluently combining matchers that must both pass. For example:
- * <pre>
- * assertThat(string, both(containsString("a")).and(containsString("b")));
- * </pre>
- */
- public static <T> CombinableMatcher<T> both(Matcher<T> matcher) {
- return new CombinableMatcher<T>(matcher);
- }
-
- /**
- * This is useful for fluently combining matchers where either may pass, for example:
- * <pre>
- * assertThat(string, either(containsString("a")).or(containsString("b")));
- * </pre>
- */
- public static <T> CombinableMatcher<T> either(Matcher<T> matcher) {
- return new CombinableMatcher<T>(matcher);
- }
+ /**
+ * @return a matcher matching any string that contains substring
+ * @deprecated Please use {@link CoreMatchers#containsString(String)} instead.
+ */
+ @Deprecated
+ public static Matcher<java.lang.String> containsString(java.lang.String substring) {
+ return CoreMatchers.containsString(substring);
+ }
+
+ /**
+ * This is useful for fluently combining matchers that must both pass. For example:
+ * <pre>
+ * assertThat(string, both(containsString("a")).and(containsString("b")));
+ * </pre>
+ *
+ * @deprecated Please use {@link CoreMatchers#both(Matcher)} instead.
+ */
+ @Deprecated
+ public static <T> CombinableBothMatcher<T> both(Matcher<? super T> matcher) {
+ return CoreMatchers.both(matcher);
+ }
+
+ /**
+ * This is useful for fluently combining matchers where either may pass, for example:
+ * <pre>
+ * assertThat(string, either(containsString("a")).or(containsString("b")));
+ * </pre>
+ *
+ * @deprecated Please use {@link CoreMatchers#either(Matcher)} instead.
+ */
+ @Deprecated
+ public static <T> CombinableEitherMatcher<T> either(Matcher<? super T> matcher) {
+ return CoreMatchers.either(matcher);
+ }
+
+ /**
+ * @return A matcher that delegates to throwableMatcher and in addition
+ * appends the stacktrace of the actual Throwable in case of a mismatch.
+ */
+ public static <T extends Throwable> Matcher<T> isThrowable(Matcher<T> throwableMatcher) {
+ return StacktracePrintingMatcher.isThrowable(throwableMatcher);
+ }
+
+ /**
+ * @return A matcher that delegates to exceptionMatcher and in addition
+ * appends the stacktrace of the actual Exception in case of a mismatch.
+ */
+ public static <T extends Exception> Matcher<T> isException(Matcher<T> exceptionMatcher) {
+ return StacktracePrintingMatcher.isException(exceptionMatcher);
+ }
}
diff --git a/src/main/java/org/junit/package-info.java b/src/main/java/org/junit/package-info.java
index bb60d0d..fb12f25 100644
--- a/src/main/java/org/junit/package-info.java
+++ b/src/main/java/org/junit/package-info.java
@@ -1,6 +1,6 @@
/**
* Provides JUnit core classes and annotations.
- *
+ *
* Corresponds to junit.framework in Junit 3.x.
*
* @since 4.0
diff --git a/src/main/java/org/junit/rules/DisableOnDebug.java b/src/main/java/org/junit/rules/DisableOnDebug.java
new file mode 100644
index 0000000..afa6dee
--- /dev/null
+++ b/src/main/java/org/junit/rules/DisableOnDebug.java
@@ -0,0 +1,127 @@
+package org.junit.rules;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.util.List;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * The {@code DisableOnDebug} Rule allows you to label certain rules to be
+ * disabled when debugging.
+ * <p>
+ * The most illustrative use case is for tests that make use of the
+ * {@link Timeout} rule, when ran in debug mode the test may terminate on
+ * timeout abruptly during debugging. Developers may disable the timeout, or
+ * increase the timeout by making a code change on tests that need debugging and
+ * remember revert the change afterwards or rules such as {@link Timeout} that
+ * may be disabled during debugging may be wrapped in a {@code DisableOnDebug}.
+ * <p>
+ * The important benefit of this feature is that you can disable such rules
+ * without any making any modifications to your test class to remove them during
+ * debugging.
+ * <p>
+ * This does nothing to tackle timeouts or time sensitive code under test when
+ * debugging and may make this less useful in such circumstances.
+ * <p>
+ * Example usage:
+ *
+ * <pre>
+ * public static class DisableTimeoutOnDebugSampleTest {
+ *
+ * &#064;Rule
+ * public TestRule timeout = new DisableOnDebug(new Timeout(20));
+ *
+ * &#064;Test
+ * public void myTest() {
+ * int i = 0;
+ * assertEquals(0, i); // suppose you had a break point here to inspect i
+ * }
+ * }
+ * </pre>
+ *
+ * @since 4.12
+ */
+public class DisableOnDebug implements TestRule {
+ private final TestRule rule;
+ private final boolean debugging;
+
+ /**
+ * Create a {@code DisableOnDebug} instance with the timeout specified in
+ * milliseconds.
+ *
+ * @param rule to disable during debugging
+ */
+ public DisableOnDebug(TestRule rule) {
+ this(rule, ManagementFactory.getRuntimeMXBean()
+ .getInputArguments());
+ }
+
+ /**
+ * Visible for testing purposes only.
+ *
+ * @param rule the rule to disable during debugging
+ * @param inputArguments
+ * arguments provided to the Java runtime
+ */
+ DisableOnDebug(TestRule rule, List<String> inputArguments) {
+ this.rule = rule;
+ debugging = isDebugging(inputArguments);
+ }
+
+ /**
+ * @see TestRule#apply(Statement, Description)
+ */
+ public Statement apply(Statement base, Description description) {
+ if (debugging) {
+ return base;
+ } else {
+ return rule.apply(base, description);
+ }
+ }
+
+ /**
+ * Parses arguments passed to the runtime environment for debug flags
+ * <p>
+ * Options specified in:
+ * <ul>
+ * <li>
+ * <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/jpda/conninv.html#Invocation"
+ * >javase-6</a></li>
+ * <li><a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/conninv.html#Invocation"
+ * >javase-7</a></li>
+ * <li><a href="http://docs.oracle.com/javase/8/docs/technotes/guides/jpda/conninv.html#Invocation"
+ * >javase-8</a></li>
+ *
+ *
+ * @param arguments
+ * the arguments passed to the runtime environment, usually this
+ * will be {@link RuntimeMXBean#getInputArguments()}
+ * @return true if the current JVM was started in debug mode, false
+ * otherwise.
+ */
+ private static boolean isDebugging(List<String> arguments) {
+ for (final String argument : arguments) {
+ if ("-Xdebug".equals(argument)) {
+ return true;
+ } else if (argument.startsWith("-agentlib:jdwp")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the JVM is in debug mode. This method may be used
+ * by test classes to take additional action to disable code paths that
+ * interfere with debugging if required.
+ *
+ * @return {@code true} if the current JVM is in debug mode, {@code false}
+ * otherwise
+ */
+ public boolean isDebugging() {
+ return debugging;
+ }
+
+}
diff --git a/src/main/java/org/junit/rules/ErrorCollector.java b/src/main/java/org/junit/rules/ErrorCollector.java
index 3522a65..8c6600e 100644
--- a/src/main/java/org/junit/rules/ErrorCollector.java
+++ b/src/main/java/org/junit/rules/ErrorCollector.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.rules;
import static org.junit.Assert.assertThat;
@@ -16,70 +13,72 @@ import org.junit.runners.model.MultipleFailureException;
* The ErrorCollector rule allows execution of a test to continue after the
* first problem is found (for example, to collect _all_ the incorrect rows in a
* table, and report them all at once):
- *
+ *
* <pre>
* public static class UsesErrorCollectorTwice {
* &#064;Rule
* public ErrorCollector collector= new ErrorCollector();
- *
- * &#064;Test
- * public void example() {
- * collector.addError(new Throwable(&quot;first thing went wrong&quot;));
- * collector.addError(new Throwable(&quot;second thing went wrong&quot;));
- * collector.checkThat(getResult(), not(containsString(&quot;ERROR!&quot;)));
- * // all lines will run, and then a combined failure logged at the end.
- * }
+ *
+ * &#064;Test
+ * public void example() {
+ * collector.addError(new Throwable(&quot;first thing went wrong&quot;));
+ * collector.addError(new Throwable(&quot;second thing went wrong&quot;));
+ * collector.checkThat(getResult(), not(containsString(&quot;ERROR!&quot;)));
+ * // all lines will run, and then a combined failure logged at the end.
+ * }
* }
* </pre>
+ *
+ * @since 4.7
*/
public class ErrorCollector extends Verifier {
- private List<Throwable> errors= new ArrayList<Throwable>();
+ private List<Throwable> errors = new ArrayList<Throwable>();
- @Override
- protected void verify() throws Throwable {
- MultipleFailureException.assertEmpty(errors);
- }
+ @Override
+ protected void verify() throws Throwable {
+ MultipleFailureException.assertEmpty(errors);
+ }
- /**
- * Adds a Throwable to the table. Execution continues, but the test will fail at the end.
- */
- public void addError(Throwable error) {
- errors.add(error);
- }
+ /**
+ * Adds a Throwable to the table. Execution continues, but the test will fail at the end.
+ */
+ public void addError(Throwable error) {
+ errors.add(error);
+ }
- /**
- * Adds a failure to the table if {@code matcher} does not match {@code value}.
- * Execution continues, but the test will fail at the end if the match fails.
- */
- public <T> void checkThat(final T value, final Matcher<T> matcher) {
- checkThat("", value, matcher);
- }
+ /**
+ * Adds a failure to the table if {@code matcher} does not match {@code value}.
+ * Execution continues, but the test will fail at the end if the match fails.
+ */
+ public <T> void checkThat(final T value, final Matcher<T> matcher) {
+ checkThat("", value, matcher);
+ }
- /**
- * Adds a failure with the given {@code reason}
- * to the table if {@code matcher} does not match {@code value}.
- * Execution continues, but the test will fail at the end if the match fails.
- */
- public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
- checkSucceeds(new Callable<Object>() {
- public Object call() throws Exception {
- assertThat(reason, value, matcher);
- return value;
- }
- });
- }
+ /**
+ * Adds a failure with the given {@code reason}
+ * to the table if {@code matcher} does not match {@code value}.
+ * Execution continues, but the test will fail at the end if the match fails.
+ */
+ public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
+ checkSucceeds(new Callable<Object>() {
+ public Object call() throws Exception {
+ assertThat(reason, value, matcher);
+ return value;
+ }
+ });
+ }
- /**
- * Adds to the table the exception, if any, thrown from {@code callable}.
- * Execution continues, but the test will fail at the end if
- * {@code callable} threw an exception.
- */
- public Object checkSucceeds(Callable<Object> callable) {
- try {
- return callable.call();
- } catch (Throwable e) {
- addError(e);
- return null;
- }
- }
-} \ No newline at end of file
+ /**
+ * Adds to the table the exception, if any, thrown from {@code callable}.
+ * Execution continues, but the test will fail at the end if
+ * {@code callable} threw an exception.
+ */
+ public <T> T checkSucceeds(Callable<T> callable) {
+ try {
+ return callable.call();
+ } catch (Throwable e) {
+ addError(e);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java
index bac2fba..4d61712 100644
--- a/src/main/java/org/junit/rules/ExpectedException.java
+++ b/src/main/java/org/junit/rules/ExpectedException.java
@@ -1,136 +1,270 @@
package org.junit.rules;
+import static java.lang.String.format;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.matchers.JUnitMatchers.both;
-import static org.junit.matchers.JUnitMatchers.containsString;
-import org.hamcrest.Description;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
+import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
+
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
-import org.junit.Assert;
-import org.junit.internal.matchers.TypeSafeMatcher;
+import org.junit.AssumptionViolatedException;
import org.junit.runners.model.Statement;
/**
- * The ExpectedException Rule allows in-test specification of expected exception
- * types and messages:
- *
- * <pre>
- * // These tests all pass.
- * public static class HasExpectedException {
- * &#064;Rule
- * public ExpectedException thrown= ExpectedException.none();
- *
- * &#064;Test
- * public void throwsNothing() {
- * // no exception expected, none thrown: passes.
- * }
+ * The {@code ExpectedException} rule allows you to verify that your code
+ * throws a specific exception.
+ *
+ * <h3>Usage</h3>
+ *
+ * <pre> public class SimpleExpectedExceptionTest {
+ * &#064;Rule
+ * public ExpectedException thrown= ExpectedException.none();
+ *
+ * &#064;Test
+ * public void throwsNothing() {
+ * // no exception expected, none thrown: passes.
+ * }
+ *
+ * &#064;Test
+ * public void throwsExceptionWithSpecificType() {
+ * thrown.expect(NullPointerException.class);
+ * throw new NullPointerException();
+ * }
+ * }</pre>
*
- * &#064;Test
- * public void throwsNullPointerException() {
- * thrown.expect(NullPointerException.class);
- * throw new NullPointerException();
- * }
- *
- * &#064;Test
- * public void throwsNullPointerExceptionWithMessage() {
- * thrown.expect(NullPointerException.class);
- * thrown.expectMessage(&quot;happened?&quot;);
- * thrown.expectMessage(startsWith(&quot;What&quot;));
- * throw new NullPointerException(&quot;What happened?&quot;);
- * }
- * }
- * </pre>
+ * <p>
+ * You have to add the {@code ExpectedException} rule to your test.
+ * This doesn't affect your existing tests (see {@code throwsNothing()}).
+ * After specifiying the type of the expected exception your test is
+ * successful when such an exception is thrown and it fails if a
+ * different or no exception is thrown.
+ *
+ * <p>
+ * Instead of specifying the exception's type you can characterize the
+ * expected exception based on other criterias, too:
+ *
+ * <ul>
+ * <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li>
+ * <li>The exception's message complies with a Hamcrest matcher: {@link #expectMessage(Matcher)}</li>
+ * <li>The exception's cause complies with a Hamcrest matcher: {@link #expectCause(Matcher)}</li>
+ * <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li>
+ * </ul>
+ *
+ * <p>
+ * You can combine any of the presented expect-methods. The test is
+ * successful if all specifications are met.
+ * <pre> &#064;Test
+ * public void throwsException() {
+ * thrown.expect(NullPointerException.class);
+ * thrown.expectMessage(&quot;happened&quot;);
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</pre>
+ *
+ * <h3>AssumptionViolatedExceptions</h3>
+ * <p>
+ * JUnit uses {@link AssumptionViolatedException}s for indicating that a test
+ * provides no useful information. (See {@link org.junit.Assume} for more
+ * information.) You have to call {@code assume} methods before you set
+ * expectations of the {@code ExpectedException} rule. In this case the rule
+ * will not handle consume the exceptions and it can be handled by the
+ * framework. E.g. the following test is ignored by JUnit's default runner.
+ *
+ * <pre> &#064;Test
+ * public void ignoredBecauseOfFailedAssumption() {
+ * assumeTrue(false); // throws AssumptionViolatedException
+ * thrown.expect(NullPointerException.class);
+ * }</pre>
+ *
+ * <h3>AssertionErrors</h3>
+ *
+ * <p>
+ * JUnit uses {@link AssertionError}s for indicating that a test is failing. You
+ * have to call {@code assert} methods before you set expectations of the
+ * {@code ExpectedException} rule, if they should be handled by the framework.
+ * E.g. the following test fails because of the {@code assertTrue} statement.
+ *
+ * <pre> &#064;Test
+ * public void throwsUnhandled() {
+ * assertTrue(false); // throws AssertionError
+ * thrown.expect(NullPointerException.class);
+ * }</pre>
+ *
+ * <h3>Missing Exceptions</h3>
+ * <p>
+ * By default missing exceptions are reported with an error message
+ * like "Expected test to throw an instance of foo". You can configure a different
+ * message by means of {@link #reportMissingExceptionWithMessage(String)}. You
+ * can use a {@code %s} placeholder for the description of the expected
+ * exception. E.g. "Test doesn't throw %s." will fail with the error message
+ * "Test doesn't throw an instance of foo.".
+ *
+ * @since 4.7
*/
public class ExpectedException implements TestRule {
- /**
- * @return a Rule that expects no exception to be thrown
- * (identical to behavior without this Rule)
- */
- public static ExpectedException none() {
- return new ExpectedException();
- }
-
- private Matcher<Object> fMatcher= null;
-
- private ExpectedException() {
-
- }
-
- public Statement apply(Statement base,
- org.junit.runner.Description description) {
- return new ExpectedExceptionStatement(base);
- }
-
- /**
- * Adds {@code matcher} to the list of requirements for any thrown exception.
- */
- // Should be able to remove this suppression in some brave new hamcrest world.
- @SuppressWarnings("unchecked")
- public void expect(Matcher<?> matcher) {
- if (fMatcher == null)
- fMatcher= (Matcher<Object>) matcher;
- else
- fMatcher= both(fMatcher).and(matcher);
- }
-
- /**
- * Adds to the list of requirements for any thrown exception that it
- * should be an instance of {@code type}
- */
- public void expect(Class<? extends Throwable> type) {
- expect(instanceOf(type));
- }
-
- /**
- * Adds to the list of requirements for any thrown exception that it
- * should <em>contain</em> string {@code substring}
- */
- public void expectMessage(String substring) {
- expectMessage(containsString(substring));
- }
-
- /**
- * Adds {@code matcher} to the list of requirements for the message
- * returned from any thrown exception.
- */
- public void expectMessage(Matcher<String> matcher) {
- expect(hasMessage(matcher));
- }
-
- private class ExpectedExceptionStatement extends Statement {
- private final Statement fNext;
-
- public ExpectedExceptionStatement(Statement base) {
- fNext= base;
- }
-
- @Override
- public void evaluate() throws Throwable {
- try {
- fNext.evaluate();
- } catch (Throwable e) {
- if (fMatcher == null)
- throw e;
- Assert.assertThat(e, fMatcher);
- return;
- }
- if (fMatcher != null)
- throw new AssertionError("Expected test to throw "
- + StringDescription.toString(fMatcher));
- }
- }
-
- private Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
- return new TypeSafeMatcher<Throwable>() {
- public void describeTo(Description description) {
- description.appendText("exception with message ");
- description.appendDescriptionOf(matcher);
- }
-
- @Override
- public boolean matchesSafely(Throwable item) {
- return matcher.matches(item.getMessage());
- }
- };
- }
+ /**
+ * Returns a {@linkplain TestRule rule} that expects no exception to
+ * be thrown (identical to behavior without this rule).
+ */
+ public static ExpectedException none() {
+ return new ExpectedException();
+ }
+
+ private final ExpectedExceptionMatcherBuilder matcherBuilder = new ExpectedExceptionMatcherBuilder();
+
+ private String missingExceptionMessage= "Expected test to throw %s";
+
+ private ExpectedException() {
+ }
+
+ /**
+ * This method does nothing. Don't use it.
+ * @deprecated AssertionErrors are handled by default since JUnit 4.12. Just
+ * like in JUnit &lt;= 4.10.
+ */
+ @Deprecated
+ public ExpectedException handleAssertionErrors() {
+ return this;
+ }
+
+ /**
+ * This method does nothing. Don't use it.
+ * @deprecated AssumptionViolatedExceptions are handled by default since
+ * JUnit 4.12. Just like in JUnit &lt;= 4.10.
+ */
+ @Deprecated
+ public ExpectedException handleAssumptionViolatedExceptions() {
+ return this;
+ }
+
+ /**
+ * Specifies the failure message for tests that are expected to throw
+ * an exception but do not throw any. You can use a {@code %s} placeholder for
+ * the description of the expected exception. E.g. "Test doesn't throw %s."
+ * will fail with the error message
+ * "Test doesn't throw an instance of foo.".
+ *
+ * @param message exception detail message
+ * @return the rule itself
+ */
+ public ExpectedException reportMissingExceptionWithMessage(String message) {
+ missingExceptionMessage = message;
+ return this;
+ }
+
+ public Statement apply(Statement base,
+ org.junit.runner.Description description) {
+ return new ExpectedExceptionStatement(base);
+ }
+
+ /**
+ * Verify that your code throws an exception that is matched by
+ * a Hamcrest matcher.
+ * <pre> &#064;Test
+ * public void throwsExceptionThatCompliesWithMatcher() {
+ * NullPointerException e = new NullPointerException();
+ * thrown.expect(is(e));
+ * throw e;
+ * }</pre>
+ */
+ public void expect(Matcher<?> matcher) {
+ matcherBuilder.add(matcher);
+ }
+
+ /**
+ * Verify that your code throws an exception that is an
+ * instance of specific {@code type}.
+ * <pre> &#064;Test
+ * public void throwsExceptionWithSpecificType() {
+ * thrown.expect(NullPointerException.class);
+ * throw new NullPointerException();
+ * }</pre>
+ */
+ public void expect(Class<? extends Throwable> type) {
+ expect(instanceOf(type));
+ }
+
+ /**
+ * Verify that your code throws an exception whose message contains
+ * a specific text.
+ * <pre> &#064;Test
+ * public void throwsExceptionWhoseMessageContainsSpecificText() {
+ * thrown.expectMessage(&quot;happened&quot;);
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</pre>
+ */
+ public void expectMessage(String substring) {
+ expectMessage(containsString(substring));
+ }
+
+ /**
+ * Verify that your code throws an exception whose message is matched
+ * by a Hamcrest matcher.
+ * <pre> &#064;Test
+ * public void throwsExceptionWhoseMessageCompliesWithMatcher() {
+ * thrown.expectMessage(startsWith(&quot;What&quot;));
+ * throw new NullPointerException(&quot;What happened?&quot;);
+ * }</pre>
+ */
+ public void expectMessage(Matcher<String> matcher) {
+ expect(hasMessage(matcher));
+ }
+
+ /**
+ * Verify that your code throws an exception whose cause is matched by
+ * a Hamcrest matcher.
+ * <pre> &#064;Test
+ * public void throwsExceptionWhoseCauseCompliesWithMatcher() {
+ * NullPointerException expectedCause = new NullPointerException();
+ * thrown.expectCause(is(expectedCause));
+ * throw new IllegalArgumentException(&quot;What happened?&quot;, cause);
+ * }</pre>
+ */
+ public void expectCause(Matcher<? extends Throwable> expectedCause) {
+ expect(hasCause(expectedCause));
+ }
+
+ private class ExpectedExceptionStatement extends Statement {
+ private final Statement next;
+
+ public ExpectedExceptionStatement(Statement base) {
+ next = base;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ next.evaluate();
+ } catch (Throwable e) {
+ handleException(e);
+ return;
+ }
+ if (isAnyExceptionExpected()) {
+ failDueToMissingException();
+ }
+ }
+ }
+
+ private void handleException(Throwable e) throws Throwable {
+ if (isAnyExceptionExpected()) {
+ assertThat(e, matcherBuilder.build());
+ } else {
+ throw e;
+ }
+ }
+
+ private boolean isAnyExceptionExpected() {
+ return matcherBuilder.expectsThrowable();
+ }
+
+ private void failDueToMissingException() throws AssertionError {
+ fail(missingExceptionMessage());
+ }
+
+ private String missingExceptionMessage() {
+ String expectation= StringDescription.toString(matcherBuilder.build());
+ return format(missingExceptionMessage, expectation);
+ }
}
diff --git a/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java
new file mode 100644
index 0000000..e7d94c4
--- /dev/null
+++ b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java
@@ -0,0 +1,46 @@
+package org.junit.rules;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.matchers.JUnitMatchers.isThrowable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hamcrest.Matcher;
+
+/**
+ * Builds special matcher used by {@link ExpectedException}.
+ */
+class ExpectedExceptionMatcherBuilder {
+
+ private final List<Matcher<?>> matchers = new ArrayList<Matcher<?>>();
+
+ void add(Matcher<?> matcher) {
+ matchers.add(matcher);
+ }
+
+ boolean expectsThrowable() {
+ return !matchers.isEmpty();
+ }
+
+ Matcher<Throwable> build() {
+ return isThrowable(allOfTheMatchers());
+ }
+
+ private Matcher<Throwable> allOfTheMatchers() {
+ if (matchers.size() == 1) {
+ return cast(matchers.get(0));
+ }
+ return allOf(castedMatchers());
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private List<Matcher<? super Throwable>> castedMatchers() {
+ return new ArrayList<Matcher<? super Throwable>>((List) matchers);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Matcher<Throwable> cast(Matcher<?> singleMatcher) {
+ return (Matcher<Throwable>) singleMatcher;
+ }
+}
diff --git a/src/main/java/org/junit/rules/ExternalResource.java b/src/main/java/org/junit/rules/ExternalResource.java
index 1fe3719..71ca287 100644
--- a/src/main/java/org/junit/rules/ExternalResource.java
+++ b/src/main/java/org/junit/rules/ExternalResource.java
@@ -7,62 +7,65 @@ import org.junit.runners.model.Statement;
* A base class for Rules (like TemporaryFolder) that set up an external
* resource before a test (a file, socket, server, database connection, etc.),
* and guarantee to tear it down afterward:
- *
+ *
* <pre>
* public static class UsesExternalResource {
- * Server myServer= new Server();
- *
- * &#064;Rule
- * public ExternalResource resource= new ExternalResource() {
- * &#064;Override
- * protected void before() throws Throwable {
- * myServer.connect();
- * };
- *
- * &#064;Override
- * protected void after() {
- * myServer.disconnect();
- * };
- * };
- *
- * &#064;Test
- * public void testFoo() {
- * new Client().run(myServer);
- * }
+ * Server myServer= new Server();
+ *
+ * &#064;Rule
+ * public ExternalResource resource= new ExternalResource() {
+ * &#064;Override
+ * protected void before() throws Throwable {
+ * myServer.connect();
+ * };
+ *
+ * &#064;Override
+ * protected void after() {
+ * myServer.disconnect();
+ * };
+ * };
+ *
+ * &#064;Test
+ * public void testFoo() {
+ * new Client().run(myServer);
+ * }
* }
* </pre>
+ *
+ * @since 4.7
*/
public abstract class ExternalResource implements TestRule {
- public Statement apply(Statement base, Description description) {
- return statement(base);
- }
+ public Statement apply(Statement base, Description description) {
+ return statement(base);
+ }
- private Statement statement(final Statement base) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- before();
- try {
- base.evaluate();
- } finally {
- after();
- }
- }
- };
- }
+ private Statement statement(final Statement base) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ before();
+ try {
+ base.evaluate();
+ } finally {
+ after();
+ }
+ }
+ };
+ }
- /**
- * Override to set up your specific external resource.
- * @throws if setup fails (which will disable {@code after}
- */
- protected void before() throws Throwable {
- // do nothing
- }
+ /**
+ * Override to set up your specific external resource.
+ *
+ * @throws Throwable if setup fails (which will disable {@code after}
+ */
+ protected void before() throws Throwable {
+ // do nothing
+ }
- /**
- * Override to tear down your specific external resource.
- */
- protected void after() {
- // do nothing
- }
+ /**
+ * Override to tear down your specific external resource.
+ */
+ protected void after() {
+ // do nothing
+ }
}
diff --git a/src/main/java/org/junit/rules/MethodRule.java b/src/main/java/org/junit/rules/MethodRule.java
index 5167672..823ee78 100644
--- a/src/main/java/org/junit/rules/MethodRule.java
+++ b/src/main/java/org/junit/rules/MethodRule.java
@@ -12,7 +12,7 @@ import org.junit.runners.model.Statement;
* {@link Statement}, which is passed to the next {@link Rule}, if any. For
* examples of how this can be useful, see these provided MethodRules,
* or write your own:
- *
+ *
* <ul>
* <li>{@link ErrorCollector}: collect multiple errors in one test method</li>
* <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li>
@@ -23,18 +23,22 @@ import org.junit.runners.model.Statement;
* <li>{@link Timeout}: cause test to fail after a set time</li>
* <li>{@link Verifier}: fail test if object state ends up incorrect</li>
* </ul>
+ *
+ * Note that {@link MethodRule} has been replaced by {@link TestRule},
+ * which has the added benefit of supporting class rules.
+ *
+ * @since 4.7
*/
-@Deprecated
public interface MethodRule {
- /**
- * Modifies the method-running {@link Statement} to implement an additional
- * test-running rule.
- *
- * @param base The {@link Statement} to be modified
- * @param method The method to be run
- * @param target The object on with the method will be run.
- * @return a new statement, which may be the same as {@code base},
- * a wrapper around {@code base}, or a completely new Statement.
- */
- Statement apply(Statement base, FrameworkMethod method, Object target);
-} \ No newline at end of file
+ /**
+ * Modifies the method-running {@link Statement} to implement an additional
+ * test-running rule.
+ *
+ * @param base The {@link Statement} to be modified
+ * @param method The method to be run
+ * @param target The object on which the method will be run.
+ * @return a new statement, which may be the same as {@code base},
+ * a wrapper around {@code base}, or a completely new Statement.
+ */
+ Statement apply(Statement base, FrameworkMethod method, Object target);
+}
diff --git a/src/main/java/org/junit/rules/RuleChain.java b/src/main/java/org/junit/rules/RuleChain.java
index 8af3c05..f43d8f5 100644
--- a/src/main/java/org/junit/rules/RuleChain.java
+++ b/src/main/java/org/junit/rules/RuleChain.java
@@ -1,6 +1,3 @@
-/**
- *
- */
package org.junit.rules;
import java.util.ArrayList;
@@ -14,24 +11,24 @@ import org.junit.runners.model.Statement;
* The RuleChain rule allows ordering of TestRules. You create a
* {@code RuleChain} with {@link #outerRule(TestRule)} and subsequent calls of
* {@link #around(TestRule)}:
- *
+ *
* <pre>
* public static class UseRuleChain {
* &#064;Rule
- * public TestRule chain= RuleChain
+ * public RuleChain chain= RuleChain
* .outerRule(new LoggingRule("outer rule")
* .around(new LoggingRule("middle rule")
* .around(new LoggingRule("inner rule");
- *
+ *
* &#064;Test
* public void example() {
* assertTrue(true);
- * }
+ * }
* }
* </pre>
- *
+ *
* writes the log
- *
+ *
* <pre>
* starting outer rule
* starting middle rule
@@ -40,60 +37,61 @@ import org.junit.runners.model.Statement;
* finished middle rule
* finished outer rule
* </pre>
+ *
+ * @since 4.10
*/
public class RuleChain implements TestRule {
- private static final RuleChain EMPTY_CHAIN= new RuleChain(
- Collections.<TestRule> emptyList());
+ private static final RuleChain EMPTY_CHAIN = new RuleChain(
+ Collections.<TestRule>emptyList());
- private List<TestRule> rulesStartingWithInnerMost;
+ private List<TestRule> rulesStartingWithInnerMost;
- /**
- * Returns a {@code RuleChain} without a {@link TestRule}. This method may
- * be the starting point of a {@code RuleChain}.
- *
- * @return a {@code RuleChain} without a {@link TestRule}.
- */
- public static RuleChain emptyRuleChain() {
- return EMPTY_CHAIN;
- }
+ /**
+ * Returns a {@code RuleChain} without a {@link TestRule}. This method may
+ * be the starting point of a {@code RuleChain}.
+ *
+ * @return a {@code RuleChain} without a {@link TestRule}.
+ */
+ public static RuleChain emptyRuleChain() {
+ return EMPTY_CHAIN;
+ }
- /**
- * Returns a {@code RuleChain} with a single {@link TestRule}. This method
- * is the usual starting point of a {@code RuleChain}.
- *
- * @param outerRule
- * the outer rule of the {@code RuleChain}.
- * @return a {@code RuleChain} with a single {@link TestRule}.
- */
- public static RuleChain outerRule(TestRule outerRule) {
- return emptyRuleChain().around(outerRule);
- }
+ /**
+ * Returns a {@code RuleChain} with a single {@link TestRule}. This method
+ * is the usual starting point of a {@code RuleChain}.
+ *
+ * @param outerRule the outer rule of the {@code RuleChain}.
+ * @return a {@code RuleChain} with a single {@link TestRule}.
+ */
+ public static RuleChain outerRule(TestRule outerRule) {
+ return emptyRuleChain().around(outerRule);
+ }
- private RuleChain(List<TestRule> rules) {
- this.rulesStartingWithInnerMost= rules;
- }
+ private RuleChain(List<TestRule> rules) {
+ this.rulesStartingWithInnerMost = rules;
+ }
- /**
- * Create a new {@code RuleChain}, which encloses the {@code nextRule} with
- * the rules of the current {@code RuleChain}.
- *
- * @param enclosedRule
- * the rule to enclose.
- * @return a new {@code RuleChain}.
- */
- public RuleChain around(TestRule enclosedRule) {
- List<TestRule> rulesOfNewChain= new ArrayList<TestRule>();
- rulesOfNewChain.add(enclosedRule);
- rulesOfNewChain.addAll(rulesStartingWithInnerMost);
- return new RuleChain(rulesOfNewChain);
- }
+ /**
+ * Create a new {@code RuleChain}, which encloses the {@code nextRule} with
+ * the rules of the current {@code RuleChain}.
+ *
+ * @param enclosedRule the rule to enclose.
+ * @return a new {@code RuleChain}.
+ */
+ public RuleChain around(TestRule enclosedRule) {
+ List<TestRule> rulesOfNewChain = new ArrayList<TestRule>();
+ rulesOfNewChain.add(enclosedRule);
+ rulesOfNewChain.addAll(rulesStartingWithInnerMost);
+ return new RuleChain(rulesOfNewChain);
+ }
- /**
- * {@inheritDoc}
- */
- public Statement apply(Statement base, Description description) {
- for (TestRule each : rulesStartingWithInnerMost)
- base= each.apply(base, description);
- return base;
- }
+ /**
+ * {@inheritDoc}
+ */
+ public Statement apply(Statement base, Description description) {
+ for (TestRule each : rulesStartingWithInnerMost) {
+ base = each.apply(base, description);
+ }
+ return base;
+ }
} \ No newline at end of file
diff --git a/src/main/java/org/junit/rules/RunRules.java b/src/main/java/org/junit/rules/RunRules.java
index d5905b9..131fc1f 100644
--- a/src/main/java/org/junit/rules/RunRules.java
+++ b/src/main/java/org/junit/rules/RunRules.java
@@ -5,23 +5,26 @@ import org.junit.runners.model.Statement;
/**
* Runs a collection of rules on a statement.
+ *
+ * @since 4.9
*/
public class RunRules extends Statement {
- private final Statement statement;
+ private final Statement statement;
- public RunRules(Statement base, Iterable<TestRule> rules, Description description) {
- statement= applyAll(base, rules, description);
- }
-
- @Override
- public void evaluate() throws Throwable {
- statement.evaluate();
- }
+ public RunRules(Statement base, Iterable<TestRule> rules, Description description) {
+ statement = applyAll(base, rules, description);
+ }
- private static Statement applyAll(Statement result, Iterable<TestRule> rules,
- Description description) {
- for (TestRule each : rules)
- result= each.apply(result, description);
- return result;
- }
+ @Override
+ public void evaluate() throws Throwable {
+ statement.evaluate();
+ }
+
+ private static Statement applyAll(Statement result, Iterable<TestRule> rules,
+ Description description) {
+ for (TestRule each : rules) {
+ result = each.apply(result, description);
+ }
+ return result;
+ }
}
diff --git a/src/main/java/org/junit/rules/Stopwatch.java b/src/main/java/org/junit/rules/Stopwatch.java
new file mode 100644
index 0000000..5d34e7f
--- /dev/null
+++ b/src/main/java/org/junit/rules/Stopwatch.java
@@ -0,0 +1,183 @@
+package org.junit.rules;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The Stopwatch Rule notifies one of its own protected methods of the time spent by a test.
+ *
+ * <p>Override them to get the time in nanoseconds. For example, this class will keep logging the
+ * time spent by each passed, failed, skipped, and finished test:
+ *
+ * <pre>
+ * public static class StopwatchTest {
+ * private static final Logger logger = Logger.getLogger(&quot;&quot;);
+ *
+ * private static void logInfo(Description description, String status, long nanos) {
+ * String testName = description.getMethodName();
+ * logger.info(String.format(&quot;Test %s %s, spent %d microseconds&quot;,
+ * testName, status, TimeUnit.NANOSECONDS.toMicros(nanos)));
+ * }
+ *
+ * &#064;Rule
+ * public Stopwatch stopwatch = new Stopwatch() {
+ * &#064;Override
+ * protected void succeeded(long nanos, Description description) {
+ * logInfo(description, &quot;succeeded&quot;, nanos);
+ * }
+ *
+ * &#064;Override
+ * protected void failed(long nanos, Throwable e, Description description) {
+ * logInfo(description, &quot;failed&quot;, nanos);
+ * }
+ *
+ * &#064;Override
+ * protected void skipped(long nanos, AssumptionViolatedException e, Description description) {
+ * logInfo(description, &quot;skipped&quot;, nanos);
+ * }
+ *
+ * &#064;Override
+ * protected void finished(long nanos, Description description) {
+ * logInfo(description, &quot;finished&quot;, nanos);
+ * }
+ * };
+ *
+ * &#064;Test
+ * public void succeeds() {
+ * }
+ *
+ * &#064;Test
+ * public void fails() {
+ * fail();
+ * }
+ *
+ * &#064;Test
+ * public void skips() {
+ * assumeTrue(false);
+ * }
+ * }
+ * </pre>
+ *
+ * An example to assert runtime:
+ * <pre>
+ * &#064;Test
+ * public void performanceTest() throws InterruptedException {
+ * long delta = 30;
+ * Thread.sleep(300L);
+ * assertEquals(300d, stopwatch.runtime(MILLISECONDS), delta);
+ * Thread.sleep(500L);
+ * assertEquals(800d, stopwatch.runtime(MILLISECONDS), delta);
+ * }
+ * </pre>
+ *
+ * @author tibor17
+ * @since 4.12
+ */
+public abstract class Stopwatch implements TestRule {
+ private final Clock clock;
+ private volatile long startNanos;
+ private volatile long endNanos;
+
+ public Stopwatch() {
+ this(new Clock());
+ }
+
+ Stopwatch(Clock clock) {
+ this.clock = clock;
+ }
+
+ /**
+ * Gets the runtime for the test.
+ *
+ * @param unit time unit for returned runtime
+ * @return runtime measured during the test
+ */
+ public long runtime(TimeUnit unit) {
+ return unit.convert(getNanos(), TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Invoked when a test succeeds
+ */
+ protected void succeeded(long nanos, Description description) {
+ }
+
+ /**
+ * Invoked when a test fails
+ */
+ protected void failed(long nanos, Throwable e, Description description) {
+ }
+
+ /**
+ * Invoked when a test is skipped due to a failed assumption.
+ */
+ protected void skipped(long nanos, AssumptionViolatedException e, Description description) {
+ }
+
+ /**
+ * Invoked when a test method finishes (whether passing or failing)
+ */
+ protected void finished(long nanos, Description description) {
+ }
+
+ private long getNanos() {
+ if (startNanos == 0) {
+ throw new IllegalStateException("Test has not started");
+ }
+ long currentEndNanos = endNanos; // volatile read happens here
+ if (currentEndNanos == 0) {
+ currentEndNanos = clock.nanoTime();
+ }
+
+ return currentEndNanos - startNanos;
+ }
+
+ private void starting() {
+ startNanos = clock.nanoTime();
+ endNanos = 0;
+ }
+
+ private void stopping() {
+ endNanos = clock.nanoTime();
+ }
+
+ public final Statement apply(Statement base, Description description) {
+ return new InternalWatcher().apply(base, description);
+ }
+
+ private class InternalWatcher extends TestWatcher {
+
+ @Override protected void starting(Description description) {
+ Stopwatch.this.starting();
+ }
+
+ @Override protected void finished(Description description) {
+ Stopwatch.this.finished(getNanos(), description);
+ }
+
+ @Override protected void succeeded(Description description) {
+ Stopwatch.this.stopping();
+ Stopwatch.this.succeeded(getNanos(), description);
+ }
+
+ @Override protected void failed(Throwable e, Description description) {
+ Stopwatch.this.stopping();
+ Stopwatch.this.failed(getNanos(), e, description);
+ }
+
+ @Override protected void skipped(AssumptionViolatedException e, Description description) {
+ Stopwatch.this.stopping();
+ Stopwatch.this.skipped(getNanos(), e, description);
+ }
+ }
+
+ static class Clock {
+
+ public long nanoTime() {
+ return System.nanoTime();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java
index a7c82aa..dc75c93 100644
--- a/src/main/java/org/junit/rules/TemporaryFolder.java
+++ b/src/main/java/org/junit/rules/TemporaryFolder.java
@@ -6,108 +6,165 @@ import java.io.IOException;
import org.junit.Rule;
/**
- * The TemporaryFolder Rule allows creation of files and folders that are
- * guaranteed to be deleted when the test method finishes (whether it passes or
- * fails):
- *
+ * The TemporaryFolder Rule allows creation of files and folders that should
+ * be deleted when the test method finishes (whether it passes or
+ * fails). Whether the deletion is successful or not is not checked by this rule.
+ * No exception will be thrown in case the deletion fails.
+ *
+ * <p>Example of usage:
* <pre>
* public static class HasTempFolder {
- * &#064;Rule
- * public TemporaryFolder folder= new TemporaryFolder();
- *
- * &#064;Test
- * public void testUsingTempFolder() throws IOException {
- * File createdFile= folder.newFile(&quot;myfile.txt&quot;);
- * File createdFolder= folder.newFolder(&quot;subfolder&quot;);
- * // ...
- * }
+ * &#064;Rule
+ * public TemporaryFolder folder= new TemporaryFolder();
+ *
+ * &#064;Test
+ * public void testUsingTempFolder() throws IOException {
+ * File createdFile= folder.newFile(&quot;myfile.txt&quot;);
+ * File createdFolder= folder.newFolder(&quot;subfolder&quot;);
+ * // ...
+ * }
* }
* </pre>
+ *
+ * @since 4.7
*/
public class TemporaryFolder extends ExternalResource {
- private File folder;
-
- @Override
- protected void before() throws Throwable {
- create();
- }
-
- @Override
- protected void after() {
- delete();
- }
-
- // testing purposes only
- /**
- * for testing purposes only. Do not use.
- */
- public void create() throws IOException {
- folder= newFolder();
- }
-
- /**
- * Returns a new fresh file with the given name under the temporary folder.
- */
- public File newFile(String fileName) throws IOException {
- File file= new File(getRoot(), fileName);
- file.createNewFile();
- return file;
- }
-
- /**
- * Returns a new fresh file with a random name under the temporary folder.
- */
- public File newFile() throws IOException {
- return File.createTempFile("junit", null, folder);
- }
-
- /**
- * Returns a new fresh folder with the given name under the temporary folder.
- */
- public File newFolder(String... folderNames) {
- File file = getRoot();
- for (String folderName : folderNames) {
- file = new File(file, folderName);
- file.mkdir();
- }
- return file;
- }
-
- /**
- * Returns a new fresh folder with a random name under the temporary
- * folder.
- */
- public File newFolder() throws IOException {
- File createdFolder= File.createTempFile("junit", "", folder);
- createdFolder.delete();
- createdFolder.mkdir();
- return createdFolder;
- }
-
- /**
- * @return the location of this temporary folder.
- */
- public File getRoot() {
- if (folder == null) {
- throw new IllegalStateException("the temporary folder has not yet been created");
- }
- return folder;
- }
-
- /**
- * Delete all files and folders under the temporary folder.
- * Usually not called directly, since it is automatically applied
- * by the {@link Rule}
- */
- public void delete() {
- recursiveDelete(folder);
- }
-
- private void recursiveDelete(File file) {
- File[] files= file.listFiles();
- if (files != null)
- for (File each : files)
- recursiveDelete(each);
- file.delete();
- }
+ private final File parentFolder;
+ private File folder;
+
+ public TemporaryFolder() {
+ this(null);
+ }
+
+ public TemporaryFolder(File parentFolder) {
+ this.parentFolder = parentFolder;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ create();
+ }
+
+ @Override
+ protected void after() {
+ delete();
+ }
+
+ // testing purposes only
+
+ /**
+ * for testing purposes only. Do not use.
+ */
+ public void create() throws IOException {
+ folder = createTemporaryFolderIn(parentFolder);
+ }
+
+ /**
+ * Returns a new fresh file with the given name under the temporary folder.
+ */
+ public File newFile(String fileName) throws IOException {
+ File file = new File(getRoot(), fileName);
+ if (!file.createNewFile()) {
+ throw new IOException(
+ "a file with the name \'" + fileName + "\' already exists in the test folder");
+ }
+ return file;
+ }
+
+ /**
+ * Returns a new fresh file with a random name under the temporary folder.
+ */
+ public File newFile() throws IOException {
+ return File.createTempFile("junit", null, getRoot());
+ }
+
+ /**
+ * Returns a new fresh folder with the given name under the temporary
+ * folder.
+ */
+ public File newFolder(String folder) throws IOException {
+ return newFolder(new String[]{folder});
+ }
+
+ /**
+ * Returns a new fresh folder with the given name(s) under the temporary
+ * folder.
+ */
+ public File newFolder(String... folderNames) throws IOException {
+ File file = getRoot();
+ for (int i = 0; i < folderNames.length; i++) {
+ String folderName = folderNames[i];
+ validateFolderName(folderName);
+ file = new File(file, folderName);
+ if (!file.mkdir() && isLastElementInArray(i, folderNames)) {
+ throw new IOException(
+ "a folder with the name \'" + folderName + "\' already exists");
+ }
+ }
+ return file;
+ }
+
+ /**
+ * Validates if multiple path components were used while creating a folder.
+ *
+ * @param folderName
+ * Name of the folder being created
+ */
+ private void validateFolderName(String folderName) throws IOException {
+ File tempFile = new File(folderName);
+ if (tempFile.getParent() != null) {
+ String errorMsg = "Folder name cannot consist of multiple path components separated by a file separator."
+ + " Please use newFolder('MyParentFolder','MyFolder') to create hierarchies of folders";
+ throw new IOException(errorMsg);
+ }
+ }
+
+ private boolean isLastElementInArray(int index, String[] array) {
+ return index == array.length - 1;
+ }
+
+ /**
+ * Returns a new fresh folder with a random name under the temporary folder.
+ */
+ public File newFolder() throws IOException {
+ return createTemporaryFolderIn(getRoot());
+ }
+
+ private File createTemporaryFolderIn(File parentFolder) throws IOException {
+ File createdFolder = File.createTempFile("junit", "", parentFolder);
+ createdFolder.delete();
+ createdFolder.mkdir();
+ return createdFolder;
+ }
+
+ /**
+ * @return the location of this temporary folder.
+ */
+ public File getRoot() {
+ if (folder == null) {
+ throw new IllegalStateException(
+ "the temporary folder has not yet been created");
+ }
+ return folder;
+ }
+
+ /**
+ * Delete all files and folders under the temporary folder. Usually not
+ * called directly, since it is automatically applied by the {@link Rule}
+ */
+ public void delete() {
+ if (folder != null) {
+ recursiveDelete(folder);
+ }
+ }
+
+ private void recursiveDelete(File file) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File each : files) {
+ recursiveDelete(each);
+ }
+ }
+ file.delete();
+ }
}
diff --git a/src/main/java/org/junit/rules/TestName.java b/src/main/java/org/junit/rules/TestName.java
index c4ab9ce..bf72602 100644
--- a/src/main/java/org/junit/rules/TestName.java
+++ b/src/main/java/org/junit/rules/TestName.java
@@ -4,36 +4,38 @@ import org.junit.runner.Description;
/**
* The TestName Rule makes the current test name available inside test methods:
- *
+ *
* <pre>
* public class TestNameTest {
- * &#064;Rule
- * public TestName name= new TestName();
- *
- * &#064;Test
- * public void testA() {
- * assertEquals(&quot;testA&quot;, name.getMethodName());
- * }
- *
- * &#064;Test
- * public void testB() {
- * assertEquals(&quot;testB&quot;, name.getMethodName());
- * }
+ * &#064;Rule
+ * public TestName name= new TestName();
+ *
+ * &#064;Test
+ * public void testA() {
+ * assertEquals(&quot;testA&quot;, name.getMethodName());
+ * }
+ *
+ * &#064;Test
+ * public void testB() {
+ * assertEquals(&quot;testB&quot;, name.getMethodName());
+ * }
* }
* </pre>
+ *
+ * @since 4.7
*/
public class TestName extends TestWatcher {
- private String fName;
+ private String name;
- @Override
- protected void starting(Description d) {
- fName= d.getMethodName();
- }
+ @Override
+ protected void starting(Description d) {
+ name = d.getMethodName();
+ }
- /**
- * @return the name of the currently-running test method
- */
- public String getMethodName() {
- return fName;
- }
+ /**
+ * @return the name of the currently-running test method
+ */
+ public String getMethodName() {
+ return name;
+ }
}
diff --git a/src/main/java/org/junit/rules/TestRule.java b/src/main/java/org/junit/rules/TestRule.java
index b7760c4..53e2f70 100644
--- a/src/main/java/org/junit/rules/TestRule.java
+++ b/src/main/java/org/junit/rules/TestRule.java
@@ -9,16 +9,16 @@ import org.junit.runners.model.Statement;
* a test that would otherwise fail to pass, or it may perform necessary setup or
* cleanup for tests, or it may observe test execution to report it elsewhere.
* {@link TestRule}s can do everything that could be done previously with
- * methods annotated with {@link org.junit.Before},
- * {@link org.junit.After}, {@link org.junit.BeforeClass}, or
- * {@link org.junit.AfterClass}, but they are more powerful, and more easily
+ * methods annotated with {@link org.junit.Before},
+ * {@link org.junit.After}, {@link org.junit.BeforeClass}, or
+ * {@link org.junit.AfterClass}, but they are more powerful, and more easily
* shared
* between projects and classes.
- *
+ *
* The default JUnit test runners for suites and
* individual test cases recognize {@link TestRule}s introduced in two different
- * ways. {@link org.junit.Rule} annotates method-level
- * {@link TestRule}s, and {@link org.junit.ClassRule}
+ * ways. {@link org.junit.Rule} annotates method-level
+ * {@link TestRule}s, and {@link org.junit.ClassRule}
* annotates class-level {@link TestRule}s. See Javadoc for those annotations
* for more information.
*
@@ -28,7 +28,7 @@ import org.junit.runners.model.Statement;
* {@link Statement}, which is passed to the next {@link org.junit.Rule}, if any. For
* examples of how this can be useful, see these provided TestRules,
* or write your own:
- *
+ *
* <ul>
* <li>{@link ErrorCollector}: collect multiple errors in one test method</li>
* <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li>
@@ -39,16 +39,18 @@ import org.junit.runners.model.Statement;
* <li>{@link Timeout}: cause test to fail after a set time</li>
* <li>{@link Verifier}: fail test if object state ends up incorrect</li>
* </ul>
+ *
+ * @since 4.9
*/
public interface TestRule {
- /**
- * Modifies the method-running {@link Statement} to implement this
- * test-running rule.
- *
- * @param base The {@link Statement} to be modified
- * @param description A {@link Description} of the test implemented in {@code base}
- * @return a new statement, which may be the same as {@code base},
- * a wrapper around {@code base}, or a completely new Statement.
- */
- Statement apply(Statement base, Description description);
+ /**
+ * Modifies the method-running {@link Statement} to implement this
+ * test-running rule.
+ *
+ * @param base The {@link Statement} to be modified
+ * @param description A {@link Description} of the test implemented in {@code base}
+ * @return a new statement, which may be the same as {@code base},
+ * a wrapper around {@code base}, or a completely new Statement.
+ */
+ Statement apply(Statement base, Description description);
}
diff --git a/src/main/java/org/junit/rules/TestWatcher.java b/src/main/java/org/junit/rules/TestWatcher.java
index 351b449..5492b6b 100644
--- a/src/main/java/org/junit/rules/TestWatcher.java
+++ b/src/main/java/org/junit/rules/TestWatcher.java
@@ -1,94 +1,166 @@
package org.junit.rules;
-import org.junit.internal.AssumptionViolatedException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
/**
* TestWatcher is a base class for Rules that take note of the testing
* action, without modifying it. For example, this class will keep a log of each
* passing and failing test:
- *
+ *
* <pre>
* public static class WatchmanTest {
- * private static String watchedLog;
- *
- * &#064;Rule
- * public MethodRule watchman= new TestWatcher() {
- * &#064;Override
- * protected void failed(Description d) {
- * watchedLog+= d + &quot;\n&quot;;
- * }
- *
- * &#064;Override
- * protected void succeeded(Description d) {
- * watchedLog+= d + &quot; &quot; + &quot;success!\n&quot;;
- * }
- * };
- *
- * &#064;Test
- * public void fails() {
- * fail();
- * }
- *
- * &#064;Test
- * public void succeeds() {
- * }
+ * private static String watchedLog;
+ *
+ * &#064;Rule
+ * public TestWatcher watchman= new TestWatcher() {
+ * &#064;Override
+ * protected void failed(Throwable e, Description description) {
+ * watchedLog+= description + &quot;\n&quot;;
+ * }
+ *
+ * &#064;Override
+ * protected void succeeded(Description description) {
+ * watchedLog+= description + &quot; &quot; + &quot;success!\n&quot;;
+ * }
+ * };
+ *
+ * &#064;Test
+ * public void fails() {
+ * fail();
+ * }
+ *
+ * &#064;Test
+ * public void succeeds() {
+ * }
* }
* </pre>
+ *
+ * @since 4.9
*/
public abstract class TestWatcher implements TestRule {
- public Statement apply(final Statement base, final Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- starting(description);
- try {
- base.evaluate();
- succeeded(description);
- } catch (AssumptionViolatedException e) {
- throw e;
- } catch (Throwable t) {
- failed(t, description);
- throw t;
- } finally {
- finished(description);
- }
- }
- };
- }
-
- /**
- * Invoked when a test succeeds
- *
- * @param description
- */
- protected void succeeded(Description description) {
- }
-
- /**
- * Invoked when a test fails
- *
- * @param e
- * @param description
- */
- protected void failed(Throwable e, Description description) {
- }
-
- /**
- * Invoked when a test is about to start
- *
- * @param description
- */
- protected void starting(Description description) {
- }
-
-
- /**
- * Invoked when a test method finishes (whether passing or failing)
- *
- * @param description
- */
- protected void finished(Description description) {
- }
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ List<Throwable> errors = new ArrayList<Throwable>();
+
+ startingQuietly(description, errors);
+ try {
+ base.evaluate();
+ succeededQuietly(description, errors);
+ } catch (@SuppressWarnings("deprecation") org.junit.internal.AssumptionViolatedException e) {
+ errors.add(e);
+ skippedQuietly(e, description, errors);
+ } catch (Throwable e) {
+ errors.add(e);
+ failedQuietly(e, description, errors);
+ } finally {
+ finishedQuietly(description, errors);
+ }
+
+ MultipleFailureException.assertEmpty(errors);
+ }
+ };
+ }
+
+ private void succeededQuietly(Description description,
+ List<Throwable> errors) {
+ try {
+ succeeded(description);
+ } catch (Throwable e) {
+ errors.add(e);
+ }
+ }
+
+ private void failedQuietly(Throwable e, Description description,
+ List<Throwable> errors) {
+ try {
+ failed(e, description);
+ } catch (Throwable e1) {
+ errors.add(e1);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void skippedQuietly(
+ org.junit.internal.AssumptionViolatedException e, Description description,
+ List<Throwable> errors) {
+ try {
+ if (e instanceof AssumptionViolatedException) {
+ skipped((AssumptionViolatedException) e, description);
+ } else {
+ skipped(e, description);
+ }
+ } catch (Throwable e1) {
+ errors.add(e1);
+ }
+ }
+
+ private void startingQuietly(Description description,
+ List<Throwable> errors) {
+ try {
+ starting(description);
+ } catch (Throwable e) {
+ errors.add(e);
+ }
+ }
+
+ private void finishedQuietly(Description description,
+ List<Throwable> errors) {
+ try {
+ finished(description);
+ } catch (Throwable e) {
+ errors.add(e);
+ }
+ }
+
+ /**
+ * Invoked when a test succeeds
+ */
+ protected void succeeded(Description description) {
+ }
+
+ /**
+ * Invoked when a test fails
+ */
+ protected void failed(Throwable e, Description description) {
+ }
+
+ /**
+ * Invoked when a test is skipped due to a failed assumption.
+ */
+ @SuppressWarnings("deprecation")
+ protected void skipped(AssumptionViolatedException e, Description description) {
+ // For backwards compatibility with JUnit 4.11 and earlier, call the legacy version
+ org.junit.internal.AssumptionViolatedException asInternalException = e;
+ skipped(asInternalException, description);
+ }
+
+ /**
+ * Invoked when a test is skipped due to a failed assumption.
+ *
+ * @deprecated use {@link #skipped(AssumptionViolatedException, Description)}
+ */
+ @Deprecated
+ protected void skipped(
+ org.junit.internal.AssumptionViolatedException e, Description description) {
+ }
+
+ /**
+ * Invoked when a test is about to start
+ */
+ protected void starting(Description description) {
+ }
+
+ /**
+ * Invoked when a test method finishes (whether passing or failing)
+ */
+ protected void finished(Description description) {
+ }
}
diff --git a/src/main/java/org/junit/rules/TestWatchman.java b/src/main/java/org/junit/rules/TestWatchman.java
index 15daa64..c8d6c71 100644
--- a/src/main/java/org/junit/rules/TestWatchman.java
+++ b/src/main/java/org/junit/rules/TestWatchman.java
@@ -8,93 +8,84 @@ import org.junit.runners.model.Statement;
* TestWatchman is a base class for Rules that take note of the testing
* action, without modifying it. For example, this class will keep a log of each
* passing and failing test:
- *
+ *
* <pre>
* public static class WatchmanTest {
- * private static String watchedLog;
- *
- * &#064;Rule
- * public MethodRule watchman= new TestWatchman() {
- * &#064;Override
- * public void failed(Throwable e, FrameworkMethod method) {
- * watchedLog+= method.getName() + &quot; &quot; + e.getClass().getSimpleName()
- * + &quot;\n&quot;;
- * }
- *
- * &#064;Override
- * public void succeeded(FrameworkMethod method) {
- * watchedLog+= method.getName() + &quot; &quot; + &quot;success!\n&quot;;
- * }
- * };
- *
- * &#064;Test
- * public void fails() {
- * fail();
- * }
- *
- * &#064;Test
- * public void succeeds() {
- * }
+ * private static String watchedLog;
+ *
+ * &#064;Rule
+ * public MethodRule watchman= new TestWatchman() {
+ * &#064;Override
+ * public void failed(Throwable e, FrameworkMethod method) {
+ * watchedLog+= method.getName() + &quot; &quot; + e.getClass().getSimpleName()
+ * + &quot;\n&quot;;
+ * }
+ *
+ * &#064;Override
+ * public void succeeded(FrameworkMethod method) {
+ * watchedLog+= method.getName() + &quot; &quot; + &quot;success!\n&quot;;
+ * }
+ * };
+ *
+ * &#064;Test
+ * public void fails() {
+ * fail();
+ * }
+ *
+ * &#064;Test
+ * public void succeeds() {
+ * }
* }
* </pre>
- *
- * @deprecated {@link MethodRule} is deprecated.
- * Use {@link TestWatcher} implements {@link TestRule} instead.
+ *
+ * @since 4.7
+ * @deprecated Use {@link TestWatcher} (which implements {@link TestRule}) instead.
*/
@Deprecated
public class TestWatchman implements MethodRule {
- public Statement apply(final Statement base, final FrameworkMethod method,
- Object target) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- starting(method);
- try {
- base.evaluate();
- succeeded(method);
- } catch (AssumptionViolatedException e) {
- throw e;
- } catch (Throwable t) {
- failed(t, method);
- throw t;
- } finally {
- finished(method);
- }
- }
- };
- }
+ public Statement apply(final Statement base, final FrameworkMethod method,
+ Object target) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ starting(method);
+ try {
+ base.evaluate();
+ succeeded(method);
+ } catch (AssumptionViolatedException e) {
+ throw e;
+ } catch (Throwable e) {
+ failed(e, method);
+ throw e;
+ } finally {
+ finished(method);
+ }
+ }
+ };
+ }
- /**
- * Invoked when a test method succeeds
- *
- * @param method
- */
- public void succeeded(FrameworkMethod method) {
- }
+ /**
+ * Invoked when a test method succeeds
+ */
+ public void succeeded(FrameworkMethod method) {
+ }
- /**
- * Invoked when a test method fails
- *
- * @param e
- * @param method
- */
- public void failed(Throwable e, FrameworkMethod method) {
- }
+ /**
+ * Invoked when a test method fails
+ */
+ public void failed(Throwable e, FrameworkMethod method) {
+ }
- /**
- * Invoked when a test method is about to start
- *
- * @param method
- */
- public void starting(FrameworkMethod method) {
- }
+ /**
+ * Invoked when a test method is about to start
+ */
+ public void starting(FrameworkMethod method) {
+ }
- /**
- * Invoked when a test method finishes (whether passing or failing)
- *
- * @param method
- */
- public void finished(FrameworkMethod method) {
- }
+ /**
+ * Invoked when a test method finishes (whether passing or failing)
+ */
+ public void finished(FrameworkMethod method) {
+ }
}
diff --git a/src/main/java/org/junit/rules/Timeout.java b/src/main/java/org/junit/rules/Timeout.java
index 85ce6d6..45a5bc5 100644
--- a/src/main/java/org/junit/rules/Timeout.java
+++ b/src/main/java/org/junit/rules/Timeout.java
@@ -1,49 +1,233 @@
-/**
- *
- */
package org.junit.rules;
import org.junit.internal.runners.statements.FailOnTimeout;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
+import java.util.concurrent.TimeUnit;
+
/**
* The Timeout Rule applies the same timeout to all test methods in a class:
- *
* <pre>
- * public static class HasGlobalTimeout {
- * public static String log;
- *
- * &#064;Rule
- * public MethodRule globalTimeout= new Timeout(20);
- *
- * &#064;Test
- * public void testInfiniteLoop1() {
- * log+= &quot;ran1&quot;;
- * for (;;) {
- * }
- * }
- *
- * &#064;Test
- * public void testInfiniteLoop2() {
- * log+= &quot;ran2&quot;;
- * for (;;) {
- * }
- * }
+ * public static class HasGlobalLongTimeout {
+ *
+ * &#064;Rule
+ * public Timeout globalTimeout= new Timeout(20);
+ *
+ * &#064;Test
+ * public void run1() throws InterruptedException {
+ * Thread.sleep(100);
+ * }
+ *
+ * &#064;Test
+ * public void infiniteLoop() {
+ * while (true) {}
+ * }
* }
* </pre>
+ * <p>
+ * Each test is run in a new thread. If the specified timeout elapses before
+ * the test completes, its execution is interrupted via {@link Thread#interrupt()}.
+ * This happens in interruptable I/O and locks, and methods in {@link Object}
+ * and {@link Thread} throwing {@link InterruptedException}.
+ * <p>
+ * A specified timeout of 0 will be interpreted as not set, however tests will
+ * still launch from separate threads. This can be useful for disabling timeouts
+ * in environments where they are dynamically set based on some property.
+ *
+ * @since 4.7
*/
public class Timeout implements TestRule {
- private final int fMillis;
-
- /**
- * @param millis the millisecond timeout
- */
- public Timeout(int millis) {
- fMillis= millis;
- }
-
- public Statement apply(Statement base, Description description) {
- return new FailOnTimeout(base, fMillis);
- }
-} \ No newline at end of file
+ private final long timeout;
+ private final TimeUnit timeUnit;
+ private final boolean lookForStuckThread;
+
+ /**
+ * Returns a new builder for building an instance.
+ *
+ * @since 4.12
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Create a {@code Timeout} instance with the timeout specified
+ * in milliseconds.
+ * <p>
+ * This constructor is deprecated.
+ * <p>
+ * Instead use {@link #Timeout(long, java.util.concurrent.TimeUnit)},
+ * {@link Timeout#millis(long)}, or {@link Timeout#seconds(long)}.
+ *
+ * @param millis the maximum time in milliseconds to allow the
+ * test to run before it should timeout
+ */
+ @Deprecated
+ public Timeout(int millis) {
+ this(millis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Create a {@code Timeout} instance with the timeout specified
+ * at the timeUnit of granularity of the provided {@code TimeUnit}.
+ *
+ * @param timeout the maximum time to allow the test to run
+ * before it should timeout
+ * @param timeUnit the time unit for the {@code timeout}
+ * @since 4.12
+ */
+ public Timeout(long timeout, TimeUnit timeUnit) {
+ this.timeout = timeout;
+ this.timeUnit = timeUnit;
+ lookForStuckThread = false;
+ }
+
+ /**
+ * Create a {@code Timeout} instance initialized with values form
+ * a builder.
+ *
+ * @since 4.12
+ */
+ protected Timeout(Builder builder) {
+ timeout = builder.getTimeout();
+ timeUnit = builder.getTimeUnit();
+ lookForStuckThread = builder.getLookingForStuckThread();
+ }
+
+ /**
+ * Creates a {@link Timeout} that will timeout a test after the
+ * given duration, in milliseconds.
+ *
+ * @since 4.12
+ */
+ public static Timeout millis(long millis) {
+ return new Timeout(millis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Creates a {@link Timeout} that will timeout a test after the
+ * given duration, in seconds.
+ *
+ * @since 4.12
+ */
+ public static Timeout seconds(long seconds) {
+ return new Timeout(seconds, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Gets the timeout configured for this rule, in the given units.
+ *
+ * @since 4.12
+ */
+ protected final long getTimeout(TimeUnit unit) {
+ return unit.convert(timeout, timeUnit);
+ }
+
+ /**
+ * Gets whether this {@code Timeout} will look for a stuck thread
+ * when the test times out.
+ *
+ * @since 4.12
+ */
+ protected final boolean getLookingForStuckThread() {
+ return lookForStuckThread;
+ }
+
+ /**
+ * Creates a {@link Statement} that will run the given
+ * {@code statement}, and timeout the operation based
+ * on the values configured in this rule. Subclasses
+ * can override this method for different behavior.
+ *
+ * @since 4.12
+ */
+ protected Statement createFailOnTimeoutStatement(
+ Statement statement) throws Exception {
+ return FailOnTimeout.builder()
+ .withTimeout(timeout, timeUnit)
+ .withLookingForStuckThread(lookForStuckThread)
+ .build(statement);
+ }
+
+ public Statement apply(Statement base, Description description) {
+ try {
+ return createFailOnTimeoutStatement(base);
+ } catch (final Exception e) {
+ return new Statement() {
+ @Override public void evaluate() throws Throwable {
+ throw new RuntimeException("Invalid parameters for Timeout", e);
+ }
+ };
+ }
+ }
+
+ /**
+ * Builder for {@link Timeout}.
+ *
+ * @since 4.12
+ */
+ public static class Builder {
+ private boolean lookForStuckThread = false;
+ private long timeout = 0;
+ private TimeUnit timeUnit = TimeUnit.SECONDS;
+
+ protected Builder() {
+ }
+
+ /**
+ * Specifies the time to wait before timing out the test.
+ *
+ * <p>If this is not called, or is called with a
+ * {@code timeout} of {@code 0}, the returned {@code Timeout}
+ * rule instance will cause the tests to wait forever to
+ * complete, however the tests will still launch from a
+ * separate thread. This can be useful for disabling timeouts
+ * in environments where they are dynamically set based on
+ * some property.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the {@code timeout} argument
+ * @return {@code this} for method chaining.
+ */
+ public Builder withTimeout(long timeout, TimeUnit unit) {
+ this.timeout = timeout;
+ this.timeUnit = unit;
+ return this;
+ }
+
+ protected long getTimeout() {
+ return timeout;
+ }
+
+ protected TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+
+ /**
+ * Specifies whether to look for a stuck thread. If a timeout occurs and this
+ * feature is enabled, the rule will look for a thread that appears to be stuck
+ * and dump its backtrace. This feature is experimental. Behavior may change
+ * after the 4.12 release in response to feedback.
+ *
+ * @param enable {@code true} to enable the feature
+ * @return {@code this} for method chaining.
+ */
+ public Builder withLookingForStuckThread(boolean enable) {
+ this.lookForStuckThread = enable;
+ return this;
+ }
+
+ protected boolean getLookingForStuckThread() {
+ return lookForStuckThread;
+ }
+
+
+ /**
+ * Builds a {@link Timeout} instance using the values in this builder.,
+ */
+ public Timeout build() {
+ return new Timeout(this);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/rules/Verifier.java b/src/main/java/org/junit/rules/Verifier.java
index be1a55e..7a03b0c 100644
--- a/src/main/java/org/junit/rules/Verifier.java
+++ b/src/main/java/org/junit/rules/Verifier.java
@@ -7,39 +7,41 @@ import org.junit.runners.model.Statement;
* Verifier is a base class for Rules like ErrorCollector, which can turn
* otherwise passing test methods into failing tests if a verification check is
* failed
- *
+ *
* <pre>
- * public static class ErrorLogVerifier() {
+ * public static class ErrorLogVerifier {
* private ErrorLog errorLog = new ErrorLog();
- *
+ *
* &#064;Rule
- * public MethodRule verifier = new Verifier() {
+ * public Verifier verifier = new Verifier() {
* &#064;Override public void verify() {
* assertTrue(errorLog.isEmpty());
* }
* }
- *
+ *
* &#064;Test public void testThatMightWriteErrorLog() {
* // ...
* }
* }
* </pre>
+ *
+ * @since 4.7
*/
-public class Verifier implements TestRule {
- public Statement apply(final Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- base.evaluate();
- verify();
- }
- };
- }
+public abstract class Verifier implements TestRule {
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ verify();
+ }
+ };
+ }
- /**
- * Override this to add verification logic. Overrides should throw an
- * exception to indicate that verification failed.
- */
- protected void verify() throws Throwable {
- }
+ /**
+ * Override this to add verification logic. Overrides should throw an
+ * exception to indicate that verification failed.
+ */
+ protected void verify() throws Throwable {
+ }
}
diff --git a/src/main/java/org/junit/runner/Computer.java b/src/main/java/org/junit/runner/Computer.java
index 939f702..8bb4b20 100644
--- a/src/main/java/org/junit/runner/Computer.java
+++ b/src/main/java/org/junit/runner/Computer.java
@@ -8,33 +8,35 @@ import org.junit.runners.model.RunnerBuilder;
* Represents a strategy for computing runners and suites.
* WARNING: this class is very likely to undergo serious changes in version 4.8 and
* beyond.
+ *
+ * @since 4.6
*/
public class Computer {
- /**
- * Returns a new default computer, which runs tests in serial order
- */
- public static Computer serial() {
- return new Computer();
- }
+ /**
+ * Returns a new default computer, which runs tests in serial order
+ */
+ public static Computer serial() {
+ return new Computer();
+ }
- /**
- * Create a suite for {@code classes}, building Runners with {@code builder}.
- * Throws an InitializationError if Runner construction fails
- */
- public Runner getSuite(final RunnerBuilder builder,
- Class<?>[] classes) throws InitializationError {
- return new Suite(new RunnerBuilder() {
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- return getRunner(builder, testClass);
- }
- }, classes);
- }
+ /**
+ * Create a suite for {@code classes}, building Runners with {@code builder}.
+ * Throws an InitializationError if Runner construction fails
+ */
+ public Runner getSuite(final RunnerBuilder builder,
+ Class<?>[] classes) throws InitializationError {
+ return new Suite(new RunnerBuilder() {
+ @Override
+ public Runner runnerForClass(Class<?> testClass) throws Throwable {
+ return getRunner(builder, testClass);
+ }
+ }, classes);
+ }
- /**
- * Create a single-class runner for {@code testClass}, using {@code builder}
- */
- protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
- return builder.runnerForClass(testClass);
- }
+ /**
+ * Create a single-class runner for {@code testClass}, using {@code builder}
+ */
+ protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
+ return builder.runnerForClass(testClass);
+ }
}
diff --git a/src/main/java/org/junit/runner/Describable.java b/src/main/java/org/junit/runner/Describable.java
index e59cc01..1514141 100644
--- a/src/main/java/org/junit/runner/Describable.java
+++ b/src/main/java/org/junit/runner/Describable.java
@@ -3,10 +3,12 @@ package org.junit.runner;
/**
* Represents an object that can describe itself
+ *
+ * @since 4.5
*/
public interface Describable {
- /**
- * @return a {@link Description} showing the tests to be run by the receiver
- */
- public abstract Description getDescription();
+ /**
+ * @return a {@link Description} showing the tests to be run by the receiver
+ */
+ public abstract Description getDescription();
} \ No newline at end of file
diff --git a/src/main/java/org/junit/runner/FilterFactories.java b/src/main/java/org/junit/runner/FilterFactories.java
new file mode 100644
index 0000000..020d394
--- /dev/null
+++ b/src/main/java/org/junit/runner/FilterFactories.java
@@ -0,0 +1,82 @@
+package org.junit.runner;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory.FilterNotCreatedException;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Utility class whose methods create a {@link FilterFactory}.
+ */
+class FilterFactories {
+ /**
+ * Creates a {@link Filter}.
+ *
+ * A filter specification is of the form "package.of.FilterFactory=args-to-filter-factory" or
+ * "package.of.FilterFactory".
+ *
+ * @param request the request that will be filtered
+ * @param filterSpec the filter specification
+ * @throws org.junit.runner.FilterFactory.FilterNotCreatedException
+ */
+ public static Filter createFilterFromFilterSpec(Request request, String filterSpec)
+ throws FilterFactory.FilterNotCreatedException {
+ Description topLevelDescription = request.getRunner().getDescription();
+ String[] tuple;
+
+ if (filterSpec.contains("=")) {
+ tuple = filterSpec.split("=", 2);
+ } else {
+ tuple = new String[]{ filterSpec, "" };
+ }
+
+ return createFilter(tuple[0], new FilterFactoryParams(topLevelDescription, tuple[1]));
+ }
+
+ /**
+ * Creates a {@link Filter}.
+ *
+ * @param filterFactoryFqcn The fully qualified class name of the {@link FilterFactory}
+ * @param params The arguments to the {@link FilterFactory}
+ */
+ public static Filter createFilter(String filterFactoryFqcn, FilterFactoryParams params)
+ throws FilterFactory.FilterNotCreatedException {
+ FilterFactory filterFactory = createFilterFactory(filterFactoryFqcn);
+
+ return filterFactory.createFilter(params);
+ }
+
+ /**
+ * Creates a {@link Filter}.
+ *
+ * @param filterFactoryClass The class of the {@link FilterFactory}
+ * @param params The arguments to the {@link FilterFactory}
+ *
+ */
+ public static Filter createFilter(Class<? extends FilterFactory> filterFactoryClass, FilterFactoryParams params)
+ throws FilterFactory.FilterNotCreatedException {
+ FilterFactory filterFactory = createFilterFactory(filterFactoryClass);
+
+ return filterFactory.createFilter(params);
+ }
+
+ static FilterFactory createFilterFactory(String filterFactoryFqcn) throws FilterNotCreatedException {
+ Class<? extends FilterFactory> filterFactoryClass;
+
+ try {
+ filterFactoryClass = Classes.getClass(filterFactoryFqcn).asSubclass(FilterFactory.class);
+ } catch (Exception e) {
+ throw new FilterNotCreatedException(e);
+ }
+
+ return createFilterFactory(filterFactoryClass);
+ }
+
+ static FilterFactory createFilterFactory(Class<? extends FilterFactory> filterFactoryClass)
+ throws FilterNotCreatedException {
+ try {
+ return filterFactoryClass.getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new FilterNotCreatedException(e);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/FilterFactory.java b/src/main/java/org/junit/runner/FilterFactory.java
new file mode 100644
index 0000000..57b4eaa
--- /dev/null
+++ b/src/main/java/org/junit/runner/FilterFactory.java
@@ -0,0 +1,24 @@
+package org.junit.runner;
+
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Extend this class to create a factory that creates {@link Filter}.
+ */
+public interface FilterFactory {
+ /**
+ * Creates a {@link Filter} given a {@link FilterFactoryParams} argument.
+ *
+ * @param params Parameters needed to create the {@link Filter}
+ */
+ Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException;
+
+ /**
+ * Exception thrown if the {@link Filter} cannot be created.
+ */
+ public static class FilterNotCreatedException extends Exception {
+ public FilterNotCreatedException(Exception exception) {
+ super(exception.getMessage(), exception);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/FilterFactoryParams.java b/src/main/java/org/junit/runner/FilterFactoryParams.java
new file mode 100644
index 0000000..1e74ab9
--- /dev/null
+++ b/src/main/java/org/junit/runner/FilterFactoryParams.java
@@ -0,0 +1,23 @@
+package org.junit.runner;
+
+public final class FilterFactoryParams {
+ private final Description topLevelDescription;
+ private final String args;
+
+ public FilterFactoryParams(Description topLevelDescription, String args) {
+ if (args == null || topLevelDescription == null) {
+ throw new NullPointerException();
+ }
+
+ this.topLevelDescription = topLevelDescription;
+ this.args = args;
+ }
+
+ public String getArgs() {
+ return args;
+ }
+
+ public Description getTopLevelDescription() {
+ return topLevelDescription;
+ }
+}
diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
new file mode 100644
index 0000000..434157c
--- /dev/null
+++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
@@ -0,0 +1,149 @@
+package org.junit.runner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory.FilterNotCreatedException;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runners.model.InitializationError;
+
+class JUnitCommandLineParseResult {
+ private final List<String> filterSpecs = new ArrayList<String>();
+ private final List<Class<?>> classes = new ArrayList<Class<?>>();
+ private final List<Throwable> parserErrors = new ArrayList<Throwable>();
+
+ /**
+ * Do not use. Testing purposes only.
+ */
+ JUnitCommandLineParseResult() {}
+
+ /**
+ * Returns filter specs parsed from command line.
+ */
+ public List<String> getFilterSpecs() {
+ return Collections.unmodifiableList(filterSpecs);
+ }
+
+ /**
+ * Returns test classes parsed from command line.
+ */
+ public List<Class<?>> getClasses() {
+ return Collections.unmodifiableList(classes);
+ }
+
+ /**
+ * Parses the arguments.
+ *
+ * @param args Arguments
+ */
+ public static JUnitCommandLineParseResult parse(String[] args) {
+ JUnitCommandLineParseResult result = new JUnitCommandLineParseResult();
+
+ result.parseArgs(args);
+
+ return result;
+ }
+
+ private void parseArgs(String[] args) {
+ parseParameters(parseOptions(args));
+ }
+
+ String[] parseOptions(String... args) {
+ for (int i = 0; i != args.length; ++i) {
+ String arg = args[i];
+
+ if (arg.equals("--")) {
+ return copyArray(args, i + 1, args.length);
+ } else if (arg.startsWith("--")) {
+ if (arg.startsWith("--filter=") || arg.equals("--filter")) {
+ String filterSpec;
+ if (arg.equals("--filter")) {
+ ++i;
+
+ if (i < args.length) {
+ filterSpec = args[i];
+ } else {
+ parserErrors.add(new CommandLineParserError(arg + " value not specified"));
+ break;
+ }
+ } else {
+ filterSpec = arg.substring(arg.indexOf('=') + 1);
+ }
+
+ filterSpecs.add(filterSpec);
+ } else {
+ parserErrors.add(new CommandLineParserError("JUnit knows nothing about the " + arg + " option"));
+ }
+ } else {
+ return copyArray(args, i, args.length);
+ }
+ }
+
+ return new String[]{};
+ }
+
+ private String[] copyArray(String[] args, int from, int to) {
+ ArrayList<String> result = new ArrayList<String>();
+
+ for (int j = from; j != to; ++j) {
+ result.add(args[j]);
+ }
+
+ return result.toArray(new String[result.size()]);
+ }
+
+ void parseParameters(String[] args) {
+ for (String arg : args) {
+ try {
+ classes.add(Classes.getClass(arg));
+ } catch (ClassNotFoundException e) {
+ parserErrors.add(new IllegalArgumentException("Could not find class [" + arg + "]", e));
+ }
+ }
+ }
+
+ private Request errorReport(Throwable cause) {
+ return Request.errorReport(JUnitCommandLineParseResult.class, cause);
+ }
+
+ /**
+ * Creates a {@link Request}.
+ *
+ * @param computer {@link Computer} to be used.
+ */
+ public Request createRequest(Computer computer) {
+ if (parserErrors.isEmpty()) {
+ Request request = Request.classes(
+ computer, classes.toArray(new Class<?>[classes.size()]));
+ return applyFilterSpecs(request);
+ } else {
+ return errorReport(new InitializationError(parserErrors));
+ }
+ }
+
+ private Request applyFilterSpecs(Request request) {
+ try {
+ for (String filterSpec : filterSpecs) {
+ Filter filter = FilterFactories.createFilterFromFilterSpec(
+ request, filterSpec);
+ request = request.filterWith(filter);
+ }
+ return request;
+ } catch (FilterNotCreatedException e) {
+ return errorReport(e);
+ }
+ }
+
+ /**
+ * Exception used if there's a problem parsing the command line.
+ */
+ public static class CommandLineParserError extends Exception {
+ private static final long serialVersionUID= 1L;
+
+ public CommandLineParserError(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/JUnitCore.java b/src/main/java/org/junit/runner/JUnitCore.java
index 2fcd3b3..c1479e0 100644
--- a/src/main/java/org/junit/runner/JUnitCore.java
+++ b/src/main/java/org/junit/runner/JUnitCore.java
@@ -1,186 +1,167 @@
package org.junit.runner;
-import java.util.ArrayList;
-import java.util.List;
-
import junit.runner.Version;
import org.junit.internal.JUnitSystem;
import org.junit.internal.RealSystem;
import org.junit.internal.TextListener;
import org.junit.internal.runners.JUnit38ClassRunner;
-import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
/**
- * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests,
- * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run
+ * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests,
+ * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run
* <code>java org.junit.runner.JUnitCore TestClass1 TestClass2 ...</code>.
- * For one-shot test runs, use the static method {@link #runClasses(Class[])}.
+ * For one-shot test runs, use the static method {@link #runClasses(Class[])}.
* If you want to add special listeners,
* create an instance of {@link org.junit.runner.JUnitCore} first and use it to run the tests.
- *
+ *
* @see org.junit.runner.Result
* @see org.junit.runner.notification.RunListener
* @see org.junit.runner.Request
+ * @since 4.0
*/
public class JUnitCore {
- private RunNotifier fNotifier;
-
- /**
- * Create a new <code>JUnitCore</code> to run tests.
- */
- public JUnitCore() {
- fNotifier= new RunNotifier();
- }
-
- /**
- * Run the tests contained in the classes named in the <code>args</code>.
- * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1.
- * Write feedback while tests are running and write
- * stack traces for all failed tests after the tests all complete.
- * @param args names of classes in which to find tests to run
- */
- public static void main(String... args) {
- runMainAndExit(new RealSystem(), args);
- }
-
- /**
- * Do not use. Testing purposes only.
- * @param system
- */
- public static void runMainAndExit(JUnitSystem system, String... args) {
- Result result= new JUnitCore().runMain(system, args);
- system.exit(result.wasSuccessful() ? 0 : 1);
- }
-
- /**
- * Run the tests contained in <code>classes</code>. Write feedback while the tests
- * are running and write stack traces for all failed tests after all tests complete. This is
- * similar to {@link #main(String[])}, but intended to be used programmatically.
- * @param computer Helps construct Runners from classes
- * @param classes Classes in which to find tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public static Result runClasses(Computer computer, Class<?>... classes) {
- return new JUnitCore().run(computer, classes);
- }
- /**
- * Run the tests contained in <code>classes</code>. Write feedback while the tests
- * are running and write stack traces for all failed tests after all tests complete. This is
- * similar to {@link #main(String[])}, but intended to be used programmatically.
- * @param classes Classes in which to find tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public static Result runClasses(Class<?>... classes) {
- return new JUnitCore().run(defaultComputer(), classes);
- }
-
- /**
- * Do not use. Testing purposes only.
- * @param system
- */
- public Result runMain(JUnitSystem system, String... args) {
- system.out().println("JUnit version " + Version.id());
- List<Class<?>> classes= new ArrayList<Class<?>>();
- List<Failure> missingClasses= new ArrayList<Failure>();
- for (String each : args)
- try {
- classes.add(Class.forName(each));
- } catch (ClassNotFoundException e) {
- system.out().println("Could not find class: " + each);
- Description description= Description.createSuiteDescription(each);
- Failure failure= new Failure(description, e);
- missingClasses.add(failure);
- }
- RunListener listener= new TextListener(system);
- addListener(listener);
- Result result= run(classes.toArray(new Class[0]));
- for (Failure each : missingClasses)
- result.getFailures().add(each);
- return result;
- }
-
- /**
- * @return the version number of this release
- */
- public String getVersion() {
- return Version.id();
- }
-
- /**
- * Run all the tests in <code>classes</code>.
- * @param classes the classes containing tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Class<?>... classes) {
- return run(Request.classes(defaultComputer(), classes));
- }
-
- /**
- * Run all the tests in <code>classes</code>.
- * @param computer Helps construct Runners from classes
- * @param classes the classes containing tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Computer computer, Class<?>... classes) {
- return run(Request.classes(computer, classes));
- }
-
- /**
- * Run all the tests contained in <code>request</code>.
- * @param request the request describing tests
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(Request request) {
- return run(request.getRunner());
- }
-
- /**
- * Run all the tests contained in JUnit 3.8.x <code>test</code>. Here for backward compatibility.
- * @param test the old-style test
- * @return a {@link Result} describing the details of the test run and the failed tests.
- */
- public Result run(junit.framework.Test test) {
- return run(new JUnit38ClassRunner(test));
- }
-
- /**
- * Do not use. Testing purposes only.
- */
- public Result run(Runner runner) {
- Result result= new Result();
- RunListener listener= result.createListener();
- fNotifier.addFirstListener(listener);
- try {
- fNotifier.fireTestRunStarted(runner.getDescription());
- runner.run(fNotifier);
- fNotifier.fireTestRunFinished(result);
- } finally {
- removeListener(listener);
- }
- return result;
- }
-
- /**
- * Add a listener to be notified as the tests run.
- * @param listener the listener to add
- * @see org.junit.runner.notification.RunListener
- */
- public void addListener(RunListener listener) {
- fNotifier.addListener(listener);
- }
-
- /**
- * Remove a listener.
- * @param listener the listener to remove
- */
- public void removeListener(RunListener listener) {
- fNotifier.removeListener(listener);
- }
-
- static Computer defaultComputer() {
- return new Computer();
- }
+ private final RunNotifier notifier = new RunNotifier();
+
+ /**
+ * Run the tests contained in the classes named in the <code>args</code>.
+ * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1.
+ * Write feedback while tests are running and write
+ * stack traces for all failed tests after the tests all complete.
+ *
+ * @param args names of classes in which to find tests to run
+ */
+ public static void main(String... args) {
+ Result result = new JUnitCore().runMain(new RealSystem(), args);
+ System.exit(result.wasSuccessful() ? 0 : 1);
+ }
+
+ /**
+ * Run the tests contained in <code>classes</code>. Write feedback while the tests
+ * are running and write stack traces for all failed tests after all tests complete. This is
+ * similar to {@link #main(String[])}, but intended to be used programmatically.
+ *
+ * @param classes Classes in which to find tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public static Result runClasses(Class<?>... classes) {
+ return runClasses(defaultComputer(), classes);
+ }
+
+ /**
+ * Run the tests contained in <code>classes</code>. Write feedback while the tests
+ * are running and write stack traces for all failed tests after all tests complete. This is
+ * similar to {@link #main(String[])}, but intended to be used programmatically.
+ *
+ * @param computer Helps construct Runners from classes
+ * @param classes Classes in which to find tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public static Result runClasses(Computer computer, Class<?>... classes) {
+ return new JUnitCore().run(computer, classes);
+ }
+
+ /**
+ * @param system
+ * @param args from main()
+ */
+ Result runMain(JUnitSystem system, String... args) {
+ system.out().println("JUnit version " + Version.id());
+
+ JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args);
+
+ RunListener listener = new TextListener(system);
+ addListener(listener);
+
+ return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
+ }
+
+ /**
+ * @return the version number of this release
+ */
+ public String getVersion() {
+ return Version.id();
+ }
+
+ /**
+ * Run all the tests in <code>classes</code>.
+ *
+ * @param classes the classes containing tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Class<?>... classes) {
+ return run(defaultComputer(), classes);
+ }
+
+ /**
+ * Run all the tests in <code>classes</code>.
+ *
+ * @param computer Helps construct Runners from classes
+ * @param classes the classes containing tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Computer computer, Class<?>... classes) {
+ return run(Request.classes(computer, classes));
+ }
+
+ /**
+ * Run all the tests contained in <code>request</code>.
+ *
+ * @param request the request describing tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Request request) {
+ return run(request.getRunner());
+ }
+
+ /**
+ * Run all the tests contained in JUnit 3.8.x <code>test</code>. Here for backward compatibility.
+ *
+ * @param test the old-style test
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(junit.framework.Test test) {
+ return run(new JUnit38ClassRunner(test));
+ }
+
+ /**
+ * Do not use. Testing purposes only.
+ */
+ public Result run(Runner runner) {
+ Result result = new Result();
+ RunListener listener = result.createListener();
+ notifier.addFirstListener(listener);
+ try {
+ notifier.fireTestRunStarted(runner.getDescription());
+ runner.run(notifier);
+ notifier.fireTestRunFinished(result);
+ } finally {
+ removeListener(listener);
+ }
+ return result;
+ }
+
+ /**
+ * Add a listener to be notified as the tests run.
+ *
+ * @param listener the listener to add
+ * @see org.junit.runner.notification.RunListener
+ */
+ public void addListener(RunListener listener) {
+ notifier.addListener(listener);
+ }
+
+ /**
+ * Remove a listener.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeListener(RunListener listener) {
+ notifier.removeListener(listener);
+ }
+ static Computer defaultComputer() {
+ return new Computer();
+ }
}
diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java
index 310b915..79c0f1e 100644
--- a/src/main/java/org/junit/runner/Request.java
+++ b/src/main/java/org/junit/runner/Request.java
@@ -11,151 +11,160 @@ import org.junit.runner.manipulation.Filter;
import org.junit.runners.model.InitializationError;
/**
- * <p>A <code>Request</code> is an abstract description of tests to be run. Older versions of
+ * A <code>Request</code> is an abstract description of tests to be run. Older versions of
* JUnit did not need such a concept--tests to be run were described either by classes containing
* tests or a tree of {@link org.junit.Test}s. However, we want to support filtering and sorting,
* so we need a more abstract specification than the tests themselves and a richer
- * specification than just the classes.</p>
- *
- * <p>The flow when JUnit runs tests is that a <code>Request</code> specifies some tests to be run ->
- * a {@link org.junit.runner.Runner} is created for each class implied by the <code>Request</code> ->
- * the {@link org.junit.runner.Runner} returns a detailed {@link org.junit.runner.Description}
- * which is a tree structure of the tests to be run.</p>
+ * specification than just the classes.
+ *
+ * <p>The flow when JUnit runs tests is that a <code>Request</code> specifies some tests to be run -&gt;
+ * a {@link org.junit.runner.Runner} is created for each class implied by the <code>Request</code> -&gt;
+ * the {@link org.junit.runner.Runner} returns a detailed {@link org.junit.runner.Description}
+ * which is a tree structure of the tests to be run.
+ *
+ * @since 4.0
*/
public abstract class Request {
- /**
- * Create a <code>Request</code> that, when processed, will run a single test.
- * This is done by filtering out all other tests. This method is used to support rerunning
- * single tests.
- * @param clazz the class of the test
- * @param methodName the name of the test
- * @return a <code>Request</code> that will cause a single test be run
- */
- public static Request method(Class<?> clazz, String methodName) {
- Description method= Description.createTestDescription(clazz, methodName);
- return Request.aClass(clazz).filterWith(method);
- }
+ /**
+ * Create a <code>Request</code> that, when processed, will run a single test.
+ * This is done by filtering out all other tests. This method is used to support rerunning
+ * single tests.
+ *
+ * @param clazz the class of the test
+ * @param methodName the name of the test
+ * @return a <code>Request</code> that will cause a single test be run
+ */
+ public static Request method(Class<?> clazz, String methodName) {
+ Description method = Description.createTestDescription(clazz, methodName);
+ return Request.aClass(clazz).filterWith(method);
+ }
- /**
- * Create a <code>Request</code> that, when processed, will run all the tests
- * in a class. The odd name is necessary because <code>class</code> is a reserved word.
- * @param clazz the class containing the tests
- * @return a <code>Request</code> that will cause all tests in the class to be run
- */
- public static Request aClass(Class<?> clazz) {
- return new ClassRequest(clazz);
- }
+ /**
+ * Create a <code>Request</code> that, when processed, will run all the tests
+ * in a class. The odd name is necessary because <code>class</code> is a reserved word.
+ *
+ * @param clazz the class containing the tests
+ * @return a <code>Request</code> that will cause all tests in the class to be run
+ */
+ public static Request aClass(Class<?> clazz) {
+ return new ClassRequest(clazz);
+ }
- /**
- * Create a <code>Request</code> that, when processed, will run all the tests
- * in a class. If the class has a suite() method, it will be ignored.
- * @param clazz the class containing the tests
- * @return a <code>Request</code> that will cause all tests in the class to be run
- */
- public static Request classWithoutSuiteMethod(Class<?> clazz) {
- return new ClassRequest(clazz, false);
- }
+ /**
+ * Create a <code>Request</code> that, when processed, will run all the tests
+ * in a class. If the class has a suite() method, it will be ignored.
+ *
+ * @param clazz the class containing the tests
+ * @return a <code>Request</code> that will cause all tests in the class to be run
+ */
+ public static Request classWithoutSuiteMethod(Class<?> clazz) {
+ return new ClassRequest(clazz, false);
+ }
- /**
- * Create a <code>Request</code> that, when processed, will run all the tests
- * in a set of classes.
- * @param computer Helps construct Runners from classes
- * @param classes the classes containing the tests
- * @return a <code>Request</code> that will cause all tests in the classes to be run
- */
- public static Request classes(Computer computer, Class<?>... classes) {
- try {
- AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
- Runner suite= computer.getSuite(builder, classes);
- return runner(suite);
- } catch (InitializationError e) {
- throw new RuntimeException(
- "Bug in saff's brain: Suite constructor, called as above, should always complete");
- }
- }
+ /**
+ * Create a <code>Request</code> that, when processed, will run all the tests
+ * in a set of classes.
+ *
+ * @param computer Helps construct Runners from classes
+ * @param classes the classes containing the tests
+ * @return a <code>Request</code> that will cause all tests in the classes to be run
+ */
+ public static Request classes(Computer computer, Class<?>... classes) {
+ try {
+ AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
+ Runner suite = computer.getSuite(builder, classes);
+ return runner(suite);
+ } catch (InitializationError e) {
+ throw new RuntimeException(
+ "Bug in saff's brain: Suite constructor, called as above, should always complete");
+ }
+ }
- /**
- * Create a <code>Request</code> that, when processed, will run all the tests
- * in a set of classes with the default <code>Computer</code>.
- * @param classes the classes containing the tests
- * @return a <code>Request</code> that will cause all tests in the classes to be run
- */
- public static Request classes(Class<?>... classes) {
- return classes(JUnitCore.defaultComputer(), classes);
- }
-
+ /**
+ * Create a <code>Request</code> that, when processed, will run all the tests
+ * in a set of classes with the default <code>Computer</code>.
+ *
+ * @param classes the classes containing the tests
+ * @return a <code>Request</code> that will cause all tests in the classes to be run
+ */
+ public static Request classes(Class<?>... classes) {
+ return classes(JUnitCore.defaultComputer(), classes);
+ }
- /**
- * Not used within JUnit. Clients should simply instantiate ErrorReportingRunner themselves
- */
- @Deprecated
- public static Request errorReport(Class<?> klass, Throwable cause) {
- return runner(new ErrorReportingRunner(klass, cause));
- }
- /**
- * @param runner the runner to return
- * @return a <code>Request</code> that will run the given runner when invoked
- */
- public static Request runner(final Runner runner) {
- return new Request(){
- @Override
- public Runner getRunner() {
- return runner;
- }
- };
- }
+ /**
+ * Creates a {@link Request} that, when processed, will report an error for the given
+ * test class with the given cause.
+ */
+ public static Request errorReport(Class<?> klass, Throwable cause) {
+ return runner(new ErrorReportingRunner(klass, cause));
+ }
- /**
- * Returns a {@link Runner} for this Request
- * @return corresponding {@link Runner} for this Request
- */
- public abstract Runner getRunner();
+ /**
+ * @param runner the runner to return
+ * @return a <code>Request</code> that will run the given runner when invoked
+ */
+ public static Request runner(final Runner runner) {
+ return new Request() {
+ @Override
+ public Runner getRunner() {
+ return runner;
+ }
+ };
+ }
- /**
- * Returns a Request that only contains those tests that should run when
- * <code>filter</code> is applied
- * @param filter The {@link Filter} to apply to this Request
- * @return the filtered Request
- */
- public Request filterWith(Filter filter) {
- return new FilterRequest(this, filter);
- }
+ /**
+ * Returns a {@link Runner} for this Request
+ *
+ * @return corresponding {@link Runner} for this Request
+ */
+ public abstract Runner getRunner();
- /**
- * Returns a Request that only runs contains tests whose {@link Description}
- * equals <code>desiredDescription</code>
- * @param desiredDescription {@link Description} of those tests that should be run
- * @return the filtered Request
- */
- public Request filterWith(final Description desiredDescription) {
- return filterWith(Filter.matchMethodDescription(desiredDescription));
- }
+ /**
+ * Returns a Request that only contains those tests that should run when
+ * <code>filter</code> is applied
+ *
+ * @param filter The {@link Filter} to apply to this Request
+ * @return the filtered Request
+ */
+ public Request filterWith(Filter filter) {
+ return new FilterRequest(this, filter);
+ }
- /**
- * Returns a Request whose Tests can be run in a certain order, defined by
- * <code>comparator</code>
- *
- * For example, here is code to run a test suite in alphabetical order:
- *
- * <pre>
- private static Comparator<Description> forward() {
- return new Comparator<Description>() {
- public int compare(Description o1, Description o2) {
- return o1.getDisplayName().compareTo(o2.getDisplayName());
- }
- };
- }
-
- public static main() {
- new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
- }
- * </pre>
- *
- * @param comparator definition of the order of the tests in this Request
- * @return a Request with ordered Tests
- */
- public Request sortWith(Comparator<Description> comparator) {
- return new SortingRequest(this, comparator);
- }
+ /**
+ * Returns a Request that only runs contains tests whose {@link Description}
+ * equals <code>desiredDescription</code>
+ *
+ * @param desiredDescription {@link Description} of those tests that should be run
+ * @return the filtered Request
+ */
+ public Request filterWith(final Description desiredDescription) {
+ return filterWith(Filter.matchMethodDescription(desiredDescription));
+ }
+
+ /**
+ * Returns a Request whose Tests can be run in a certain order, defined by
+ * <code>comparator</code>
+ * <p>
+ * For example, here is code to run a test suite in alphabetical order:
+ * <pre>
+ * private static Comparator&lt;Description&gt; forward() {
+ * return new Comparator&lt;Description&gt;() {
+ * public int compare(Description o1, Description o2) {
+ * return o1.getDisplayName().compareTo(o2.getDisplayName());
+ * }
+ * };
+ * }
+ *
+ * public static main() {
+ * new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
+ * }
+ * </pre>
+ *
+ * @param comparator definition of the order of the tests in this Request
+ * @return a Request with ordered Tests
+ */
+ public Request sortWith(Comparator<Description> comparator) {
+ return new SortingRequest(this, comparator);
+ }
}
diff --git a/src/main/java/org/junit/runner/Result.java b/src/main/java/org/junit/runner/Result.java
index edfb97c..73ad059 100644
--- a/src/main/java/org/junit/runner/Result.java
+++ b/src/main/java/org/junit/runner/Result.java
@@ -1,106 +1,196 @@
package org.junit.runner;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamField;
import java.io.Serializable;
import java.util.ArrayList;
-import java.util.List;
import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
/**
- * A <code>Result</code> collects and summarizes information from running multiple
- * tests. Since tests are expected to run correctly, successful tests are only noted in
- * the count of tests that ran.
+ * A <code>Result</code> collects and summarizes information from running multiple tests.
+ * All tests are counted -- additional information is collected from tests that fail.
+ *
+ * @since 4.0
*/
public class Result implements Serializable {
- private static final long serialVersionUID = 1L;
- private AtomicInteger fCount = new AtomicInteger();
- private AtomicInteger fIgnoreCount= new AtomicInteger();
- private final List<Failure> fFailures= Collections.synchronizedList(new ArrayList<Failure>());
- private long fRunTime= 0;
- private long fStartTime;
-
- /**
- * @return the number of tests run
- */
- public int getRunCount() {
- return fCount.get();
- }
-
- /**
- * @return the number of tests that failed during the run
- */
- public int getFailureCount() {
- return fFailures.size();
- }
-
- /**
- * @return the number of milliseconds it took to run the entire suite to run
- */
- public long getRunTime() {
- return fRunTime;
- }
-
- /**
- * @return the {@link Failure}s describing tests that failed and the problems they encountered
- */
- public List<Failure> getFailures() {
- return fFailures;
- }
-
- /**
- * @return the number of tests ignored during the run
- */
- public int getIgnoreCount() {
- return fIgnoreCount.get();
- }
-
- /**
- * @return <code>true</code> if all tests succeeded
- */
- public boolean wasSuccessful() {
- return getFailureCount() == 0;
- }
-
- private class Listener extends RunListener {
- @Override
- public void testRunStarted(Description description) throws Exception {
- fStartTime= System.currentTimeMillis();
- }
-
- @Override
- public void testRunFinished(Result result) throws Exception {
- long endTime= System.currentTimeMillis();
- fRunTime+= endTime - fStartTime;
- }
-
- @Override
- public void testFinished(Description description) throws Exception {
- fCount.getAndIncrement();
- }
-
- @Override
- public void testFailure(Failure failure) throws Exception {
- fFailures.add(failure);
- }
-
- @Override
- public void testIgnored(Description description) throws Exception {
- fIgnoreCount.getAndIncrement();
- }
-
- @Override
- public void testAssumptionFailure(Failure failure) {
- // do nothing: same as passing (for 4.5; may change in 4.6)
- }
- }
-
- /**
- * Internal use only.
- */
- public RunListener createListener() {
- return new Listener();
- }
+ private static final long serialVersionUID = 1L;
+ private static final ObjectStreamField[] serialPersistentFields =
+ ObjectStreamClass.lookup(SerializedForm.class).getFields();
+ private final AtomicInteger count;
+ private final AtomicInteger ignoreCount;
+ private final CopyOnWriteArrayList<Failure> failures;
+ private final AtomicLong runTime;
+ private final AtomicLong startTime;
+
+ /** Only set during deserialization process. */
+ private SerializedForm serializedForm;
+
+ public Result() {
+ count = new AtomicInteger();
+ ignoreCount = new AtomicInteger();
+ failures = new CopyOnWriteArrayList<Failure>();
+ runTime = new AtomicLong();
+ startTime = new AtomicLong();
+ }
+
+ private Result(SerializedForm serializedForm) {
+ count = serializedForm.fCount;
+ ignoreCount = serializedForm.fIgnoreCount;
+ failures = new CopyOnWriteArrayList<Failure>(serializedForm.fFailures);
+ runTime = new AtomicLong(serializedForm.fRunTime);
+ startTime = new AtomicLong(serializedForm.fStartTime);
+ }
+
+ /**
+ * @return the number of tests run
+ */
+ public int getRunCount() {
+ return count.get();
+ }
+
+ /**
+ * @return the number of tests that failed during the run
+ */
+ public int getFailureCount() {
+ return failures.size();
+ }
+
+ /**
+ * @return the number of milliseconds it took to run the entire suite to run
+ */
+ public long getRunTime() {
+ return runTime.get();
+ }
+
+ /**
+ * @return the {@link Failure}s describing tests that failed and the problems they encountered
+ */
+ public List<Failure> getFailures() {
+ return failures;
+ }
+
+ /**
+ * @return the number of tests ignored during the run
+ */
+ public int getIgnoreCount() {
+ return ignoreCount.get();
+ }
+
+ /**
+ * @return <code>true</code> if all tests succeeded
+ */
+ public boolean wasSuccessful() {
+ return getFailureCount() == 0;
+ }
+
+ private void writeObject(ObjectOutputStream s) throws IOException {
+ SerializedForm serializedForm = new SerializedForm(this);
+ serializedForm.serialize(s);
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException {
+ serializedForm = SerializedForm.deserialize(s);
+ }
+
+ private Object readResolve() {
+ return new Result(serializedForm);
+ }
+
+ @RunListener.ThreadSafe
+ private class Listener extends RunListener {
+ @Override
+ public void testRunStarted(Description description) throws Exception {
+ startTime.set(System.currentTimeMillis());
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ long endTime = System.currentTimeMillis();
+ runTime.addAndGet(endTime - startTime.get());
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ count.getAndIncrement();
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ failures.add(failure);
+ }
+
+ @Override
+ public void testIgnored(Description description) throws Exception {
+ ignoreCount.getAndIncrement();
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ // do nothing: same as passing (for 4.5; may change in 4.6)
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public RunListener createListener() {
+ return new Listener();
+ }
+
+ /**
+ * Represents the serialized output of {@code Result}. The fields on this
+ * class match the files that {@code Result} had in JUnit 4.11.
+ */
+ private static class SerializedForm implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private final AtomicInteger fCount;
+ private final AtomicInteger fIgnoreCount;
+ private final List<Failure> fFailures;
+ private final long fRunTime;
+ private final long fStartTime;
+
+ public SerializedForm(Result result) {
+ fCount = result.count;
+ fIgnoreCount = result.ignoreCount;
+ fFailures = Collections.synchronizedList(new ArrayList<Failure>(result.failures));
+ fRunTime = result.runTime.longValue();
+ fStartTime = result.startTime.longValue();
+ }
+
+ @SuppressWarnings("unchecked")
+ private SerializedForm(ObjectInputStream.GetField fields) throws IOException {
+ fCount = (AtomicInteger) fields.get("fCount", null);
+ fIgnoreCount = (AtomicInteger) fields.get("fIgnoreCount", null);
+ fFailures = (List<Failure>) fields.get("fFailures", null);
+ fRunTime = fields.get("fRunTime", 0L);
+ fStartTime = fields.get("fStartTime", 0L);
+ }
+
+ public void serialize(ObjectOutputStream s) throws IOException {
+ ObjectOutputStream.PutField fields = s.putFields();
+ fields.put("fCount", fCount);
+ fields.put("fIgnoreCount", fIgnoreCount);
+ fields.put("fFailures", fFailures);
+ fields.put("fRunTime", fRunTime);
+ fields.put("fStartTime", fStartTime);
+ s.writeFields();
+ }
+
+ public static SerializedForm deserialize(ObjectInputStream s)
+ throws ClassNotFoundException, IOException {
+ ObjectInputStream.GetField fields = s.readFields();
+ return new SerializedForm(fields);
+ }
+ }
}
diff --git a/src/main/java/org/junit/runner/RunWith.java b/src/main/java/org/junit/runner/RunWith.java
index 602edf0..3428ee2 100644
--- a/src/main/java/org/junit/runner/RunWith.java
+++ b/src/main/java/org/junit/runner/RunWith.java
@@ -7,28 +7,30 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * When a class is annotated with <code>&#064;RunWith</code> or extends a class annotated
- * with <code>&#064;RunWith</code>, JUnit will invoke the class it references to run the
- * tests in that class instead of the runner built into JUnit. We added this feature late
- * in development. While it seems powerful we expect the runner API to change as we learn
- * how people really use it. Some of the classes that are currently internal will likely
+ * When a class is annotated with <code>&#064;RunWith</code> or extends a class annotated
+ * with <code>&#064;RunWith</code>, JUnit will invoke the class it references to run the
+ * tests in that class instead of the runner built into JUnit. We added this feature late
+ * in development. While it seems powerful we expect the runner API to change as we learn
+ * how people really use it. Some of the classes that are currently internal will likely
* be refined and become public.
- *
+ *
* For example, suites in JUnit 4 are built using RunWith, and a custom runner named Suite:
- *
+ *
* <pre>
* &#064;RunWith(Suite.class)
* &#064;SuiteClasses({ATest.class, BTest.class, CTest.class})
* public class ABCSuite {
* }
* </pre>
+ *
+ * @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface RunWith {
- /**
- * @return a Runner class (must have a constructor that takes a single Class to run)
- */
- Class<? extends Runner> value();
+ /**
+ * @return a Runner class (must have a constructor that takes a single Class to run)
+ */
+ Class<? extends Runner> value();
}
diff --git a/src/main/java/org/junit/runner/Runner.java b/src/main/java/org/junit/runner/Runner.java
index 39e424f..d728dd8 100644
--- a/src/main/java/org/junit/runner/Runner.java
+++ b/src/main/java/org/junit/runner/Runner.java
@@ -9,32 +9,35 @@ import org.junit.runner.notification.RunNotifier;
* a custom runner, in addition to implementing the abstract methods here you must
* also provide a constructor that takes as an argument the {@link Class} containing
* the tests.
- * <p/>
- * The default runner implementation guarantees that the instances of the test case
+ *
+ * <p>The default runner implementation guarantees that the instances of the test case
* class will be constructed immediately before running the test and that the runner
- * will retain no reference to the test case instances, generally making them
+ * will retain no reference to the test case instances, generally making them
* available for garbage collection.
- *
+ *
* @see org.junit.runner.Description
* @see org.junit.runner.RunWith
+ * @since 4.0
*/
public abstract class Runner implements Describable {
- /* (non-Javadoc)
- * @see org.junit.runner.Describable#getDescription()
- */
- public abstract Description getDescription();
+ /*
+ * (non-Javadoc)
+ * @see org.junit.runner.Describable#getDescription()
+ */
+ public abstract Description getDescription();
- /**
- * Run the tests for this runner.
- * @param notifier will be notified of events while tests are being run--tests being
- * started, finishing, and failing
- */
- public abstract void run(RunNotifier notifier);
-
- /**
- * @return the number of tests to be run by the receiver
- */
- public int testCount() {
- return getDescription().testCount();
- }
-} \ No newline at end of file
+ /**
+ * Run the tests for this runner.
+ *
+ * @param notifier will be notified of events while tests are being run--tests being
+ * started, finishing, and failing
+ */
+ public abstract void run(RunNotifier notifier);
+
+ /**
+ * @return the number of tests to be run by the receiver
+ */
+ public int testCount() {
+ return getDescription().testCount();
+ }
+}
diff --git a/src/main/java/org/junit/runner/manipulation/Filter.java b/src/main/java/org/junit/runner/manipulation/Filter.java
index c0f31b0..0287351 100644
--- a/src/main/java/org/junit/runner/manipulation/Filter.java
+++ b/src/main/java/org/junit/runner/manipulation/Filter.java
@@ -7,108 +7,116 @@ import org.junit.runner.Request;
* The canonical case of filtering is when you want to run a single test method in a class. Rather
* than introduce runner API just for that one case, JUnit provides a general filtering mechanism.
* If you want to filter the tests to be run, extend <code>Filter</code> and apply an instance of
- * your filter to the {@link org.junit.runner.Request} before running it (see
- * {@link org.junit.runner.JUnitCore#run(Request)}. Alternatively, apply a <code>Filter</code> to
- * a {@link org.junit.runner.Runner} before running tests (for example, in conjunction with
+ * your filter to the {@link org.junit.runner.Request} before running it (see
+ * {@link org.junit.runner.JUnitCore#run(Request)}. Alternatively, apply a <code>Filter</code> to
+ * a {@link org.junit.runner.Runner} before running tests (for example, in conjunction with
* {@link org.junit.runner.RunWith}.
+ *
+ * @since 4.0
*/
public abstract class Filter {
- /**
- * A null <code>Filter</code> that passes all tests through.
- */
- public static Filter ALL= new Filter() {
- @Override
- public boolean shouldRun(Description description) {
- return true;
- }
+ /**
+ * A null <code>Filter</code> that passes all tests through.
+ */
+ public static final Filter ALL = new Filter() {
+ @Override
+ public boolean shouldRun(Description description) {
+ return true;
+ }
- @Override
- public String describe() {
- return "all tests";
- }
-
- @Override
- public void apply(Object child) throws NoTestsRemainException {
- // do nothing
- }
+ @Override
+ public String describe() {
+ return "all tests";
+ }
- @Override
- public Filter intersect(Filter second) {
- return second;
- }
- };
-
- /**
- * Returns a {@code Filter} that only runs the single method described by
- * {@code desiredDescription}
- */
- public static Filter matchMethodDescription(final Description desiredDescription) {
- return new Filter() {
- @Override
- public boolean shouldRun(Description description) {
- if (description.isTest())
- return desiredDescription.equals(description);
-
- // explicitly check if any children want to run
- for (Description each : description.getChildren())
- if (shouldRun(each))
- return true;
- return false;
- }
+ @Override
+ public void apply(Object child) throws NoTestsRemainException {
+ // do nothing
+ }
- @Override
- public String describe() {
- return String.format("Method %s", desiredDescription.getDisplayName());
- }
- };
- }
+ @Override
+ public Filter intersect(Filter second) {
+ return second;
+ }
+ };
+ /**
+ * Returns a {@code Filter} that only runs the single method described by
+ * {@code desiredDescription}
+ */
+ public static Filter matchMethodDescription(final Description desiredDescription) {
+ return new Filter() {
+ @Override
+ public boolean shouldRun(Description description) {
+ if (description.isTest()) {
+ return desiredDescription.equals(description);
+ }
- /**
- * @param description the description of the test to be run
- * @return <code>true</code> if the test should be run
- */
- public abstract boolean shouldRun(Description description);
+ // explicitly check if any children want to run
+ for (Description each : description.getChildren()) {
+ if (shouldRun(each)) {
+ return true;
+ }
+ }
+ return false;
+ }
- /**
- * Returns a textual description of this Filter
- * @return a textual description of this Filter
- */
- public abstract String describe();
+ @Override
+ public String describe() {
+ return String.format("Method %s", desiredDescription.getDisplayName());
+ }
+ };
+ }
- /**
- * Invoke with a {@link org.junit.runner.Runner} to cause all tests it intends to run
- * to first be checked with the filter. Only those that pass the filter will be run.
- * @param child the runner to be filtered by the receiver
- * @throws NoTestsRemainException if the receiver removes all tests
- */
- public void apply(Object child) throws NoTestsRemainException {
- if (!(child instanceof Filterable))
- return;
- Filterable filterable= (Filterable) child;
- filterable.filter(this);
- }
- /**
- * Returns a new Filter that accepts the intersection of the tests accepted
- * by this Filter and {@code second}
- */
- public Filter intersect(final Filter second) {
- if (second == this || second == ALL) {
- return this;
- }
- final Filter first= this;
- return new Filter() {
- @Override
- public boolean shouldRun(Description description) {
- return first.shouldRun(description)
- && second.shouldRun(description);
- }
+ /**
+ * @param description the description of the test to be run
+ * @return <code>true</code> if the test should be run
+ */
+ public abstract boolean shouldRun(Description description);
- @Override
- public String describe() {
- return first.describe() + " and " + second.describe();
- }
- };
- }
+ /**
+ * Returns a textual description of this Filter
+ *
+ * @return a textual description of this Filter
+ */
+ public abstract String describe();
+
+ /**
+ * Invoke with a {@link org.junit.runner.Runner} to cause all tests it intends to run
+ * to first be checked with the filter. Only those that pass the filter will be run.
+ *
+ * @param child the runner to be filtered by the receiver
+ * @throws NoTestsRemainException if the receiver removes all tests
+ */
+ public void apply(Object child) throws NoTestsRemainException {
+ if (!(child instanceof Filterable)) {
+ return;
+ }
+ Filterable filterable = (Filterable) child;
+ filterable.filter(this);
+ }
+
+ /**
+ * Returns a new Filter that accepts the intersection of the tests accepted
+ * by this Filter and {@code second}
+ */
+ public Filter intersect(final Filter second) {
+ if (second == this || second == ALL) {
+ return this;
+ }
+ final Filter first = this;
+ return new Filter() {
+ @Override
+ public boolean shouldRun(Description description) {
+ return first.shouldRun(description)
+ && second.shouldRun(description);
+ }
+
+ @Override
+ public String describe() {
+ return first.describe() + " and " + second.describe();
+ }
+ };
+ }
}
diff --git a/src/main/java/org/junit/runner/manipulation/Filterable.java b/src/main/java/org/junit/runner/manipulation/Filterable.java
index 782c0f7..d605027 100644
--- a/src/main/java/org/junit/runner/manipulation/Filterable.java
+++ b/src/main/java/org/junit/runner/manipulation/Filterable.java
@@ -3,14 +3,17 @@ package org.junit.runner.manipulation;
/**
* Runners that allow filtering should implement this interface. Implement {@link #filter(Filter)}
* to remove tests that don't pass the filter.
+ *
+ * @since 4.0
*/
public interface Filterable {
- /**
- * Remove tests that don't pass the parameter <code>filter</code>.
- * @param filter the {@link Filter} to apply
- * @throws NoTestsRemainException if all tests are filtered out
- */
- void filter(Filter filter) throws NoTestsRemainException;
+ /**
+ * Remove tests that don't pass the parameter <code>filter</code>.
+ *
+ * @param filter the {@link Filter} to apply
+ * @throws NoTestsRemainException if all tests are filtered out
+ */
+ void filter(Filter filter) throws NoTestsRemainException;
}
diff --git a/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java b/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java
index 03fb3bf..21935bd 100644
--- a/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java
+++ b/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java
@@ -2,7 +2,9 @@ package org.junit.runner.manipulation;
/**
* Thrown when a filter removes all tests from a runner.
+ *
+ * @since 4.0
*/
public class NoTestsRemainException extends Exception {
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
}
diff --git a/src/main/java/org/junit/runner/manipulation/Sortable.java b/src/main/java/org/junit/runner/manipulation/Sortable.java
index fec1d0c..9ac864c 100644
--- a/src/main/java/org/junit/runner/manipulation/Sortable.java
+++ b/src/main/java/org/junit/runner/manipulation/Sortable.java
@@ -5,13 +5,16 @@ package org.junit.runner.manipulation;
* failed first, you can reduce the average time to the first test failing. Test sorting should not be used to
* cope with order dependencies between tests. Tests that are isolated from each other are less
* expensive to maintain and can be run individually.
+ *
+ * @since 4.0
*/
public interface Sortable {
- /**
- * Sorts the tests using <code>sorter</code>
- * @param sorter the {@link Sorter} to use for sorting the tests
- */
- public void sort(Sorter sorter);
+ /**
+ * Sorts the tests using <code>sorter</code>
+ *
+ * @param sorter the {@link Sorter} to use for sorting the tests
+ */
+ public void sort(Sorter sorter);
}
diff --git a/src/main/java/org/junit/runner/manipulation/Sorter.java b/src/main/java/org/junit/runner/manipulation/Sorter.java
index 242df14..20192d0 100644
--- a/src/main/java/org/junit/runner/manipulation/Sorter.java
+++ b/src/main/java/org/junit/runner/manipulation/Sorter.java
@@ -7,40 +7,42 @@ import org.junit.runner.Description;
/**
* A <code>Sorter</code> orders tests. In general you will not need
* to use a <code>Sorter</code> directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}.
- *
- *
+ *
+ * @since 4.0
*/
public class Sorter implements Comparator<Description> {
- /**
- * NULL is a <code>Sorter</code> that leaves elements in an undefined order
- */
- public static Sorter NULL= new Sorter(new Comparator<Description>() {
- public int compare(Description o1, Description o2) {
- return 0;
- }});
- private final Comparator<Description> fComparator;
+ /**
+ * NULL is a <code>Sorter</code> that leaves elements in an undefined order
+ */
+ public static final Sorter NULL = new Sorter(new Comparator<Description>() {
+ public int compare(Description o1, Description o2) {
+ return 0;
+ }
+ });
- /**
- * Creates a <code>Sorter</code> that uses <code>comparator</code>
- * to sort tests
- * @param comparator the {@link Comparator} to use when sorting tests
- */
- public Sorter(Comparator<Description> comparator) {
- fComparator= comparator;
- }
+ private final Comparator<Description> comparator;
- /**
- * Sorts the test in <code>runner</code> using <code>comparator</code>
- * @param object
- */
- public void apply(Object object) {
- if (object instanceof Sortable) {
- Sortable sortable = (Sortable) object;
- sortable.sort(this);
- }
- }
+ /**
+ * Creates a <code>Sorter</code> that uses <code>comparator</code>
+ * to sort tests
+ *
+ * @param comparator the {@link Comparator} to use when sorting tests
+ */
+ public Sorter(Comparator<Description> comparator) {
+ this.comparator = comparator;
+ }
- public int compare(Description o1, Description o2) {
- return fComparator.compare(o1, o2);
- }
+ /**
+ * Sorts the test in <code>runner</code> using <code>comparator</code>
+ */
+ public void apply(Object object) {
+ if (object instanceof Sortable) {
+ Sortable sortable = (Sortable) object;
+ sortable.sort(this);
+ }
+ }
+
+ public int compare(Description o1, Description o2) {
+ return comparator.compare(o1, o2);
+ }
}
diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java
index 501caa5..c03b4c1 100644
--- a/src/main/java/org/junit/runner/notification/Failure.java
+++ b/src/main/java/org/junit/runner/notification/Failure.java
@@ -12,68 +12,76 @@ import org.junit.runner.Description;
* will be of a single test. However, if problems are encountered while constructing the
* test (for example, if a {@link org.junit.BeforeClass} method is not static), it may describe
* something other than a single test.
+ *
+ * @since 4.0
*/
public class Failure implements Serializable {
- private static final long serialVersionUID = 1L;
- private final Description fDescription;
- private final Throwable fThrownException;
+ private static final long serialVersionUID = 1L;
- /**
- * Constructs a <code>Failure</code> with the given description and exception.
- * @param description a {@link org.junit.runner.Description} of the test that failed
- * @param thrownException the exception that was thrown while running the test
- */
- public Failure(Description description, Throwable thrownException) {
- fThrownException = thrownException;
- fDescription= description;
- }
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final Description fDescription;
+ private final Throwable fThrownException;
- /**
- * @return a user-understandable label for the test
- */
- public String getTestHeader() {
- return fDescription.getDisplayName();
- }
+ /**
+ * Constructs a <code>Failure</code> with the given description and exception.
+ *
+ * @param description a {@link org.junit.runner.Description} of the test that failed
+ * @param thrownException the exception that was thrown while running the test
+ */
+ public Failure(Description description, Throwable thrownException) {
+ this.fThrownException = thrownException;
+ this.fDescription = description;
+ }
- /**
- * @return the raw description of the context of the failure.
- */
- public Description getDescription() {
- return fDescription;
- }
+ /**
+ * @return a user-understandable label for the test
+ */
+ public String getTestHeader() {
+ return fDescription.getDisplayName();
+ }
- /**
- * @return the exception thrown
- */
+ /**
+ * @return the raw description of the context of the failure.
+ */
+ public Description getDescription() {
+ return fDescription;
+ }
- public Throwable getException() {
- return fThrownException;
- }
+ /**
+ * @return the exception thrown
+ */
- @Override
- public String toString() {
- StringBuffer buffer= new StringBuffer();
- buffer.append(getTestHeader() + ": "+fThrownException.getMessage());
- return buffer.toString();
- }
+ public Throwable getException() {
+ return fThrownException;
+ }
- /**
- * Convenience method
- * @return the printed form of the exception
- */
- public String getTrace() {
- StringWriter stringWriter= new StringWriter();
- PrintWriter writer= new PrintWriter(stringWriter);
- getException().printStackTrace(writer);
- StringBuffer buffer= stringWriter.getBuffer();
- return buffer.toString();
- }
+ @Override
+ public String toString() {
+ return getTestHeader() + ": " + fThrownException.getMessage();
+ }
- /**
- * Convenience method
- * @return the message of the thrown exception
- */
- public String getMessage() {
- return getException().getMessage();
- }
+ /**
+ * Convenience method
+ *
+ * @return the printed form of the exception
+ */
+ public String getTrace() {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ getException().printStackTrace(writer);
+ return stringWriter.toString();
+ }
+
+ /**
+ * Convenience method
+ *
+ * @return the message of the thrown exception
+ */
+ public String getMessage() {
+ return getException().getMessage();
+ }
}
diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java
index ffe8134..db9d8c1 100644
--- a/src/main/java/org/junit/runner/notification/RunListener.java
+++ b/src/main/java/org/junit/runner/notification/RunListener.java
@@ -1,15 +1,21 @@
package org.junit.runner.notification;
-import org.junit.internal.AssumptionViolatedException;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
import org.junit.runner.Description;
import org.junit.runner.Result;
/**
- * <p>If you need to respond to the events during a test run, extend <code>RunListener</code>
- * and override the appropriate methods. If a listener throws an exception while processing a
- * test event, it will be removed for the remainder of the test run.</p>
- *
- * <p>For example, suppose you have a <code>Cowbell</code>
+ * Register an instance of this class with {@link RunNotifier} to be notified
+ * of events that occur during a test run. All of the methods in this class
+ * are abstract and have no implementation; override one or more methods to
+ * receive events.
+ * <p>
+ * For example, suppose you have a <code>Cowbell</code>
* class that you want to make a noise whenever a test fails. You could write:
* <pre>
* public class RingingListener extends RunListener {
@@ -18,9 +24,8 @@ import org.junit.runner.Result;
* }
* }
* </pre>
- * </p>
- *
- * <p>To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
+ * <p>
+ * To invoke your listener, you need to run your tests through <code>JUnitCore</code>.
* <pre>
* public void main(String... args) {
* JUnitCore core= new JUnitCore();
@@ -28,66 +33,108 @@ import org.junit.runner.Result;
* core.run(MyTestClass.class);
* }
* </pre>
- * </p>
+ * <p>
+ * If a listener throws an exception for a test event, the other listeners will
+ * have their {@link RunListener#testFailure(Failure)} called with a {@code Description}
+ * of {@link Description#TEST_MECHANISM} to indicate the failure.
+ * <p>
+ * By default, JUnit will synchronize calls to your listener. If your listener
+ * is thread-safe and you want to allow JUnit to call your listener from
+ * multiple threads when tests are run in parallel, you can annotate your
+ * test class with {@link RunListener.ThreadSafe}.
+ * <p>
+ * Listener methods will be called from the same thread as is running
+ * the test, unless otherwise indicated by the method Javadoc
+ *
* @see org.junit.runner.JUnitCore
+ * @since 4.0
*/
public class RunListener {
- /**
- * Called before any tests have been run.
- * @param description describes the tests to be run
- */
- public void testRunStarted(Description description) throws Exception {
- }
-
- /**
- * Called when all tests have finished
- * @param result the summary of the test run, including all the tests that failed
- */
- public void testRunFinished(Result result) throws Exception {
- }
-
- /**
- * Called when an atomic test is about to be started.
- * @param description the description of the test that is about to be run
- * (generally a class and method name)
- */
- public void testStarted(Description description) throws Exception {
- }
+ /**
+ * Called before any tests have been run. This may be called on an
+ * arbitrary thread.
+ *
+ * @param description describes the tests to be run
+ */
+ public void testRunStarted(Description description) throws Exception {
+ }
- /**
- * Called when an atomic test has finished, whether the test succeeds or fails.
- * @param description the description of the test that just ran
- */
- public void testFinished(Description description) throws Exception {
- }
+ /**
+ * Called when all tests have finished. This may be called on an
+ * arbitrary thread.
+ *
+ * @param result the summary of the test run, including all the tests that failed
+ */
+ public void testRunFinished(Result result) throws Exception {
+ }
- /**
- * Called when an atomic test fails.
- * @param failure describes the test that failed and the exception that was thrown
- */
- public void testFailure(Failure failure) throws Exception {
- }
+ /**
+ * Called when an atomic test is about to be started.
+ *
+ * @param description the description of the test that is about to be run
+ * (generally a class and method name)
+ */
+ public void testStarted(Description description) throws Exception {
+ }
- /**
- * Called when an atomic test flags that it assumes a condition that is
- * false
- *
- * @param failure
- * describes the test that failed and the
- * {@link AssumptionViolatedException} that was thrown
- */
- public void testAssumptionFailure(Failure failure) {
- }
+ /**
+ * Called when an atomic test has finished, whether the test succeeds or fails.
+ *
+ * @param description the description of the test that just ran
+ */
+ public void testFinished(Description description) throws Exception {
+ }
- /**
- * Called when a test will not be run, generally because a test method is annotated
- * with {@link org.junit.Ignore}.
- *
- * @param description describes the test that will not be run
- */
- public void testIgnored(Description description) throws Exception {
- }
-}
+ /**
+ * Called when an atomic test fails, or when a listener throws an exception.
+ *
+ * <p>In the case of a failure of an atomic test, this method will be called
+ * with the same {@code Description} passed to
+ * {@link #testStarted(Description)}, from the same thread that called
+ * {@link #testStarted(Description)}.
+ *
+ * <p>In the case of a listener throwing an exception, this will be called with
+ * a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called
+ * on an arbitrary thread.
+ *
+ * @param failure describes the test that failed and the exception that was thrown
+ */
+ public void testFailure(Failure failure) throws Exception {
+ }
+
+ /**
+ * Called when an atomic test flags that it assumes a condition that is
+ * false
+ *
+ * @param failure describes the test that failed and the
+ * {@link org.junit.AssumptionViolatedException} that was thrown
+ */
+ public void testAssumptionFailure(Failure failure) {
+ }
+ /**
+ * Called when a test will not be run, generally because a test method is annotated
+ * with {@link org.junit.Ignore}.
+ *
+ * @param description describes the test that will not be run
+ */
+ public void testIgnored(Description description) throws Exception {
+ }
+
+ /**
+ * Indicates a {@code RunListener} that can have its methods called
+ * concurrently. This implies that the class is thread-safe (i.e. no set of
+ * listener calls can put the listener into an invalid state, even if those
+ * listener calls are being made by multiple threads without
+ * synchronization).
+ *
+ * @since 4.12
+ */
+ @Documented
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ThreadSafe {
+ }
+}
diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java
index d0f6c85..6875f76 100644
--- a/src/main/java/org/junit/runner/notification/RunNotifier.java
+++ b/src/main/java/org/junit/runner/notification/RunNotifier.java
@@ -1,166 +1,214 @@
package org.junit.runner.notification;
+import static java.util.Arrays.asList;
+
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
-import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.Result;
/**
* If you write custom runners, you may need to notify JUnit of your progress running tests.
* Do this by invoking the <code>RunNotifier</code> passed to your implementation of
- * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
+ * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to
* move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)}
* to a separate class since they should only be called once per run.
+ *
+ * @since 4.0
*/
public class RunNotifier {
- private final List<RunListener> fListeners=
- Collections.synchronizedList(new ArrayList<RunListener>());
- private boolean fPleaseStop= false;
-
- /** Internal use only
- */
- public void addListener(RunListener listener) {
- fListeners.add(listener);
- }
-
- /** Internal use only
- */
- public void removeListener(RunListener listener) {
- fListeners.remove(listener);
+ private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
+ private volatile boolean pleaseStop = false;
+
+ /**
+ * Internal use only
+ */
+ public void addListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot add a null listener");
+ }
+ listeners.add(wrapIfNotThreadSafe(listener));
+ }
+
+ /**
+ * Internal use only
+ */
+ public void removeListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot remove a null listener");
+ }
+ listeners.remove(wrapIfNotThreadSafe(listener));
+ }
+
+ /**
+ * Wraps the given listener with {@link SynchronizedRunListener} if
+ * it is not annotated with {@link RunListener.ThreadSafe}.
+ */
+ RunListener wrapIfNotThreadSafe(RunListener listener) {
+ return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
+ listener : new SynchronizedRunListener(listener, this);
+ }
+
+
+ private abstract class SafeNotifier {
+ private final List<RunListener> currentListeners;
+
+ SafeNotifier() {
+ this(listeners);
+ }
+
+ SafeNotifier(List<RunListener> currentListeners) {
+ this.currentListeners = currentListeners;
+ }
+
+ void run() {
+ int capacity = currentListeners.size();
+ ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
+ ArrayList<Failure> failures = new ArrayList<Failure>(capacity);
+ for (RunListener listener : currentListeners) {
+ try {
+ notifyListener(listener);
+ safeListeners.add(listener);
+ } catch (Exception e) {
+ failures.add(new Failure(Description.TEST_MECHANISM, e));
+ }
+ }
+ fireTestFailures(safeListeners, failures);
+ }
+
+ abstract protected void notifyListener(RunListener each) throws Exception;
+ }
+
+ /**
+ * Do not invoke.
+ */
+ public void fireTestRunStarted(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testRunStarted(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Do not invoke.
+ */
+ public void fireTestRunFinished(final Result result) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testRunFinished(result);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test is about to start.
+ *
+ * @param description the description of the atomic test (generally a class and method name)
+ * @throws StoppedByUserException thrown if a user has requested that the test run stop
+ */
+ public void fireTestStarted(final Description description) throws StoppedByUserException {
+ if (pleaseStop) {
+ throw new StoppedByUserException();
+ }
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testStarted(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test failed.
+ *
+ * @param failure the description of the test that failed and the exception thrown
+ */
+ public void fireTestFailure(Failure failure) {
+ fireTestFailures(listeners, asList(failure));
+ }
+
+ private void fireTestFailures(List<RunListener> listeners,
+ final List<Failure> failures) {
+ if (!failures.isEmpty()) {
+ new SafeNotifier(listeners) {
+ @Override
+ protected void notifyListener(RunListener listener) throws Exception {
+ for (Failure each : failures) {
+ listener.testFailure(each);
+ }
+ }
+ }.run();
+ }
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test flagged that it assumed
+ * something false.
+ *
+ * @param failure the description of the test that failed and the
+ * {@link org.junit.AssumptionViolatedException} thrown
+ */
+ public void fireTestAssumptionFailed(final Failure failure) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testAssumptionFailure(failure);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test was ignored.
+ *
+ * @param description the description of the ignored test
+ */
+ public void fireTestIgnored(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testIgnored(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that an atomic test finished. Always invoke
+ * this method if you invoke {@link #fireTestStarted(Description)}
+ * as listeners are likely to expect them to come in pairs.
+ *
+ * @param description the description of the test that finished
+ */
+ public void fireTestFinished(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testFinished(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Ask that the tests run stop before starting the next test. Phrased politely because
+ * the test currently running will not be interrupted. It seems a little odd to put this
+ * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
+ * to be shared amongst the many runners involved.
+ */
+ public void pleaseStop() {
+ pleaseStop = true;
}
- private abstract class SafeNotifier {
- void run() {
- synchronized (fListeners) {
- for (Iterator<RunListener> all= fListeners.iterator(); all.hasNext();)
- try {
- notifyListener(all.next());
- } catch (Exception e) {
- all.remove(); // Remove the offending listener first to avoid an infinite loop
- fireTestFailure(new Failure(Description.TEST_MECHANISM, e));
- }
- }
- }
-
- abstract protected void notifyListener(RunListener each) throws Exception;
- }
-
- /**
- * Do not invoke.
- */
- public void fireTestRunStarted(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testRunStarted(description);
- };
- }.run();
- }
-
- /**
- * Do not invoke.
- */
- public void fireTestRunFinished(final Result result) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testRunFinished(result);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test is about to start.
- * @param description the description of the atomic test (generally a class and method name)
- * @throws StoppedByUserException thrown if a user has requested that the test run stop
- */
- public void fireTestStarted(final Description description) throws StoppedByUserException {
- if (fPleaseStop)
- throw new StoppedByUserException();
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testStarted(description);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test failed.
- * @param failure the description of the test that failed and the exception thrown
- */
- public void fireTestFailure(final Failure failure) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testFailure(failure);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test flagged that it assumed
- * something false.
- *
- * @param failure
- * the description of the test that failed and the
- * {@link AssumptionViolatedException} thrown
- */
- public void fireTestAssumptionFailed(final Failure failure) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testAssumptionFailure(failure);
- };
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test was ignored.
- * @param description the description of the ignored test
- */
- public void fireTestIgnored(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testIgnored(description);
- }
- }.run();
- }
-
- /**
- * Invoke to tell listeners that an atomic test finished. Always invoke
- * {@link #fireTestFinished(Description)} if you invoke {@link #fireTestStarted(Description)}
- * as listeners are likely to expect them to come in pairs.
- * @param description the description of the test that finished
- */
- public void fireTestFinished(final Description description) {
- new SafeNotifier() {
- @Override
- protected void notifyListener(RunListener each) throws Exception {
- each.testFinished(description);
- };
- }.run();
- }
-
- /**
- * Ask that the tests run stop before starting the next test. Phrased politely because
- * the test currently running will not be interrupted. It seems a little odd to put this
- * functionality here, but the <code>RunNotifier</code> is the only object guaranteed
- * to be shared amongst the many runners involved.
- */
- public void pleaseStop() {
- fPleaseStop= true;
- }
-
- /**
- * Internal use only. The Result's listener must be first.
- */
- public void addFirstListener(RunListener listener) {
- fListeners.add(0, listener);
- }
-} \ No newline at end of file
+ /**
+ * Internal use only. The Result's listener must be first.
+ */
+ public void addFirstListener(RunListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Cannot add a null listener");
+ }
+ listeners.add(0, wrapIfNotThreadSafe(listener));
+ }
+}
diff --git a/src/main/java/org/junit/runner/notification/StoppedByUserException.java b/src/main/java/org/junit/runner/notification/StoppedByUserException.java
index 89be3ba..f5490f7 100644
--- a/src/main/java/org/junit/runner/notification/StoppedByUserException.java
+++ b/src/main/java/org/junit/runner/notification/StoppedByUserException.java
@@ -1,11 +1,12 @@
package org.junit.runner.notification;
/**
- * Thrown when a user has requested that the test run stop. Writers of
+ * Thrown when a user has requested that the test run stop. Writers of
* test running GUIs should be prepared to catch a <code>StoppedByUserException</code>.
- *
+ *
* @see org.junit.runner.notification.RunNotifier
+ * @since 4.0
*/
public class StoppedByUserException extends RuntimeException {
- private static final long serialVersionUID= 1L;
+ private static final long serialVersionUID = 1L;
}
diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
new file mode 100644
index 0000000..c53c1ee
--- /dev/null
+++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
@@ -0,0 +1,103 @@
+package org.junit.runner.notification;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+
+/**
+ * Thread-safe decorator for {@link RunListener} implementations that synchronizes
+ * calls to the delegate.
+ *
+ * <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because
+ * prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier,
+ * so no two listeners were ever called concurrently. If we instead made the methods here
+ * sychronized, clients that added multiple listeners that called common code might see
+ * issues due to the reduced synchronization.
+ *
+ * @author Tibor Digana (tibor17)
+ * @author Kevin Cooney (kcooney)
+ * @since 4.12
+ *
+ * @see RunNotifier
+ */
+@RunListener.ThreadSafe
+final class SynchronizedRunListener extends RunListener {
+ private final RunListener listener;
+ private final Object monitor;
+
+ SynchronizedRunListener(RunListener listener, Object monitor) {
+ this.listener = listener;
+ this.monitor = monitor;
+ }
+
+ @Override
+ public void testRunStarted(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testRunStarted(description);
+ }
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ synchronized (monitor) {
+ listener.testRunFinished(result);
+ }
+ }
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testStarted(description);
+ }
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testFinished(description);
+ }
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ synchronized (monitor) {
+ listener.testFailure(failure);
+ }
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ synchronized (monitor) {
+ listener.testAssumptionFailure(failure);
+ }
+ }
+
+ @Override
+ public void testIgnored(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testIgnored(description);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof SynchronizedRunListener)) {
+ return false;
+ }
+ SynchronizedRunListener that = (SynchronizedRunListener) other;
+
+ return listener.equals(that.listener);
+ }
+
+ @Override
+ public String toString() {
+ return listener.toString() + " (with synchronization wrapper)";
+ }
+}
diff --git a/src/main/java/org/junit/runners/AllTests.java b/src/main/java/org/junit/runners/AllTests.java
index 50c02db..416c99d 100644
--- a/src/main/java/org/junit/runners/AllTests.java
+++ b/src/main/java/org/junit/runners/AllTests.java
@@ -2,7 +2,8 @@ package org.junit.runners;
import org.junit.internal.runners.SuiteMethod;
-/** Runner for use with JUnit 3.8.x-style AllTests classes
+/**
+ * Runner for use with JUnit 3.8.x-style AllTests classes
* (those that only implement a static <code>suite()</code>
* method). For example:
* <pre>
@@ -13,12 +14,14 @@ import org.junit.internal.runners.SuiteMethod;
* }
* }
* </pre>
+ *
+ * @since 4.0
*/
public class AllTests extends SuiteMethod {
- /**
- * Only called reflectively. Do not use programmatically.
- */
- public AllTests(Class<?> klass) throws Throwable {
- super(klass);
- }
+ /**
+ * 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
index 397da19..4d06199 100644
--- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
+++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
@@ -1,8 +1,11 @@
package org.junit.runners;
-import static org.junit.internal.runners.rules.RuleFieldValidator.RULE_VALIDATOR;
+import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR;
+import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
@@ -17,6 +20,7 @@ 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.MethodRule;
import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -31,377 +35,403 @@ import org.junit.runners.model.Statement;
* 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}).
- *
+ * <p>
* 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>
+ * <p>
+ * In turn, in 2009 we introduced {@link Rule}s. In many cases where extending
+ * BlockJUnit4ClassRunner was necessary to add new behavior, {@link Rule}s can
+ * be used, which makes the extension more reusable and composable.
+ *
+ * @since 4.5
*/
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"));
- }
-
- protected 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();
- }
+ private final ConcurrentHashMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>();
+ /**
+ * 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 (isIgnored(method)) {
+ notifier.fireTestIgnored(description);
+ } else {
+ runLeaf(methodBlock(method), description, notifier);
+ }
+ }
+
+ /**
+ * Evaluates whether {@link FrameworkMethod}s are ignored based on the
+ * {@link Ignore} annotation.
+ */
+ @Override
+ protected boolean isIgnored(FrameworkMethod child) {
+ return child.getAnnotation(Ignore.class) != null;
+ }
+
+ @Override
+ protected Description describeChild(FrameworkMethod method) {
+ Description description = methodDescriptions.get(method);
+
+ if (description == null) {
+ description = Description.createTestDescription(getTestClass().getJavaClass(),
+ testName(method), method.getAnnotations());
+ methodDescriptions.putIfAbsent(method, description);
+ }
+
+ return description;
+ }
+
+ @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);
+ validateMethods(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
+ 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"));
+ }
+ }
+
+ protected void validateFields(List<Throwable> errors) {
+ RULE_VALIDATOR.validate(getTestClass(), errors);
+ }
+
+ private void validateMethods(List<Throwable> errors) {
+ RULE_METHOD_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.
+ */
+ 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
+ protected Statement withPotentialTimeout(FrameworkMethod method,
+ Object test, Statement next) {
+ long timeout = getTimeout(method.getAnnotation(Test.class));
+ if (timeout <= 0) {
+ return next;
+ }
+ return FailOnTimeout.builder()
+ .withTimeout(timeout, TimeUnit.MILLISECONDS)
+ .build(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.
+ */
+ 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}.
+ */
+ 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) {
+ List<TestRule> testRules = getTestRules(target);
+ Statement result = statement;
+ result = withMethodRules(method, testRules, target, result);
+ result = withTestRules(method, testRules, result);
+
+ return result;
+ }
+
+ private Statement withMethodRules(FrameworkMethod method, List<TestRule> testRules,
+ Object target, Statement result) {
+ for (org.junit.rules.MethodRule each : getMethodRules(target)) {
+ if (!testRules.contains(each)) {
+ result = each.apply(result, method, target);
+ }
+ }
+ return result;
+ }
+
+ 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
+ */
+ protected List<MethodRule> rules(Object target) {
+ List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(target,
+ Rule.class, MethodRule.class);
+
+ rules.addAll(getTestClass().getAnnotatedFieldValues(target,
+ Rule.class, MethodRule.class));
+
+ return rules;
+ }
+
+ /**
+ * Returns a {@link Statement}: apply all non-static 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, List<TestRule> testRules,
+ Statement statement) {
+ 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) {
+ List<TestRule> result = getTestClass().getAnnotatedMethodValues(target,
+ Rule.class, TestRule.class);
+
+ result.addAll(getTestClass().getAnnotatedFieldValues(target,
+ Rule.class, TestRule.class));
+
+ return result;
+ }
+
+ 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
index 1e1f347..6ba28c2 100644
--- a/src/main/java/org/junit/runners/JUnit4.java
+++ b/src/main/java/org/junit/runners/JUnit4.java
@@ -11,12 +11,14 @@ import org.junit.runners.model.InitializationError;
* 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.
+ *
+ * @since 4.5
*/
public final class JUnit4 extends BlockJUnit4ClassRunner {
- /**
- * Constructs a new instance of the default runner
- */
- public JUnit4(Class<?> klass) throws InitializationError {
- super(klass);
- }
+ /**
+ * 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/MethodSorters.java b/src/main/java/org/junit/runners/MethodSorters.java
new file mode 100644
index 0000000..5821892
--- /dev/null
+++ b/src/main/java/org/junit/runners/MethodSorters.java
@@ -0,0 +1,41 @@
+package org.junit.runners;
+
+import java.lang.reflect.Method;
+import java.util.Comparator;
+
+import org.junit.internal.MethodSorter;
+
+/**
+ * Sort the methods into a specified execution order.
+ * Defines common {@link MethodSorter} implementations.
+ *
+ * @since 4.11
+ */
+public enum MethodSorters {
+ /**
+ * Sorts the test methods by the method name, in lexicographic order,
+ * with {@link Method#toString()} used as a tiebreaker
+ */
+ NAME_ASCENDING(MethodSorter.NAME_ASCENDING),
+
+ /**
+ * Leaves the test methods in the order returned by the JVM.
+ * Note that the order from the JVM may vary from run to run
+ */
+ JVM(null),
+
+ /**
+ * Sorts the test methods in a deterministic, but not predictable, order
+ */
+ DEFAULT(MethodSorter.DEFAULT);
+
+ private final Comparator<Method> comparator;
+
+ private MethodSorters(Comparator<Method> comparator) {
+ this.comparator = comparator;
+ }
+
+ public Comparator<Method> getComparator() {
+ return comparator;
+ }
+}
diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java
index 3ebfead..829c8f0 100644
--- a/src/main/java/org/junit/runners/Parameterized.java
+++ b/src/main/java/org/junit/runners/Parameterized.java
@@ -1,167 +1,351 @@
package org.junit.runners;
-import java.lang.annotation.Annotation;
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.lang.reflect.Modifier;
+import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Arrays;
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;
+import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
+import org.junit.runners.parameterized.ParametersRunnerFactory;
+import org.junit.runners.parameterized.TestWithParameters;
/**
- * <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>
- *
+ * <p>
* For example, to test a Fibonacci function, write:
- *
* <pre>
* &#064;RunWith(Parameterized.class)
* public class FibonacciTest {
- * &#064;Parameters
- * public static List&lt;Object[]&gt; 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;
- * }
- *
- * &#064;Test
- * public void test() {
- * assertEquals(fExpected, Fibonacci.compute(fInput));
- * }
+ * &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
+ * public static Iterable&lt;Object[]&gt; 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;
+ * }
+ *
+ * &#064;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>&#064;Parameters</code> method.
- * </p>
+ * <p>
+ * In order that you can easily identify the individual tests, you may provide a
+ * name for the <code>&#064;Parameters</code> annotation. This name is allowed
+ * to contain placeholders, which are replaced at runtime. The placeholders are
+ * <dl>
+ * <dt>{index}</dt>
+ * <dd>the current parameter index</dd>
+ * <dt>{0}</dt>
+ * <dd>the first parameter value</dd>
+ * <dt>{1}</dt>
+ * <dd>the second parameter value</dd>
+ * <dt>...</dt>
+ * <dd>...</dd>
+ * </dl>
+ * <p>
+ * In the example given above, the <code>Parameterized</code> runner creates
+ * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter,
+ * then the current parameter index is used as name.
+ * <p>
+ * You can also write:
+ * <pre>
+ * &#064;RunWith(Parameterized.class)
+ * public class FibonacciTest {
+ * &#064;Parameters
+ * public static Iterable&lt;Object[]&gt; data() {
+ * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
+ * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
+ * }
+ *
+ * &#064;Parameter(0)
+ * public int fInput;
+ *
+ * &#064;Parameter(1)
+ * public int fExpected;
+ *
+ * &#064;Test
+ * public void test() {
+ * assertEquals(fExpected, Fibonacci.compute(fInput));
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
+ * and fields annotated by <code>&#064;Parameter</code> will be initialized
+ * with the data values in the <code>&#064;Parameters</code> method.
+ *
+ * <p>
+ * The parameters can be provided as an array, too:
+ *
+ * <pre>
+ * &#064;Parameters
+ * public static Object[][] data() {
+ * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
+ * { 5, 5 }, { 6, 8 } };
+ * }
+ * </pre>
+ *
+ * <h3>Tests with single parameter</h3>
+ * <p>
+ * If your test needs a single parameter only, you don't have to wrap it with an
+ * array. Instead you can provide an <code>Iterable</code> or an array of
+ * objects.
+ * <pre>
+ * &#064;Parameters
+ * public static Iterable&lt;? extends Object&gt; data() {
+ * return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
+ * }
+ * </pre>
+ * <p>
+ * or
+ * <pre>
+ * &#064;Parameters
+ * public static Object[] data() {
+ * return new Object[] { &quot;first test&quot;, &quot;second test&quot; };
+ * }
+ * </pre>
+ *
+ * <h3>Create different runners</h3>
+ * <p>
+ * By default the {@code Parameterized} runner creates a slightly modified
+ * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an
+ * own {@code Parameterized} runner that creates another runner for each set of
+ * parameters. Therefore you have to build a {@link ParametersRunnerFactory}
+ * that creates a runner for each {@link TestWithParameters}. (
+ * {@code TestWithParameters} are bundling the parameters and the test name.)
+ * The factory must have a public zero-arg constructor.
+ *
+ * <pre>
+ * public class YourRunnerFactory implements ParameterizedRunnerFactory {
+ * public Runner createRunnerForTestWithParameters(TestWithParameters test)
+ * throws InitializationError {
+ * return YourRunner(test);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized}
+ * runner that it should use your factory.
+ *
+ * <pre>
+ * &#064;RunWith(Parameterized.class)
+ * &#064;UseParametersRunnerFactory(YourRunnerFactory.class)
+ * public class YourTest {
+ * ...
+ * }
+ * </pre>
+ *
+ * @since 4.0
*/
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());
- }
+ /**
+ * Annotation for a method which provides parameters to be injected into the
+ * test class constructor by <code>Parameterized</code>. The method has to
+ * be public and static.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public static @interface Parameters {
+ /**
+ * Optional pattern to derive the test's name from the parameters. Use
+ * numbers in braces to refer to the parameters or the additional data
+ * as follows:
+ * <pre>
+ * {index} - the current parameter index
+ * {0} - the first parameter value
+ * {1} - the second parameter value
+ * etc...
+ * </pre>
+ * <p>
+ * Default value is "{index}" for compatibility with previous JUnit
+ * versions.
+ *
+ * @return {@link MessageFormat} pattern string, except the index
+ * placeholder.
+ * @see MessageFormat
+ */
+ String name() default "{index}";
+ }
+
+ /**
+ * Annotation for fields of the test class which will be initialized by the
+ * method annotated by <code>Parameters</code>.
+ * By using directly this annotation, the test class constructor isn't needed.
+ * Index range must start at 0.
+ * Default value is 0.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public static @interface Parameter {
+ /**
+ * Method that returns the index of the parameter in the array
+ * returned by the method annotated by <code>Parameters</code>.
+ * Index range must start at 0.
+ * Default value is 0.
+ *
+ * @return the index of the parameter.
+ */
+ int value() default 0;
+ }
+
+ /**
+ * Add this annotation to your test class if you want to generate a special
+ * runner. You have to specify a {@link ParametersRunnerFactory} class that
+ * creates such runners. The factory must have a public zero-arg
+ * constructor.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @Target(ElementType.TYPE)
+ public @interface UseParametersRunnerFactory {
+ /**
+ * @return a {@link ParametersRunnerFactory} class (must have a default
+ * constructor)
+ */
+ Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class;
+ }
+
+ private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
+
+ private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList();
+
+ private final List<Runner> runners;
+
+ /**
+ * Only called reflectively. Do not use programmatically.
+ */
+ public Parameterized(Class<?> klass) throws Throwable {
+ super(klass, NO_RUNNERS);
+ ParametersRunnerFactory runnerFactory = getParametersRunnerFactory(
+ klass);
+ Parameters parameters = getParametersMethod().getAnnotation(
+ Parameters.class);
+ runners = Collections.unmodifiableList(createRunnersForParameters(
+ allParameters(), parameters.name(), runnerFactory));
+ }
+
+ private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass)
+ throws InstantiationException, IllegalAccessException {
+ UseParametersRunnerFactory annotation = klass
+ .getAnnotation(UseParametersRunnerFactory.class);
+ if (annotation == null) {
+ return DEFAULT_FACTORY;
+ } else {
+ Class<? extends ParametersRunnerFactory> factoryClass = annotation
+ .value();
+ return factoryClass.newInstance();
+ }
+ }
+
+ @Override
+ protected List<Runner> getChildren() {
+ return runners;
+ }
+
+ private TestWithParameters createTestWithNotNormalizedParameters(
+ String pattern, int index, Object parametersOrSingleParameter) {
+ Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
+ : new Object[] { parametersOrSingleParameter };
+ return createTestWithParameters(getTestClass(), pattern, index,
+ parameters);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Iterable<Object> allParameters() throws Throwable {
+ Object parameters = getParametersMethod().invokeExplosively(null);
+ if (parameters instanceof Iterable) {
+ return (Iterable<Object>) parameters;
+ } else if (parameters instanceof Object[]) {
+ return Arrays.asList((Object[]) parameters);
+ } else {
+ throw parametersMethodReturnedWrongType();
+ }
+ }
+
+ private FrameworkMethod getParametersMethod() throws Exception {
+ List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(
+ Parameters.class);
+ for (FrameworkMethod each : methods) {
+ if (each.isStatic() && each.isPublic()) {
+ return each;
+ }
+ }
+
+ throw new Exception("No public static parameters method on class "
+ + getTestClass().getName());
+ }
+
+ private List<Runner> createRunnersForParameters(
+ Iterable<Object> allParameters, String namePattern,
+ ParametersRunnerFactory runnerFactory)
+ throws InitializationError,
+ Exception {
+ try {
+ List<TestWithParameters> tests = createTestsForParameters(
+ allParameters, namePattern);
+ List<Runner> runners = new ArrayList<Runner>();
+ for (TestWithParameters test : tests) {
+ runners.add(runnerFactory
+ .createRunnerForTestWithParameters(test));
+ }
+ return runners;
+ } catch (ClassCastException e) {
+ throw parametersMethodReturnedWrongType();
+ }
+ }
+
+ private List<TestWithParameters> createTestsForParameters(
+ Iterable<Object> allParameters, String namePattern)
+ throws Exception {
+ int i = 0;
+ List<TestWithParameters> children = new ArrayList<TestWithParameters>();
+ for (Object parametersOfSingleTest : allParameters) {
+ children.add(createTestWithNotNormalizedParameters(namePattern,
+ i++, parametersOfSingleTest));
+ }
+ return children;
+ }
+
+ private Exception parametersMethodReturnedWrongType() throws Exception {
+ String className = getTestClass().getName();
+ String methodName = getParametersMethod().getName();
+ String message = MessageFormat.format(
+ "{0}.{1}() must return an Iterable of arrays.",
+ className, methodName);
+ return new Exception(message);
+ }
+ private static TestWithParameters createTestWithParameters(
+ TestClass testClass, String pattern, int index, Object[] parameters) {
+ String finalPattern = pattern.replaceAll("\\{index\\}",
+ Integer.toString(index));
+ String name = MessageFormat.format(finalPattern, parameters);
+ return new TestWithParameters("[" + name + "]", testClass,
+ Arrays.asList(parameters));
+ }
}
diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java
index a41aad3..92641bf 100644..100755
--- a/src/main/java/org/junit/runners/ParentRunner.java
+++ b/src/main/java/org/junit/runners/ParentRunner.java
@@ -1,10 +1,13 @@
package org.junit.runners;
-import static org.junit.internal.runners.rules.RuleFieldValidator.CLASS_RULE_VALIDATOR;
+import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_METHOD_VALIDATOR;
+import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_VALIDATOR;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -13,6 +16,7 @@ import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
@@ -31,10 +35,12 @@ 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;
+import org.junit.validator.AnnotationsValidator;
+import org.junit.validator.PublicClassValidator;
+import org.junit.validator.TestClassValidator;
/**
* Provides most of the functionality specific to a Runner that implements a
@@ -43,336 +49,404 @@ import org.junit.runners.model.TestClass;
* {@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,
+ * {@code @BeforeClass} and {@code @AfterClass} methods,
* handle annotated {@link ClassRule}s, create a composite
* {@link Description}, and run children sequentially.
+ *
+ * @since 4.5
*/
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;
- }
+ Sortable {
+ private static final List<TestClassValidator> VALIDATORS = Arrays.asList(
+ new AnnotationsValidator(), new PublicClassValidator());
+
+ private final Object childrenLock = new Object();
+ private final TestClass testClass;
+
+ // Guarded by childrenLock
+ private volatile Collection<T> filteredChildren = null;
+
+ private volatile RunnerScheduler scheduler = new RunnerScheduler() {
+ public void schedule(Runnable childStatement) {
+ childStatement.run();
+ }
+
+ public void finished() {
+ // do nothing
+ }
+ };
+
+ /**
+ * Constructs a new {@code ParentRunner} that will run {@code @TestClass}
+ */
+ protected ParentRunner(Class<?> testClass) throws InitializationError {
+ this.testClass = createTestClass(testClass);
+ validate();
+ }
+
+ protected TestClass createTestClass(Class<?> testClass) {
+ return new TestClass(testClass);
+ }
+
+ //
+ // 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);
+ applyValidators(errors);
+ }
+
+ private void applyValidators(List<Throwable> errors) {
+ if (getTestClass().getJavaClass() != null) {
+ for (TestClassValidator each : VALIDATORS) {
+ errors.addAll(each.validateTestClass(getTestClass()));
+ }
+ }
+ }
+
+ /**
+ * 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}).
+ * </ul>
+ */
+ 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);
+ CLASS_RULE_METHOD_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:
+ * <ol>
+ * <li>Determine the children to be run using {@link #getChildren()}
+ * (subject to any imposed filter and sort).</li>
+ * <li>If there are any children remaining after filtering and ignoring,
+ * construct a statement that will:
+ * <ol>
+ * <li>Apply all {@code ClassRule}s on the test-class and superclasses.</li>
+ * <li>Run all non-overridden {@code @BeforeClass} methods on the test-class
+ * and superclasses; if any throws an Exception, stop execution and pass the
+ * exception on.</li>
+ * <li>Run all remaining tests on the test-class.</li>
+ * <li>Run all non-overridden {@code @AfterClass} methods on the test-class
+ * and superclasses: exceptions thrown by previous steps are combined, if
+ * necessary, with exceptions from AfterClass methods into a
+ * {@link org.junit.runners.model.MultipleFailureException}.</li>
+ * </ol>
+ * </li>
+ * </ol>
+ *
+ * @return {@code Statement}
+ */
+ protected Statement classBlock(final RunNotifier notifier) {
+ Statement statement = childrenInvoker(notifier);
+ if (!areAllChildrenIgnored()) {
+ statement = withBeforeClasses(statement);
+ statement = withAfterClasses(statement);
+ statement = withClassRules(statement);
+ }
+ return statement;
+ }
+
+ private boolean areAllChildrenIgnored() {
+ for (T child : getFilteredChildren()) {
+ if (!isIgnored(child)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 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 = testClass
+ .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 org.junit.runners.model.MultipleFailureException}.
+ */
+ protected Statement withAfterClasses(Statement statement) {
+ List<FrameworkMethod> afters = testClass
+ .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() {
+ List<TestRule> result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class);
+ result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class));
+ return result;
+ }
+
+ /**
+ * 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);
+ }
+ };
+ }
+
+ /**
+ * Evaluates whether a child is ignored. The default implementation always
+ * returns <code>false</code>.
+ *
+ * <p>{@link BlockJUnit4ClassRunner}, for example, overrides this method to
+ * filter tests based on the {@link Ignore} annotation.
+ */
+ protected boolean isIgnored(T child) {
+ return false;
+ }
+
+ private void runChildren(final RunNotifier notifier) {
+ final RunnerScheduler currentScheduler = scheduler;
+ try {
+ for (final T each : getFilteredChildren()) {
+ currentScheduler.schedule(new Runnable() {
+ public void run() {
+ ParentRunner.this.runChild(each, notifier);
+ }
+ });
+ }
+ } finally {
+ currentScheduler.finished();
+ }
+ }
+
+ /**
+ * Returns a name used to describe this Runner
+ */
+ protected String getName() {
+ return testClass.getName();
+ }
+
+ //
+ // Available for subclasses
+ //
+
+ /**
+ * Returns a {@link TestClass} object wrapping the class to be executed.
+ */
+ public final TestClass getTestClass() {
+ return testClass;
+ }
+
+ /**
+ * 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 testClass.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.addFailedAssumption(e);
+ } catch (StoppedByUserException e) {
+ throw e;
+ } catch (Throwable e) {
+ testNotifier.addFailure(e);
+ }
+ }
+
+ //
+ // Implementation of Filterable and Sortable
+ //
+
+ public void filter(Filter filter) throws NoTestsRemainException {
+ synchronized (childrenLock) {
+ List<T> children = new ArrayList<T>(getFilteredChildren());
+ for (Iterator<T> iter = children.iterator(); iter.hasNext(); ) {
+ T each = iter.next();
+ if (shouldRun(filter, each)) {
+ try {
+ filter.apply(each);
+ } catch (NoTestsRemainException e) {
+ iter.remove();
+ }
+ } else {
+ iter.remove();
+ }
+ }
+ filteredChildren = Collections.unmodifiableCollection(children);
+ if (filteredChildren.isEmpty()) {
+ throw new NoTestsRemainException();
+ }
+ }
+ }
+
+ public void sort(Sorter sorter) {
+ synchronized (childrenLock) {
+ for (T each : getFilteredChildren()) {
+ sorter.apply(each);
+ }
+ List<T> sortedChildren = new ArrayList<T>(getFilteredChildren());
+ Collections.sort(sortedChildren, comparator(sorter));
+ filteredChildren = Collections.unmodifiableCollection(sortedChildren);
+ }
+ }
+
+ //
+ // Private implementation
+ //
+
+ private void validate() throws InitializationError {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ collectInitializationErrors(errors);
+ if (!errors.isEmpty()) {
+ throw new InitializationError(errors);
+ }
+ }
+
+ private Collection<T> getFilteredChildren() {
+ if (filteredChildren == null) {
+ synchronized (childrenLock) {
+ if (filteredChildren == null) {
+ filteredChildren = Collections.unmodifiableCollection(getChildren());
+ }
+ }
+ }
+ return filteredChildren;
+ }
+
+ private boolean shouldRun(Filter filter, T each) {
+ return filter.shouldRun(describeChild(each));
+ }
+
+ private Comparator<? super T> comparator(final Sorter sorter) {
+ return new Comparator<T>() {
+ public int compare(T o1, T o2) {
+ return sorter.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.scheduler = scheduler;
+ }
}
diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java
index 1b3bb48..b37179f 100644
--- a/src/main/java/org/junit/runners/Suite.java
+++ b/src/main/java/org/junit/runners/Suite.java
@@ -5,6 +5,7 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Collections;
import java.util.List;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
@@ -20,111 +21,110 @@ import org.junit.runners.model.RunnerBuilder;
* 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.
+ *
+ * @since 4.0
*/
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();
- }
+ /**
+ * 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");
+ }
+ }
- private final List<Runner> fRunners;
+ /**
+ * 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();
+ }
- /**
- * 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));
- }
+ 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();
+ }
- /**
- * 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();
- }
+ private final List<Runner> runners;
- @Override
- protected void runChild(Runner runner, final RunNotifier notifier) {
- runner.run(notifier);
- }
+ /**
+ * 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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}
+ */
+ protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
+ super(klass);
+ this.runners = Collections.unmodifiableList(runners);
+ }
+
+ @Override
+ protected List<Runner> getChildren() {
+ return runners;
+ }
+
+ @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/Annotatable.java b/src/main/java/org/junit/runners/model/Annotatable.java
new file mode 100644
index 0000000..8eff6fd
--- /dev/null
+++ b/src/main/java/org/junit/runners/model/Annotatable.java
@@ -0,0 +1,20 @@
+package org.junit.runners.model;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A model element that may have annotations.
+ *
+ * @since 4.12
+ */
+public interface Annotatable {
+ /**
+ * Returns the model elements' annotations.
+ */
+ Annotation[] getAnnotations();
+
+ /**
+ * Returns the annotation on the model element of the given type, or @code{null}
+ */
+ <T extends Annotation> T getAnnotation(Class<T> annotationType);
+}
diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java
index 4a4d4a4..945e389 100644
--- a/src/main/java/org/junit/runners/model/FrameworkField.java
+++ b/src/main/java/org/junit/runners/model/FrameworkField.java
@@ -2,64 +2,79 @@ 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)
+ *
+ * @since 4.7
*/
public class FrameworkField extends FrameworkMember<FrameworkField> {
- private final Field fField;
+ private final Field field;
- FrameworkField(Field field) {
- fField= field;
- }
+ FrameworkField(Field field) {
+ if (field == null) {
+ throw new NullPointerException(
+ "FrameworkField cannot be created without an underlying field.");
+ }
+ this.field = field;
+ }
- public String getName() {
- return getField().getName();
- }
+ @Override
+ public String getName() {
+ return getField().getName();
+ }
- @Override
- public Annotation[] getAnnotations() {
- return fField.getAnnotations();
- }
+ public Annotation[] getAnnotations() {
+ return field.getAnnotations();
+ }
- public boolean isPublic() {
- int modifiers= fField.getModifiers();
- return Modifier.isPublic(modifiers);
- }
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return field.getAnnotation(annotationType);
+ }
- @Override
- public boolean isShadowedBy(FrameworkField otherMember) {
- return otherMember.getName().equals(getName());
- }
+ @Override
+ public boolean isShadowedBy(FrameworkField otherMember) {
+ return otherMember.getName().equals(getName());
+ }
- public boolean isStatic() {
- int modifiers= fField.getModifiers();
- return Modifier.isStatic(modifiers);
- }
+ @Override
+ protected int getModifiers() {
+ return field.getModifiers();
+ }
- /**
- * @return the underlying java Field
- */
- public Field getField() {
- return fField;
- }
+ /**
+ * @return the underlying java Field
+ */
+ public Field getField() {
+ return field;
+ }
- /**
- * @return the underlying Java Field type
- * @see java.lang.reflect.Field#getType()
- */
- public Class<?> getType() {
- return fField.getType();
- }
+ /**
+ * @return the underlying Java Field type
+ * @see java.lang.reflect.Field#getType()
+ */
+ @Override
+ public Class<?> getType() {
+ return field.getType();
+ }
+
+ @Override
+ public Class<?> getDeclaringClass() {
+ return field.getDeclaringClass();
+ }
- /**
- * Attempts to retrieve the value of this field on {@code target}
- */
- public Object get(Object target) throws IllegalArgumentException, IllegalAccessException {
- return fField.get(target);
- }
+ /**
+ * Attempts to retrieve the value of this field on {@code target}
+ */
+ public Object get(Object target) throws IllegalArgumentException, IllegalAccessException {
+ return field.get(target);
+ }
+
+ @Override
+ public String toString() {
+ return field.toString();
+ }
}
diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java
index 9cccd4b..724f096 100644
--- a/src/main/java/org/junit/runners/model/FrameworkMember.java
+++ b/src/main/java/org/junit/runners/model/FrameworkMember.java
@@ -1,20 +1,45 @@
package org.junit.runners.model;
-import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
import java.util.List;
-abstract class FrameworkMember<T extends FrameworkMember<T>> {
- /**
- * Returns the annotations on this method
- */
- abstract Annotation[] getAnnotations();
+/**
+ * Parent class for {@link FrameworkField} and {@link FrameworkMethod}
+ *
+ * @since 4.7
+ */
+public abstract class FrameworkMember<T extends FrameworkMember<T>> implements
+ Annotatable {
+ abstract boolean isShadowedBy(T otherMember);
- abstract boolean isShadowedBy(T otherMember);
+ boolean isShadowedBy(List<T> members) {
+ for (T each : members) {
+ if (isShadowedBy(each)) {
+ return true;
+ }
+ }
+ return false;
+ }
- boolean isShadowedBy(List<T> members) {
- for (T each : members)
- if (isShadowedBy(each))
- return true;
- return false;
- }
+ protected abstract int getModifiers();
+
+ /**
+ * Returns true if this member is static, false if not.
+ */
+ public boolean isStatic() {
+ return Modifier.isStatic(getModifiers());
+ }
+
+ /**
+ * Returns true if this member is public, false if not.
+ */
+ public boolean isPublic() {
+ return Modifier.isPublic(getModifiers());
+ }
+
+ public abstract String getName();
+
+ public abstract Class<?> getType();
+
+ public abstract Class<?> getDeclaringClass();
}
diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java
index 81c8963..3580052 100644
--- a/src/main/java/org/junit/runners/model/FrameworkMethod.java
+++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java
@@ -1,9 +1,8 @@
-package org.junit.runners.model;
+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;
@@ -12,145 +11,192 @@ 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 @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass},
* {@code @AfterClass}, etc.)
+ *
+ * @since 4.5
*/
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);
- }
+ private final Method method;
+
+ /**
+ * Returns a new {@code FrameworkMethod} for {@code method}
+ */
+ public FrameworkMethod(Method method) {
+ if (method == null) {
+ throw new NullPointerException(
+ "FrameworkMethod cannot be created without an underlying method.");
+ }
+ this.method = method;
+ }
+
+ /**
+ * Returns the underlying Java method
+ */
+ public Method getMethod() {
+ return method;
+ }
+
+ /**
+ * 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 method.invoke(target, params);
+ }
+ }.run();
+ }
+
+ /**
+ * Returns the method's name
+ */
+ @Override
+ public String getName() {
+ return method.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}).
+ * </ul>
+ */
+ public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) {
+ validatePublicVoid(isStatic, errors);
+ if (method.getParameterTypes().length != 0) {
+ errors.add(new Exception("Method " + method.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}).
+ * </ul>
+ */
+ public void validatePublicVoid(boolean isStatic, List<Throwable> errors) {
+ if (isStatic() != isStatic) {
+ String state = isStatic ? "should" : "should not";
+ errors.add(new Exception("Method " + method.getName() + "() " + state + " be static"));
+ }
+ if (!isPublic()) {
+ errors.add(new Exception("Method " + method.getName() + "() should be public"));
+ }
+ if (method.getReturnType() != Void.TYPE) {
+ errors.add(new Exception("Method " + method.getName() + "() should be void"));
+ }
+ }
+
+ @Override
+ protected int getModifiers() {
+ return method.getModifiers();
+ }
+
+ /**
+ * Returns the return type of the method
+ */
+ public Class<?> getReturnType() {
+ return method.getReturnType();
+ }
+
+ /**
+ * Returns the return type of the method
+ */
+ @Override
+ public Class<?> getType() {
+ return getReturnType();
+ }
+
+ /**
+ * Returns the class where the method is actually declared
+ */
+ @Override
+ public Class<?> getDeclaringClass() {
+ return method.getDeclaringClass();
+ }
+
+ public void validateNoTypeParametersOnArgs(List<Throwable> errors) {
+ new NoGenericTypeParametersValidator(method).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).method.equals(method);
+ }
+
+ @Override
+ public int hashCode() {
+ return method.hashCode();
+ }
+
+ /**
+ * Returns true if 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(method.getReturnType());
+ }
+
+ private Class<?>[] getParameterTypes() {
+ return method.getParameterTypes();
+ }
+
+ /**
+ * Returns the annotations on this method
+ */
+ public Annotation[] getAnnotations() {
+ return method.getAnnotations();
+ }
+
+ /**
+ * Returns the annotation of type {@code annotationType} on this method, if
+ * one exists.
+ */
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ return method.getAnnotation(annotationType);
+ }
+
+ @Override
+ public String toString() {
+ return method.toString();
+ }
}
diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java
index 4de9ea7..841b565 100644
--- a/src/main/java/org/junit/runners/model/InitializationError.java
+++ b/src/main/java/org/junit/runners/model/InitializationError.java
@@ -5,35 +5,43 @@ import java.util.List;
/**
* Represents one or more problems encountered while initializing a Runner
+ *
+ * @since 4.5
*/
public class InitializationError extends Exception {
- private static final long serialVersionUID= 1L;
- private final List<Throwable> fErrors;
+ private static final long serialVersionUID = 1L;
- /**
- * 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));
- }
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final List<Throwable> fErrors;
- /**
- * Returns one or more Throwables that led to this initialization error.
- */
- public List<Throwable> getCauses() {
- return fErrors;
- }
+ /**
+ * Construct a new {@code InitializationError} with one or more
+ * errors {@code errors} as causes
+ */
+ public InitializationError(List<Throwable> errors) {
+ this.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
index 6d70ca0..325c645 100644
--- a/src/main/java/org/junit/runners/model/MultipleFailureException.java
+++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java
@@ -1,60 +1,69 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
package org.junit.runners.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.junit.internal.Throwables;
+
/**
* Collects multiple {@code Throwable}s into one exception.
+ *
+ * @since 4.9
*/
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);
- }
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * We have to use the f prefix until the next major release to ensure
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit/issues/976
+ */
+ private final List<Throwable> fErrors;
+
+ public MultipleFailureException(List<Throwable> errors) {
+ this.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 Exception or Error if the list is not empty
+ */
+ @SuppressWarnings("deprecation")
+ public static void assertEmpty(List<Throwable> errors) throws Exception {
+ if (errors.isEmpty()) {
+ return;
+ }
+ if (errors.size() == 1) {
+ throw Throwables.rethrowAsException(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
index 77662b8..386b7ff 100644
--- a/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java
+++ b/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java
@@ -9,45 +9,50 @@ 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);
- }
+ private final Method method;
+
+ NoGenericTypeParametersValidator(Method method) {
+ this.method = method;
+ }
+
+ void validate(List<Throwable> errors) {
+ for (Type each : method.getGenericParameterTypes()) {
+ validateNoTypeParameterOnType(each, errors);
+ }
+ }
+
+ private void validateNoTypeParameterOnType(Type type, List<Throwable> errors) {
+ if (type instanceof TypeVariable<?>) {
+ errors.add(new Exception("Method " + method.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
index 3a334be..7d3eee3 100644
--- a/src/main/java/org/junit/runners/model/RunnerBuilder.java
+++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java
@@ -9,21 +9,21 @@ import org.junit.internal.runners.ErrorReportingRunner;
import org.junit.runner.Runner;
/**
- * A RunnerBuilder is a strategy for constructing runners for classes.
- *
+ * 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.
+ * 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) {
* // ...
@@ -32,73 +32,77 @@ import org.junit.runner.Runner;
* // ...
* }
* </pre>
- *
+ *
* @see org.junit.runners.Suite
+ * @since 4.5
*/
public abstract class RunnerBuilder {
- private final Set<Class<?>> parents= new HashSet<Class<?>>();
+ 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;
+ /**
+ * 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);
- }
- }
+ /**
+ * 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;
- }
+ 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);
- }
+ 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);
+ /**
+ * 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;
- }
+ 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
index fbc25a4..db43308 100644
--- a/src/main/java/org/junit/runners/model/RunnerScheduler.java
+++ b/src/main/java/org/junit/runners/model/RunnerScheduler.java
@@ -3,19 +3,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.
+ *
+ * @since 4.7
*/
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();
+ /**
+ * 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
index a7c5478..fa53fa1 100644
--- a/src/main/java/org/junit/runners/model/Statement.java
+++ b/src/main/java/org/junit/runners/model/Statement.java
@@ -1,16 +1,15 @@
-/**
- *
- */
package org.junit.runners.model;
/**
* Represents one or more actions to be taken at runtime in the course
* of running a JUnit test suite.
+ *
+ * @since 4.5
*/
public abstract class Statement {
- /**
- * Run the action, throwing a {@code Throwable} if anything goes wrong.
- */
- public abstract void evaluate() throws Throwable;
+ /**
+ * 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
index 362a13a..c8a544d 100644..100755
--- a/src/main/java/org/junit/runners/model/TestClass.java
+++ b/src/main/java/org/junit/runners/model/TestClass.java
@@ -1,159 +1,313 @@
package org.junit.runners.model;
import static java.lang.reflect.Modifier.isStatic;
+import static org.junit.internal.MethodSorter.NAME_ASCENDING;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.internal.MethodSorter;
/**
* Wraps a class to be run, providing method validation and annotation searching
+ *
+ * @since 4.5
*/
-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());
- }
+public class TestClass implements Annotatable {
+ private static final FieldComparator FIELD_COMPARATOR = new FieldComparator();
+ private static final MethodComparator METHOD_COMPARATOR = new MethodComparator();
+
+ private final Class<?> clazz;
+ private final Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations;
+ private final Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations;
+
+ /**
+ * Creates a {@code TestClass} wrapping {@code clazz}. 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<?> clazz) {
+ this.clazz = clazz;
+ if (clazz != null && clazz.getConstructors().length > 1) {
+ throw new IllegalArgumentException(
+ "Test class can only have one constructor");
+ }
+
+ Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
+ new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
+ Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =
+ new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();
+
+ scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
+
+ this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
+ this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
+ }
+
+ protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) {
+ for (Class<?> eachClass : getSuperClasses(clazz)) {
+ for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
+ addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);
+ }
+ // ensuring fields are sorted to make sure that entries are inserted
+ // and read from fieldForAnnotations in a deterministic order
+ for (Field eachField : getSortedDeclaredFields(eachClass)) {
+ addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations);
+ }
+ }
+ }
+
+ private static Field[] getSortedDeclaredFields(Class<?> clazz) {
+ Field[] declaredFields = clazz.getDeclaredFields();
+ Arrays.sort(declaredFields, FIELD_COMPARATOR);
+ return declaredFields;
+ }
+
+ protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member,
+ Map<Class<? extends Annotation>, List<T>> map) {
+ for (Annotation each : member.getAnnotations()) {
+ Class<? extends Annotation> type = each.annotationType();
+ List<T> members = getAnnotatedMembers(map, type, true);
+ if (member.isShadowedBy(members)) {
+ return;
+ }
+ if (runsTopToBottom(type)) {
+ members.add(0, member);
+ } else {
+ members.add(member);
+ }
+ }
+ }
+
+ private static <T extends FrameworkMember<T>> Map<Class<? extends Annotation>, List<T>>
+ makeDeeplyUnmodifiable(Map<Class<? extends Annotation>, List<T>> source) {
+ LinkedHashMap<Class<? extends Annotation>, List<T>> copy =
+ new LinkedHashMap<Class<? extends Annotation>, List<T>>();
+ for (Map.Entry<Class<? extends Annotation>, List<T>> entry : source.entrySet()) {
+ copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
+ }
+ return Collections.unmodifiableMap(copy);
+ }
+
+ /**
+ * Returns, efficiently, all the non-overridden methods in this class and
+ * its superclasses that are annotated}.
+ *
+ * @since 4.12
+ */
+ public List<FrameworkMethod> getAnnotatedMethods() {
+ List<FrameworkMethod> methods = collectValues(methodsForAnnotations);
+ Collections.sort(methods, METHOD_COMPARATOR);
+ return methods;
+ }
+
+ /**
+ * 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 Collections.unmodifiableList(getAnnotatedMembers(methodsForAnnotations, annotationClass, false));
+ }
+
+ /**
+ * Returns, efficiently, all the non-overridden fields in this class and its
+ * superclasses that are annotated.
+ *
+ * @since 4.12
+ */
+ public List<FrameworkField> getAnnotatedFields() {
+ return collectValues(fieldsForAnnotations);
+ }
+
+ /**
+ * 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 Collections.unmodifiableList(getAnnotatedMembers(fieldsForAnnotations, annotationClass, false));
+ }
+
+ private <T> List<T> collectValues(Map<?, List<T>> map) {
+ Set<T> values = new LinkedHashSet<T>();
+ for (List<T> additionalValues : map.values()) {
+ values.addAll(additionalValues);
+ }
+ return new ArrayList<T>(values);
+ }
+
+ private static <T> List<T> getAnnotatedMembers(Map<Class<? extends Annotation>, List<T>> map,
+ Class<? extends Annotation> type, boolean fillIfAbsent) {
+ if (!map.containsKey(type) && fillIfAbsent) {
+ map.put(type, new ArrayList<T>());
+ }
+ List<T> members = map.get(type);
+ return members == null ? Collections.<T>emptyList() : members;
+ }
+
+ private static boolean runsTopToBottom(Class<? extends Annotation> annotation) {
+ return annotation.equals(Before.class)
+ || annotation.equals(BeforeClass.class);
+ }
+
+ private static 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 clazz;
+ }
+
+ /**
+ * Returns the class's name.
+ */
+ public String getName() {
+ if (clazz == null) {
+ return "null";
+ }
+ return clazz.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 = clazz.getConstructors();
+ Assert.assertEquals(1, constructors.length);
+ return constructors[0];
+ }
+
+ /**
+ * Returns the annotations on this class
+ */
+ public Annotation[] getAnnotations() {
+ if (clazz == null) {
+ return new Annotation[0];
+ }
+ return clazz.getAnnotations();
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ if (clazz == null) {
+ return null;
+ }
+ return clazz.getAnnotation(annotationType);
+ }
+
+ 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 <T> List<T> getAnnotatedMethodValues(Object test,
+ Class<? extends Annotation> annotationClass, Class<T> valueClass) {
+ List<T> results = new ArrayList<T>();
+ for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) {
+ try {
+ /*
+ * A method annotated with @Rule may return a @TestRule or a @MethodRule,
+ * we cannot call the method to check whether the return type matches our
+ * expectation i.e. subclass of valueClass. If we do that then the method
+ * will be invoked twice and we do not want to do that. So we first check
+ * whether return type matches our expectation and only then call the method
+ * to fetch the MethodRule
+ */
+ if (valueClass.isAssignableFrom(each.getReturnType())) {
+ Object fieldValue = each.invokeExplosively(test);
+ results.add(valueClass.cast(fieldValue));
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Exception in " + each.getName(), e);
+ }
+ }
+ return results;
+ }
+
+ public boolean isPublic() {
+ return Modifier.isPublic(clazz.getModifiers());
+ }
+
+ public boolean isANonStaticInnerClass() {
+ return clazz.isMemberClass() && !isStatic(clazz.getModifiers());
+ }
+
+ @Override
+ public int hashCode() {
+ return (clazz == null) ? 0 : clazz.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TestClass other = (TestClass) obj;
+ return clazz == other.clazz;
+ }
+
+ /**
+ * Compares two fields by its name.
+ */
+ private static class FieldComparator implements Comparator<Field> {
+ public int compare(Field left, Field right) {
+ return left.getName().compareTo(right.getName());
+ }
+ }
+
+ /**
+ * Compares two methods by its name.
+ */
+ private static class MethodComparator implements
+ Comparator<FrameworkMethod> {
+ public int compare(FrameworkMethod left, FrameworkMethod right) {
+ return NAME_ASCENDING.compare(left.getMethod(), right.getMethod());
+ }
+ }
}
diff --git a/src/main/java/org/junit/runners/model/TestTimedOutException.java b/src/main/java/org/junit/runners/model/TestTimedOutException.java
new file mode 100644
index 0000000..60e1a8a
--- /dev/null
+++ b/src/main/java/org/junit/runners/model/TestTimedOutException.java
@@ -0,0 +1,44 @@
+package org.junit.runners.model;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Exception thrown when a test fails on timeout.
+ *
+ * @since 4.12
+ *
+ */
+public class TestTimedOutException extends Exception {
+
+ private static final long serialVersionUID = 31935685163547539L;
+
+ private final TimeUnit timeUnit;
+ private final long timeout;
+
+ /**
+ * Creates exception with a standard message "test timed out after [timeout] [timeUnit]"
+ *
+ * @param timeout the amount of time passed before the test was interrupted
+ * @param timeUnit the time unit for the timeout value
+ */
+ public TestTimedOutException(long timeout, TimeUnit timeUnit) {
+ super(String.format("test timed out after %d %s",
+ timeout, timeUnit.name().toLowerCase()));
+ this.timeUnit = timeUnit;
+ this.timeout = timeout;
+ }
+
+ /**
+ * Gets the time passed before the test was interrupted
+ */
+ public long getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Gets the time unit for the timeout value
+ */
+ public TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+}
diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java
new file mode 100644
index 0000000..1c49f84
--- /dev/null
+++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java
@@ -0,0 +1,143 @@
+package org.junit.runners.parameterized;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.List;
+
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+/**
+ * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be
+ * injected via constructor or into annotated fields.
+ */
+public class BlockJUnit4ClassRunnerWithParameters extends
+ BlockJUnit4ClassRunner {
+ private final Object[] parameters;
+
+ private final String name;
+
+ public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
+ throws InitializationError {
+ super(test.getTestClass().getJavaClass());
+ parameters = test.getParameters().toArray(
+ new Object[test.getParameters().size()]);
+ name = test.getName();
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ if (fieldsAreAnnotated()) {
+ return createTestUsingFieldInjection();
+ } else {
+ return createTestUsingConstructorInjection();
+ }
+ }
+
+ private Object createTestUsingConstructorInjection() throws Exception {
+ return getTestClass().getOnlyConstructor().newInstance(parameters);
+ }
+
+ private Object createTestUsingFieldInjection() throws Exception {
+ List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
+ if (annotatedFieldsByParameter.size() != parameters.length) {
+ throw new Exception(
+ "Wrong number of parameters and @Parameter fields."
+ + " @Parameter fields counted: "
+ + annotatedFieldsByParameter.size()
+ + ", available parameters: " + parameters.length
+ + ".");
+ }
+ Object testClassInstance = getTestClass().getJavaClass().newInstance();
+ for (FrameworkField each : annotatedFieldsByParameter) {
+ Field field = each.getField();
+ Parameter annotation = field.getAnnotation(Parameter.class);
+ int index = annotation.value();
+ try {
+ field.set(testClassInstance, parameters[index]);
+ } catch (IllegalArgumentException iare) {
+ throw new Exception(getTestClass().getName()
+ + ": Trying to set " + field.getName()
+ + " with the value " + parameters[index]
+ + " that is not the right type ("
+ + parameters[index].getClass().getSimpleName()
+ + " instead of " + field.getType().getSimpleName()
+ + ").", iare);
+ }
+ }
+ return testClassInstance;
+ }
+
+ @Override
+ protected String getName() {
+ return name;
+ }
+
+ @Override
+ protected String testName(FrameworkMethod method) {
+ return method.getName() + getName();
+ }
+
+ @Override
+ protected void validateConstructor(List<Throwable> errors) {
+ validateOnlyOneConstructor(errors);
+ if (fieldsAreAnnotated()) {
+ validateZeroArgConstructor(errors);
+ }
+ }
+
+ @Override
+ protected void validateFields(List<Throwable> errors) {
+ super.validateFields(errors);
+ if (fieldsAreAnnotated()) {
+ List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
+ int[] usedIndices = new int[annotatedFieldsByParameter.size()];
+ for (FrameworkField each : annotatedFieldsByParameter) {
+ int index = each.getField().getAnnotation(Parameter.class)
+ .value();
+ if (index < 0 || index > annotatedFieldsByParameter.size() - 1) {
+ errors.add(new Exception("Invalid @Parameter value: "
+ + index + ". @Parameter fields counted: "
+ + annotatedFieldsByParameter.size()
+ + ". Please use an index between 0 and "
+ + (annotatedFieldsByParameter.size() - 1) + "."));
+ } else {
+ usedIndices[index]++;
+ }
+ }
+ for (int index = 0; index < usedIndices.length; index++) {
+ int numberOfUse = usedIndices[index];
+ if (numberOfUse == 0) {
+ errors.add(new Exception("@Parameter(" + index
+ + ") is never used."));
+ } else if (numberOfUse > 1) {
+ errors.add(new Exception("@Parameter(" + index
+ + ") is used more than once (" + numberOfUse + ")."));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Statement classBlock(RunNotifier notifier) {
+ return childrenInvoker(notifier);
+ }
+
+ @Override
+ protected Annotation[] getRunnerAnnotations() {
+ return new Annotation[0];
+ }
+
+ private List<FrameworkField> getAnnotatedFieldsByParameter() {
+ return getTestClass().getAnnotatedFields(Parameter.class);
+ }
+
+ private boolean fieldsAreAnnotated() {
+ return !getAnnotatedFieldsByParameter().isEmpty();
+ }
+}
diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java
new file mode 100644
index 0000000..ae49ef4
--- /dev/null
+++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java
@@ -0,0 +1,18 @@
+package org.junit.runners.parameterized;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * A {@link ParametersRunnerFactory} that creates
+ * {@link BlockJUnit4ClassRunnerWithParameters}.
+ *
+ * @since 4.12
+ */
+public class BlockJUnit4ClassRunnerWithParametersFactory implements
+ ParametersRunnerFactory {
+ public Runner createRunnerForTestWithParameters(TestWithParameters test)
+ throws InitializationError {
+ return new BlockJUnit4ClassRunnerWithParameters(test);
+ }
+}
diff --git a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java
new file mode 100644
index 0000000..16ea1f3
--- /dev/null
+++ b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java
@@ -0,0 +1,21 @@
+package org.junit.runners.parameterized;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * A {@code ParameterizedRunnerFactory} creates a runner for a single
+ * {@link TestWithParameters}.
+ *
+ * @since 4.12
+ */
+public interface ParametersRunnerFactory {
+ /**
+ * Returns a runner for the specified {@link TestWithParameters}.
+ *
+ * @throws InitializationError
+ * if the runner could not be created.
+ */
+ Runner createRunnerForTestWithParameters(TestWithParameters test)
+ throws InitializationError;
+}
diff --git a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java
new file mode 100644
index 0000000..1b86644
--- /dev/null
+++ b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java
@@ -0,0 +1,82 @@
+package org.junit.runners.parameterized;
+
+import static java.util.Collections.unmodifiableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.runners.model.TestClass;
+
+/**
+ * A {@code TestWithParameters} keeps the data together that are needed for
+ * creating a runner for a single data set of a parameterized test. It has a
+ * name, the test class and a list of parameters.
+ *
+ * @since 4.12
+ */
+public class TestWithParameters {
+ private final String name;
+
+ private final TestClass testClass;
+
+ private final List<Object> parameters;
+
+ public TestWithParameters(String name, TestClass testClass,
+ List<Object> parameters) {
+ notNull(name, "The name is missing.");
+ notNull(testClass, "The test class is missing.");
+ notNull(parameters, "The parameters are missing.");
+ this.name = name;
+ this.testClass = testClass;
+ this.parameters = unmodifiableList(new ArrayList<Object>(parameters));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public TestClass getTestClass() {
+ return testClass;
+ }
+
+ public List<Object> getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public int hashCode() {
+ int prime = 14747;
+ int result = prime + name.hashCode();
+ result = prime * result + testClass.hashCode();
+ return prime * result + parameters.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TestWithParameters other = (TestWithParameters) obj;
+ return name.equals(other.name)
+ && parameters.equals(other.parameters)
+ && testClass.equals(other.testClass);
+ }
+
+ @Override
+ public String toString() {
+ return testClass.getName() + " '" + name + "' with parameters "
+ + parameters;
+ }
+
+ private static void notNull(Object value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/validator/AnnotationValidator.java b/src/main/java/org/junit/validator/AnnotationValidator.java
new file mode 100644
index 0000000..8a53adf
--- /dev/null
+++ b/src/main/java/org/junit/validator/AnnotationValidator.java
@@ -0,0 +1,60 @@
+package org.junit.validator;
+
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+import static java.util.Collections.emptyList;
+
+import java.util.List;
+
+/**
+ * Validates annotations on classes and methods. To be validated,
+ * an annotation should be annotated with {@link ValidateWith}
+ *
+ * Instances of this class are shared by multiple test runners, so they should
+ * be immutable and thread-safe.
+ *
+ * @since 4.12
+ */
+public abstract class AnnotationValidator {
+
+ private static final List<Exception> NO_VALIDATION_ERRORS = emptyList();
+
+ /**
+ * Validates annotation on the given class.
+ *
+ * @param testClass that is being validated
+ * @return A list of exceptions. Default behavior is to return an empty list.
+ *
+ * @since 4.12
+ */
+ public List<Exception> validateAnnotatedClass(TestClass testClass) {
+ return NO_VALIDATION_ERRORS;
+ }
+
+ /**
+ * Validates annotation on the given field.
+ *
+ * @param field that is being validated
+ * @return A list of exceptions. Default behavior is to return an empty list.
+ *
+ * @since 4.12
+ */
+ public List<Exception> validateAnnotatedField(FrameworkField field) {
+ return NO_VALIDATION_ERRORS;
+
+ }
+
+ /**
+ * Validates annotation on the given method.
+ *
+ * @param method that is being validated
+ * @return A list of exceptions. Default behavior is to return an empty list.
+ *
+ * @since 4.12
+ */
+ public List<Exception> validateAnnotatedMethod(FrameworkMethod method) {
+ return NO_VALIDATION_ERRORS;
+ }
+}
diff --git a/src/main/java/org/junit/validator/AnnotationValidatorFactory.java b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java
new file mode 100644
index 0000000..7309fdd
--- /dev/null
+++ b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java
@@ -0,0 +1,42 @@
+package org.junit.validator;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Creates instances of Annotation Validators.
+ *
+ * @since 4.12
+ */
+public class AnnotationValidatorFactory {
+ private static final ConcurrentHashMap<ValidateWith, AnnotationValidator> VALIDATORS_FOR_ANNOTATION_TYPES =
+ new ConcurrentHashMap<ValidateWith, AnnotationValidator>();
+
+ /**
+ * Creates the AnnotationValidator specified by the value in
+ * {@link org.junit.validator.ValidateWith}. Instances are
+ * cached.
+ *
+ * @return An instance of the AnnotationValidator.
+ *
+ * @since 4.12
+ */
+ public AnnotationValidator createAnnotationValidator(ValidateWith validateWithAnnotation) {
+ AnnotationValidator validator = VALIDATORS_FOR_ANNOTATION_TYPES.get(validateWithAnnotation);
+ if (validator != null) {
+ return validator;
+ }
+
+ Class<? extends AnnotationValidator> clazz = validateWithAnnotation.value();
+ if (clazz == null) {
+ throw new IllegalArgumentException("Can't create validator, value is null in annotation " + validateWithAnnotation.getClass().getName());
+ }
+ try {
+ AnnotationValidator annotationValidator = clazz.newInstance();
+ VALIDATORS_FOR_ANNOTATION_TYPES.putIfAbsent(validateWithAnnotation, annotationValidator);
+ return VALIDATORS_FOR_ANNOTATION_TYPES.get(validateWithAnnotation);
+ } catch (Exception e) {
+ throw new RuntimeException("Exception received when creating AnnotationValidator class " + clazz.getName(), e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/junit/validator/AnnotationsValidator.java b/src/main/java/org/junit/validator/AnnotationsValidator.java
new file mode 100644
index 0000000..30f54a6
--- /dev/null
+++ b/src/main/java/org/junit/validator/AnnotationsValidator.java
@@ -0,0 +1,120 @@
+package org.junit.validator;
+
+import static java.util.Collections.singletonList;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.runners.model.Annotatable;
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+/**
+ * An {@code AnnotationsValidator} validates all annotations of a test class,
+ * including its annotated fields and methods.
+ *
+ * @since 4.12
+ */
+public final class AnnotationsValidator implements TestClassValidator {
+ private static final List<AnnotatableValidator<?>> VALIDATORS = Arrays.<AnnotatableValidator<?>>asList(
+ new ClassValidator(), new MethodValidator(), new FieldValidator());
+
+ /**
+ * Validate all annotations of the specified test class that are be
+ * annotated with {@link ValidateWith}.
+ *
+ * @param testClass
+ * the {@link TestClass} that is validated.
+ * @return the errors found by the validator.
+ */
+ public List<Exception> validateTestClass(TestClass testClass) {
+ List<Exception> validationErrors= new ArrayList<Exception>();
+ for (AnnotatableValidator<?> validator : VALIDATORS) {
+ List<Exception> additionalErrors= validator
+ .validateTestClass(testClass);
+ validationErrors.addAll(additionalErrors);
+ }
+ return validationErrors;
+ }
+
+ private static abstract class AnnotatableValidator<T extends Annotatable> {
+ private static final AnnotationValidatorFactory ANNOTATION_VALIDATOR_FACTORY = new AnnotationValidatorFactory();
+
+ abstract Iterable<T> getAnnotatablesForTestClass(TestClass testClass);
+
+ abstract List<Exception> validateAnnotatable(
+ AnnotationValidator validator, T annotatable);
+
+ public List<Exception> validateTestClass(TestClass testClass) {
+ List<Exception> validationErrors= new ArrayList<Exception>();
+ for (T annotatable : getAnnotatablesForTestClass(testClass)) {
+ List<Exception> additionalErrors= validateAnnotatable(annotatable);
+ validationErrors.addAll(additionalErrors);
+ }
+ return validationErrors;
+ }
+
+ private List<Exception> validateAnnotatable(T annotatable) {
+ List<Exception> validationErrors= new ArrayList<Exception>();
+ for (Annotation annotation : annotatable.getAnnotations()) {
+ Class<? extends Annotation> annotationType = annotation
+ .annotationType();
+ ValidateWith validateWith = annotationType
+ .getAnnotation(ValidateWith.class);
+ if (validateWith != null) {
+ AnnotationValidator annotationValidator = ANNOTATION_VALIDATOR_FACTORY
+ .createAnnotationValidator(validateWith);
+ List<Exception> errors= validateAnnotatable(
+ annotationValidator, annotatable);
+ validationErrors.addAll(errors);
+ }
+ }
+ return validationErrors;
+ }
+ }
+
+ private static class ClassValidator extends AnnotatableValidator<TestClass> {
+ @Override
+ Iterable<TestClass> getAnnotatablesForTestClass(TestClass testClass) {
+ return singletonList(testClass);
+ }
+
+ @Override
+ List<Exception> validateAnnotatable(
+ AnnotationValidator validator, TestClass testClass) {
+ return validator.validateAnnotatedClass(testClass);
+ }
+ }
+
+ private static class MethodValidator extends
+ AnnotatableValidator<FrameworkMethod> {
+ @Override
+ Iterable<FrameworkMethod> getAnnotatablesForTestClass(
+ TestClass testClass) {
+ return testClass.getAnnotatedMethods();
+ }
+
+ @Override
+ List<Exception> validateAnnotatable(
+ AnnotationValidator validator, FrameworkMethod method) {
+ return validator.validateAnnotatedMethod(method);
+ }
+ }
+
+ private static class FieldValidator extends
+ AnnotatableValidator<FrameworkField> {
+ @Override
+ Iterable<FrameworkField> getAnnotatablesForTestClass(TestClass testClass) {
+ return testClass.getAnnotatedFields();
+ }
+
+ @Override
+ List<Exception> validateAnnotatable(
+ AnnotationValidator validator, FrameworkField field) {
+ return validator.validateAnnotatedField(field);
+ }
+ };
+}
diff --git a/src/main/java/org/junit/validator/PublicClassValidator.java b/src/main/java/org/junit/validator/PublicClassValidator.java
new file mode 100644
index 0000000..fe3f185
--- /dev/null
+++ b/src/main/java/org/junit/validator/PublicClassValidator.java
@@ -0,0 +1,33 @@
+package org.junit.validator;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
+import java.util.List;
+
+import org.junit.runners.model.TestClass;
+
+/**
+ * Validates that a {@link TestClass} is public.
+ *
+ * @since 4.12
+ */
+public class PublicClassValidator implements TestClassValidator {
+ private static final List<Exception> NO_VALIDATION_ERRORS = emptyList();
+
+ /**
+ * Validate that the specified {@link TestClass} is public.
+ *
+ * @param testClass the {@link TestClass} that is validated.
+ * @return an empty list if the class is public or a list with a single
+ * exception otherwise.
+ */
+ public List<Exception> validateTestClass(TestClass testClass) {
+ if (testClass.isPublic()) {
+ return NO_VALIDATION_ERRORS;
+ } else {
+ return singletonList(new Exception("The class "
+ + testClass.getName() + " is not public."));
+ }
+ }
+}
diff --git a/src/main/java/org/junit/validator/TestClassValidator.java b/src/main/java/org/junit/validator/TestClassValidator.java
new file mode 100644
index 0000000..43cb787
--- /dev/null
+++ b/src/main/java/org/junit/validator/TestClassValidator.java
@@ -0,0 +1,21 @@
+package org.junit.validator;
+
+import java.util.List;
+
+import org.junit.runners.model.TestClass;
+
+/**
+ * Validates a single facet of a test class.
+ *
+ * @since 4.12
+ */
+public interface TestClassValidator {
+ /**
+ * Validate a single facet of a test class.
+ *
+ * @param testClass
+ * the {@link TestClass} that is validated.
+ * @return the validation errors found by the validator.
+ */
+ public List<Exception> validateTestClass(TestClass testClass);
+}
diff --git a/src/main/java/org/junit/validator/ValidateWith.java b/src/main/java/org/junit/validator/ValidateWith.java
new file mode 100644
index 0000000..03d7906
--- /dev/null
+++ b/src/main/java/org/junit/validator/ValidateWith.java
@@ -0,0 +1,19 @@
+package org.junit.validator;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Allows for an {@link AnnotationValidator} to be attached to an annotation.
+ *
+ * <p>When attached to an annotation, the validator will be instantiated and invoked
+ * by the {@link org.junit.runners.ParentRunner}.</p>
+ *
+ * @since 4.12
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ValidateWith {
+ Class<? extends AnnotationValidator> value();
+}