aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit
diff options
context:
space:
mode:
authorPaul Duffin <paulduffin@google.com>2016-12-14 11:49:43 +0000
committerPaul Duffin <paulduffin@google.com>2016-12-20 15:52:52 +0000
commitaeb93fc33cae3aadbb9b46083350ad2dc9aea645 (patch)
treeb316db7dee11d1aeee3510562e036fd41705b8b5 /src/main/java/org/junit
parent26401927b83770db45f00706ccc589955644c6c2 (diff)
downloadjunit-aeb93fc33cae3aadbb9b46083350ad2dc9aea645.tar.gz
Upgrade to JUnit 4.12
The license has changed from Common Public License v1.0 to Eclipse Public License v1.0. This will not compile as it is because it is intended to be built against Hamcrest 1.3 or later but it is being built against Hamcrest 1.1. A follow on patch will fix the compilation errors so that it builds against Hamcrest 1.1. That allows Hamcrest to be upgraded separately. The patch can be reverted once Hamcrest has been upgraded. There are also some Android specific issues that will also be fixed in follow on patches. Bug: 33613916 Test: make checkbuild Change-Id: Ic2c983a030399e3ace1a14927cb143fbd8307b4f
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();
+}