From 8e80a2a7b89329f95cb41e8b2981044362478c04 Mon Sep 17 00:00:00 2001 From: Pete Bentley Date: Sun, 21 Feb 2021 18:26:28 +0000 Subject: Upgrade external/junit to 4.13.2 Contains just the changes from 4.12 to 4.13.2 and undoes local Android changes. Will re-patch those in in subsequent CLs. This change re-lands https://r.android.com/1598413. Bug: 129054170 Test: m Change-Id: I6135799c8be5db2ec4c3f13951c18c072427e30d --- README.version | 7 +- .../java/junit/extensions/ActiveTestSuite.java | 2 +- src/main/java/junit/extensions/TestDecorator.java | 1 + src/main/java/junit/framework/Assert.java | 78 ++-- .../java/junit/framework/ComparisonCompactor.java | 1 + .../java/junit/framework/JUnit4TestAdapter.java | 25 +- src/main/java/junit/framework/Protectable.java | 4 +- src/main/java/junit/framework/TestCase.java | 47 +-- src/main/java/junit/framework/TestFailure.java | 8 +- src/main/java/junit/framework/TestResult.java | 10 +- src/main/java/junit/framework/TestSuite.java | 29 +- src/main/java/junit/runner/BaseTestRunner.java | 9 +- src/main/java/junit/runner/TestRunListener.java | 16 +- src/main/java/junit/runner/Version.java | 2 +- src/main/java/junit/textui/TestRunner.java | 4 +- src/main/java/org/junit/Assert.java | 170 ++++++--- src/main/java/org/junit/Assume.java | 19 +- .../org/junit/AssumptionViolatedException.java | 4 +- src/main/java/org/junit/ClassRule.java | 33 +- src/main/java/org/junit/ComparisonFailure.java | 2 +- src/main/java/org/junit/Rule.java | 39 +- src/main/java/org/junit/Test.java | 39 +- .../org/junit/TestCouldNotBeSkippedException.java | 19 + .../junit/experimental/categories/Categories.java | 108 +++--- .../categories/CategoryFilterFactory.java | 6 +- .../org/junit/experimental/max/MaxHistory.java | 15 +- .../experimental/results/PrintableResult.java | 9 + .../junit/experimental/results/ResultMatchers.java | 35 +- .../theories/ParametersSuppliedBy.java | 2 +- .../org/junit/experimental/theories/Theories.java | 11 +- .../theories/internal/Assignments.java | 11 +- .../java/org/junit/function/ThrowingRunnable.java | 14 + .../org/junit/internal/ArrayComparisonFailure.java | 13 +- .../internal/AssumptionViolatedException.java | 30 +- src/main/java/org/junit/internal/Checks.java | 37 ++ src/main/java/org/junit/internal/Classes.java | 28 +- .../org/junit/internal/ComparisonCriteria.java | 89 ++++- .../internal/SerializableMatcherDescription.java | 47 +++ .../internal/SerializableValueDescription.java | 38 ++ src/main/java/org/junit/internal/TextListener.java | 4 +- src/main/java/org/junit/internal/Throwables.java | 231 ++++++++++++ .../builders/AllDefaultPossibilitiesBuilder.java | 11 + .../org/junit/internal/builders/JUnit4Builder.java | 6 +- .../internal/management/FakeRuntimeMXBean.java | 21 ++ .../internal/management/FakeThreadMXBean.java | 27 ++ .../internal/management/ManagementFactory.java | 77 ++++ .../management/ReflectiveRuntimeMXBean.java | 61 ++++ .../management/ReflectiveThreadMXBean.java | 92 +++++ .../junit/internal/management/RuntimeMXBean.java | 14 + .../junit/internal/management/ThreadMXBean.java | 17 + .../matchers/StacktracePrintingMatcher.java | 9 +- .../internal/matchers/ThrowableCauseMatcher.java | 6 +- .../junit/internal/matchers/TypeSafeMatcher.java | 2 +- .../org/junit/internal/requests/ClassRequest.java | 41 ++- .../org/junit/internal/requests/FilterRequest.java | 2 +- .../junit/internal/requests/MemoizingRequest.java | 30 ++ .../junit/internal/requests/OrderingRequest.java | 29 ++ .../internal/runners/ErrorReportingRunner.java | 50 ++- .../internal/runners/InitializationError.java | 2 +- .../junit/internal/runners/JUnit38ClassRunner.java | 22 +- .../junit/internal/runners/MethodValidator.java | 2 +- .../java/org/junit/internal/runners/TestClass.java | 2 +- .../internal/runners/model/EachTestNotifier.java | 23 ++ .../internal/runners/rules/ValidationError.java | 3 + .../runners/statements/ExpectException.java | 4 +- .../internal/runners/statements/FailOnTimeout.java | 155 +++++++- .../internal/runners/statements/RunAfters.java | 9 +- .../internal/runners/statements/RunBefores.java | 9 +- .../java/org/junit/matchers/JUnitMatchers.java | 4 +- src/main/java/org/junit/rules/DisableOnDebug.java | 125 +++++++ src/main/java/org/junit/rules/ErrorCollector.java | 38 +- .../java/org/junit/rules/ExpectedException.java | 57 +-- .../java/org/junit/rules/ExternalResource.java | 15 +- src/main/java/org/junit/rules/MethodRule.java | 16 +- src/main/java/org/junit/rules/RuleChain.java | 50 ++- src/main/java/org/junit/rules/Stopwatch.java | 2 +- src/main/java/org/junit/rules/TemporaryFolder.java | 277 +++++++++++--- src/main/java/org/junit/rules/TestName.java | 2 +- src/main/java/org/junit/rules/TestWatcher.java | 12 +- src/main/java/org/junit/rules/Timeout.java | 37 +- src/main/java/org/junit/runner/Computer.java | 12 +- src/main/java/org/junit/runner/Describable.java | 2 +- src/main/java/org/junit/runner/Description.java | 13 +- src/main/java/org/junit/runner/FilterFactory.java | 3 +- .../junit/runner/JUnitCommandLineParseResult.java | 8 +- src/main/java/org/junit/runner/OrderWith.java | 28 ++ .../java/org/junit/runner/OrderWithValidator.java | 38 ++ src/main/java/org/junit/runner/Request.java | 58 ++- src/main/java/org/junit/runner/Result.java | 31 +- .../junit/runner/manipulation/Alphanumeric.java | 27 ++ .../manipulation/InvalidOrderingException.java | 21 ++ .../org/junit/runner/manipulation/Orderable.java | 21 ++ .../org/junit/runner/manipulation/Orderer.java | 62 ++++ .../org/junit/runner/manipulation/Ordering.java | 172 +++++++++ .../org/junit/runner/manipulation/Sortable.java | 2 +- .../java/org/junit/runner/manipulation/Sorter.java | 54 ++- .../org/junit/runner/notification/Failure.java | 23 +- .../org/junit/runner/notification/RunListener.java | 28 ++ .../org/junit/runner/notification/RunNotifier.java | 41 ++- .../notification/SynchronizedRunListener.java | 33 +- .../org/junit/runners/BlockJUnit4ClassRunner.java | 172 +++++---- src/main/java/org/junit/runners/JUnit4.java | 3 +- src/main/java/org/junit/runners/Parameterized.java | 405 ++++++++++++++------- src/main/java/org/junit/runners/ParentRunner.java | 176 +++++++-- src/main/java/org/junit/runners/RuleContainer.java | 113 ++++++ src/main/java/org/junit/runners/Suite.java | 4 +- .../org/junit/runners/model/FrameworkField.java | 21 +- .../org/junit/runners/model/FrameworkMember.java | 24 +- .../org/junit/runners/model/FrameworkMethod.java | 14 + .../junit/runners/model/InitializationError.java | 2 +- .../junit/runners/model/InvalidTestClassError.java | 39 ++ .../junit/runners/model/MemberValueConsumer.java | 18 + .../runners/model/MultipleFailureException.java | 41 ++- .../org/junit/runners/model/RunnerBuilder.java | 30 +- .../java/org/junit/runners/model/TestClass.java | 57 ++- .../BlockJUnit4ClassRunnerWithParameters.java | 94 ++++- .../parameterized/ParametersRunnerFactory.java | 2 +- .../runners/parameterized/TestWithParameters.java | 7 +- .../validator/AnnotationValidatorFactory.java | 3 - .../org/junit/validator/AnnotationsValidator.java | 4 +- .../org/junit/validator/TestClassValidator.java | 2 +- .../java/org/junit/validator/ValidateWith.java | 3 + version | 2 +- 123 files changed, 3780 insertions(+), 790 deletions(-) mode change 100755 => 100644 src/main/java/org/junit/Assert.java create mode 100644 src/main/java/org/junit/TestCouldNotBeSkippedException.java create mode 100644 src/main/java/org/junit/function/ThrowingRunnable.java create mode 100644 src/main/java/org/junit/internal/Checks.java create mode 100644 src/main/java/org/junit/internal/SerializableMatcherDescription.java create mode 100644 src/main/java/org/junit/internal/SerializableValueDescription.java create mode 100644 src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java create mode 100644 src/main/java/org/junit/internal/management/FakeThreadMXBean.java create mode 100644 src/main/java/org/junit/internal/management/ManagementFactory.java create mode 100644 src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java create mode 100644 src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java create mode 100644 src/main/java/org/junit/internal/management/RuntimeMXBean.java create mode 100644 src/main/java/org/junit/internal/management/ThreadMXBean.java create mode 100644 src/main/java/org/junit/internal/requests/MemoizingRequest.java create mode 100644 src/main/java/org/junit/internal/requests/OrderingRequest.java create mode 100644 src/main/java/org/junit/rules/DisableOnDebug.java create mode 100644 src/main/java/org/junit/runner/OrderWith.java create mode 100644 src/main/java/org/junit/runner/OrderWithValidator.java create mode 100644 src/main/java/org/junit/runner/manipulation/Alphanumeric.java create mode 100644 src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java create mode 100644 src/main/java/org/junit/runner/manipulation/Orderable.java create mode 100644 src/main/java/org/junit/runner/manipulation/Orderer.java create mode 100644 src/main/java/org/junit/runner/manipulation/Ordering.java mode change 100755 => 100644 src/main/java/org/junit/runners/ParentRunner.java create mode 100644 src/main/java/org/junit/runners/RuleContainer.java create mode 100644 src/main/java/org/junit/runners/model/InvalidTestClassError.java create mode 100644 src/main/java/org/junit/runners/model/MemberValueConsumer.java mode change 100755 => 100644 src/main/java/org/junit/runners/model/TestClass.java diff --git a/README.version b/README.version index fd9d421..2f38283 100644 --- a/README.version +++ b/README.version @@ -1,8 +1,5 @@ -URL: https://github.com/junit-team/junit/archive/r4.12.tar.gz -Version: 4.12 +URL: https://github.com/junit-team/junit/archive/r4.13.2.tar.gz +Version: 4.13.2 BugComponent: 40416 Local Changes: - Remove DisableOnDebug (new in 4.12) as it is not supported on Android - Remove support for stuck threads - Extra generic type information to aid certain javacs. diff --git a/src/main/java/junit/extensions/ActiveTestSuite.java b/src/main/java/junit/extensions/ActiveTestSuite.java index 95c5e2e..6f0f99d 100644 --- a/src/main/java/junit/extensions/ActiveTestSuite.java +++ b/src/main/java/junit/extensions/ActiveTestSuite.java @@ -63,7 +63,7 @@ public class ActiveTestSuite extends TestSuite { } } - synchronized public void runFinished() { + public synchronized void runFinished() { fActiveTestDeathCount++; notifyAll(); } diff --git a/src/main/java/junit/extensions/TestDecorator.java b/src/main/java/junit/extensions/TestDecorator.java index 2b74f30..a3c5e08 100644 --- a/src/main/java/junit/extensions/TestDecorator.java +++ b/src/main/java/junit/extensions/TestDecorator.java @@ -9,6 +9,7 @@ import junit.framework.TestResult; * test decorators. Test decorator subclasses can be introduced to add behaviour * before or after a test is run. */ +@SuppressWarnings("deprecation") public class TestDecorator extends Assert implements Test { protected Test fTest; diff --git a/src/main/java/junit/framework/Assert.java b/src/main/java/junit/framework/Assert.java index 663461c..43482a1 100644 --- a/src/main/java/junit/framework/Assert.java +++ b/src/main/java/junit/framework/Assert.java @@ -17,7 +17,7 @@ public class Assert { * Asserts that a condition is true. If it isn't it throws * an AssertionFailedError with the given message. */ - static public void assertTrue(String message, boolean condition) { + public static void assertTrue(String message, boolean condition) { if (!condition) { fail(message); } @@ -27,7 +27,7 @@ public class Assert { * Asserts that a condition is true. If it isn't it throws * an AssertionFailedError. */ - static public void assertTrue(boolean condition) { + public static void assertTrue(boolean condition) { assertTrue(null, condition); } @@ -35,7 +35,7 @@ public class Assert { * Asserts that a condition is false. If it isn't it throws * an AssertionFailedError with the given message. */ - static public void assertFalse(String message, boolean condition) { + public static void assertFalse(String message, boolean condition) { assertTrue(message, !condition); } @@ -43,14 +43,14 @@ public class Assert { * Asserts that a condition is false. If it isn't it throws * an AssertionFailedError. */ - static public void assertFalse(boolean condition) { + public static void assertFalse(boolean condition) { assertFalse(null, condition); } /** * Fails a test with the given message. */ - static public void fail(String message) { + public static void fail(String message) { if (message == null) { throw new AssertionFailedError(); } @@ -60,7 +60,7 @@ public class Assert { /** * Fails a test with no message. */ - static public void fail() { + public static void fail() { fail(null); } @@ -68,7 +68,7 @@ public class Assert { * Asserts that two objects are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, Object expected, Object actual) { + public static void assertEquals(String message, Object expected, Object actual) { if (expected == null && actual == null) { return; } @@ -82,14 +82,14 @@ public class Assert { * Asserts that two objects are equal. If they are not * an AssertionFailedError is thrown. */ - static public void assertEquals(Object expected, Object actual) { + public static void assertEquals(Object expected, Object actual) { assertEquals(null, expected, actual); } /** * Asserts that two Strings are equal. */ - static public void assertEquals(String message, String expected, String actual) { + public static void assertEquals(String message, String expected, String actual) { if (expected == null && actual == null) { return; } @@ -103,7 +103,7 @@ public class Assert { /** * Asserts that two Strings are equal. */ - static public void assertEquals(String expected, String actual) { + public static void assertEquals(String expected, String actual) { assertEquals(null, expected, actual); } @@ -112,12 +112,12 @@ public class Assert { * an AssertionFailedError is thrown with the given message. If the expected * value is infinity then the delta value is ignored. */ - static public void assertEquals(String message, double expected, double actual, double delta) { + public static void assertEquals(String message, double expected, double actual, double delta) { if (Double.compare(expected, actual) == 0) { return; } if (!(Math.abs(expected - actual) <= delta)) { - failNotEquals(message, new Double(expected), new Double(actual)); + failNotEquals(message, Double.valueOf(expected), Double.valueOf(actual)); } } @@ -125,7 +125,7 @@ public class Assert { * Asserts that two doubles are equal concerning a delta. If the expected * value is infinity then the delta value is ignored. */ - static public void assertEquals(double expected, double actual, double delta) { + public static void assertEquals(double expected, double actual, double delta) { assertEquals(null, expected, actual, delta); } @@ -134,12 +134,12 @@ public class Assert { * are not an AssertionFailedError is thrown with the given message. If the * expected value is infinity then the delta value is ignored. */ - static public void assertEquals(String message, float expected, float actual, float delta) { + public static void assertEquals(String message, float expected, float actual, float delta) { if (Float.compare(expected, actual) == 0) { return; } if (!(Math.abs(expected - actual) <= delta)) { - failNotEquals(message, new Float(expected), new Float(actual)); + failNotEquals(message, Float.valueOf(expected), Float.valueOf(actual)); } } @@ -147,7 +147,7 @@ public class Assert { * Asserts that two floats are equal concerning a delta. If the expected * value is infinity then the delta value is ignored. */ - static public void assertEquals(float expected, float actual, float delta) { + public static void assertEquals(float expected, float actual, float delta) { assertEquals(null, expected, actual, delta); } @@ -155,14 +155,14 @@ public class Assert { * Asserts that two longs are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, long expected, long actual) { + public static void assertEquals(String message, long expected, long actual) { assertEquals(message, Long.valueOf(expected), Long.valueOf(actual)); } /** * Asserts that two longs are equal. */ - static public void assertEquals(long expected, long actual) { + public static void assertEquals(long expected, long actual) { assertEquals(null, expected, actual); } @@ -170,14 +170,14 @@ public class Assert { * Asserts that two booleans are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, boolean expected, boolean actual) { + public static void assertEquals(String message, boolean expected, boolean actual) { assertEquals(message, Boolean.valueOf(expected), Boolean.valueOf(actual)); } /** * Asserts that two booleans are equal. */ - static public void assertEquals(boolean expected, boolean actual) { + public static void assertEquals(boolean expected, boolean actual) { assertEquals(null, expected, actual); } @@ -185,14 +185,14 @@ public class Assert { * Asserts that two bytes are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, byte expected, byte actual) { + public static void assertEquals(String message, byte expected, byte actual) { assertEquals(message, Byte.valueOf(expected), Byte.valueOf(actual)); } /** * Asserts that two bytes are equal. */ - static public void assertEquals(byte expected, byte actual) { + public static void assertEquals(byte expected, byte actual) { assertEquals(null, expected, actual); } @@ -200,14 +200,14 @@ public class Assert { * Asserts that two chars are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, char expected, char actual) { + public static void assertEquals(String message, char expected, char actual) { assertEquals(message, Character.valueOf(expected), Character.valueOf(actual)); } /** * Asserts that two chars are equal. */ - static public void assertEquals(char expected, char actual) { + public static void assertEquals(char expected, char actual) { assertEquals(null, expected, actual); } @@ -215,14 +215,14 @@ public class Assert { * Asserts that two shorts are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, short expected, short actual) { + public static void assertEquals(String message, short expected, short actual) { assertEquals(message, Short.valueOf(expected), Short.valueOf(actual)); } /** * Asserts that two shorts are equal. */ - static public void assertEquals(short expected, short actual) { + public static void assertEquals(short expected, short actual) { assertEquals(null, expected, actual); } @@ -230,21 +230,21 @@ public class Assert { * Asserts that two ints are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertEquals(String message, int expected, int actual) { + public static void assertEquals(String message, int expected, int actual) { assertEquals(message, Integer.valueOf(expected), Integer.valueOf(actual)); } /** * Asserts that two ints are equal. */ - static public void assertEquals(int expected, int actual) { + public static void assertEquals(int expected, int actual) { assertEquals(null, expected, actual); } /** * Asserts that an object isn't null. */ - static public void assertNotNull(Object object) { + public static void assertNotNull(Object object) { assertNotNull(null, object); } @@ -252,7 +252,7 @@ public class Assert { * Asserts that an object isn't null. If it is * an AssertionFailedError is thrown with the given message. */ - static public void assertNotNull(String message, Object object) { + public static void assertNotNull(String message, Object object) { assertTrue(message, object != null); } @@ -263,7 +263,7 @@ public class Assert { * * @param object Object to check or null */ - static public void assertNull(Object object) { + public static void assertNull(Object object) { if (object != null) { assertNull("Expected: but was: " + object.toString(), object); } @@ -273,7 +273,7 @@ public class Assert { * Asserts that an object is null. If it is not * an AssertionFailedError is thrown with the given message. */ - static public void assertNull(String message, Object object) { + public static void assertNull(String message, Object object) { assertTrue(message, object == null); } @@ -281,7 +281,7 @@ public class Assert { * Asserts that two objects refer to the same object. If they are not * an AssertionFailedError is thrown with the given message. */ - static public void assertSame(String message, Object expected, Object actual) { + public static void assertSame(String message, Object expected, Object actual) { if (expected == actual) { return; } @@ -292,7 +292,7 @@ public class Assert { * Asserts that two objects refer to the same object. If they are not * the same an AssertionFailedError is thrown. */ - static public void assertSame(Object expected, Object actual) { + public static void assertSame(Object expected, Object actual) { assertSame(null, expected, actual); } @@ -301,7 +301,7 @@ public class Assert { * refer to the same object an AssertionFailedError is thrown with the * given message. */ - static public void assertNotSame(String message, Object expected, Object actual) { + public static void assertNotSame(String message, Object expected, Object actual) { if (expected == actual) { failSame(message); } @@ -311,21 +311,21 @@ public class Assert { * Asserts that two objects do not refer to the same object. If they do * refer to the same object an AssertionFailedError is thrown. */ - static public void assertNotSame(Object expected, Object actual) { + public static void assertNotSame(Object expected, Object actual) { assertNotSame(null, expected, actual); } - static public void failSame(String message) { + public static void failSame(String message) { String formatted = (message != null) ? message + " " : ""; fail(formatted + "expected not same"); } - static public void failNotSame(String message, Object expected, Object actual) { + public static void failNotSame(String message, Object expected, Object actual) { String formatted = (message != null) ? message + " " : ""; fail(formatted + "expected same:<" + expected + "> was not:<" + actual + ">"); } - static public void failNotEquals(String message, Object expected, Object actual) { + public static void failNotEquals(String message, Object expected, Object actual) { fail(format(message, expected, actual)); } diff --git a/src/main/java/junit/framework/ComparisonCompactor.java b/src/main/java/junit/framework/ComparisonCompactor.java index fa20a8e..81ddd5b 100644 --- a/src/main/java/junit/framework/ComparisonCompactor.java +++ b/src/main/java/junit/framework/ComparisonCompactor.java @@ -18,6 +18,7 @@ public class ComparisonCompactor { fActual = actual; } + @SuppressWarnings("deprecation") public String compact(String message) { if (fExpected == null || fActual == null || areStringsEqual()) { return Assert.format(message, fExpected, fActual); diff --git a/src/main/java/junit/framework/JUnit4TestAdapter.java b/src/main/java/junit/framework/JUnit4TestAdapter.java index cbb66db..9d32031 100644 --- a/src/main/java/junit/framework/JUnit4TestAdapter.java +++ b/src/main/java/junit/framework/JUnit4TestAdapter.java @@ -9,11 +9,23 @@ import org.junit.runner.Request; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.Orderer; +import org.junit.runner.manipulation.InvalidOrderingException; import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Orderable; import org.junit.runner.manipulation.Sorter; -public class JUnit4TestAdapter implements Test, Filterable, Sortable, Describable { +/** + * The JUnit4TestAdapter enables running JUnit-4-style tests using a JUnit-3-style test runner. + * + *

To use it, add the following to a test class: + *

+      public static Test suite() {
+        return new JUnit4TestAdapter(YourJUnit4TestClass.class);
+      }
+
+ */ +public class JUnit4TestAdapter implements Test, Filterable, Orderable, Describable { private final Class fNewTestClass; private final Runner fRunner; @@ -83,4 +95,13 @@ public class JUnit4TestAdapter implements Test, Filterable, Sortable, Describabl public void sort(Sorter sorter) { sorter.apply(fRunner); } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + public void order(Orderer orderer) throws InvalidOrderingException { + orderer.apply(fRunner); + } } \ No newline at end of file diff --git a/src/main/java/junit/framework/Protectable.java b/src/main/java/junit/framework/Protectable.java index 9f30b10..c5ceb16 100644 --- a/src/main/java/junit/framework/Protectable.java +++ b/src/main/java/junit/framework/Protectable.java @@ -8,7 +8,7 @@ package junit.framework; public interface Protectable { /** - * Run the the following method protected. + * Run the following method protected. */ public abstract void protect() throws Throwable; -} \ No newline at end of file +} diff --git a/src/main/java/junit/framework/TestCase.java b/src/main/java/junit/framework/TestCase.java index b89ce71..e474a64 100644 --- a/src/main/java/junit/framework/TestCase.java +++ b/src/main/java/junit/framework/TestCase.java @@ -73,6 +73,7 @@ import java.lang.reflect.Modifier; * @see TestResult * @see TestSuite */ +@SuppressWarnings("deprecation") public abstract class TestCase extends Assert implements Test { /** * the name of the test case @@ -102,7 +103,7 @@ public abstract class TestCase extends Assert implements Test { } /** - * Creates a default TestResult object + * Creates a default TestResult object. * * @see TestResult */ @@ -187,7 +188,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that a condition is true. If it isn't it throws * an AssertionFailedError with the given message. */ - @SuppressWarnings("deprecation") public static void assertTrue(String message, boolean condition) { Assert.assertTrue(message, condition); } @@ -196,7 +196,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that a condition is true. If it isn't it throws * an AssertionFailedError. */ - @SuppressWarnings("deprecation") public static void assertTrue(boolean condition) { Assert.assertTrue(condition); } @@ -205,7 +204,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that a condition is false. If it isn't it throws * an AssertionFailedError with the given message. */ - @SuppressWarnings("deprecation") public static void assertFalse(String message, boolean condition) { Assert.assertFalse(message, condition); } @@ -214,7 +212,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that a condition is false. If it isn't it throws * an AssertionFailedError. */ - @SuppressWarnings("deprecation") public static void assertFalse(boolean condition) { Assert.assertFalse(condition); } @@ -222,7 +219,6 @@ public abstract class TestCase extends Assert implements Test { /** * Fails a test with the given message. */ - @SuppressWarnings("deprecation") public static void fail(String message) { Assert.fail(message); } @@ -230,7 +226,6 @@ public abstract class TestCase extends Assert implements Test { /** * Fails a test with no message. */ - @SuppressWarnings("deprecation") public static void fail() { Assert.fail(); } @@ -239,7 +234,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two objects are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, Object expected, Object actual) { Assert.assertEquals(message, expected, actual); } @@ -248,7 +242,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two objects are equal. If they are not * an AssertionFailedError is thrown. */ - @SuppressWarnings("deprecation") public static void assertEquals(Object expected, Object actual) { Assert.assertEquals(expected, actual); } @@ -256,7 +249,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two Strings are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, String expected, String actual) { Assert.assertEquals(message, expected, actual); } @@ -264,7 +256,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two Strings are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(String expected, String actual) { Assert.assertEquals(expected, actual); } @@ -274,7 +265,6 @@ public abstract class TestCase extends Assert implements Test { * an AssertionFailedError is thrown with the given message. If the expected * value is infinity then the delta value is ignored. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, double expected, double actual, double delta) { Assert.assertEquals(message, expected, actual, delta); } @@ -283,7 +273,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two doubles are equal concerning a delta. If the expected * value is infinity then the delta value is ignored. */ - @SuppressWarnings("deprecation") public static void assertEquals(double expected, double actual, double delta) { Assert.assertEquals(expected, actual, delta); } @@ -293,7 +282,6 @@ public abstract class TestCase extends Assert implements Test { * are not an AssertionFailedError is thrown with the given message. If the * expected value is infinity then the delta value is ignored. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, float expected, float actual, float delta) { Assert.assertEquals(message, expected, actual, delta); } @@ -302,7 +290,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two floats are equal concerning a delta. If the expected * value is infinity then the delta value is ignored. */ - @SuppressWarnings("deprecation") public static void assertEquals(float expected, float actual, float delta) { Assert.assertEquals(expected, actual, delta); } @@ -311,7 +298,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two longs are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, long expected, long actual) { Assert.assertEquals(message, expected, actual); } @@ -319,7 +305,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two longs are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(long expected, long actual) { Assert.assertEquals(expected, actual); } @@ -328,7 +313,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two booleans are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, boolean expected, boolean actual) { Assert.assertEquals(message, expected, actual); } @@ -336,7 +320,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two booleans are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(boolean expected, boolean actual) { Assert.assertEquals(expected, actual); } @@ -345,7 +328,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two bytes are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, byte expected, byte actual) { Assert.assertEquals(message, expected, actual); } @@ -353,7 +335,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two bytes are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(byte expected, byte actual) { Assert.assertEquals(expected, actual); } @@ -362,7 +343,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two chars are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, char expected, char actual) { Assert.assertEquals(message, expected, actual); } @@ -370,7 +350,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two chars are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(char expected, char actual) { Assert.assertEquals(expected, actual); } @@ -379,7 +358,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two shorts are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, short expected, short actual) { Assert.assertEquals(message, expected, actual); } @@ -387,7 +365,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two shorts are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(short expected, short actual) { Assert.assertEquals(expected, actual); } @@ -396,7 +373,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two ints are equal. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertEquals(String message, int expected, int actual) { Assert.assertEquals(message, expected, actual); } @@ -404,7 +380,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that two ints are equal. */ - @SuppressWarnings("deprecation") public static void assertEquals(int expected, int actual) { Assert.assertEquals(expected, actual); } @@ -412,7 +387,6 @@ public abstract class TestCase extends Assert implements Test { /** * Asserts that an object isn't null. */ - @SuppressWarnings("deprecation") public static void assertNotNull(Object object) { Assert.assertNotNull(object); } @@ -421,7 +395,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that an object isn't null. If it is * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertNotNull(String message, Object object) { Assert.assertNotNull(message, object); } @@ -433,7 +406,6 @@ public abstract class TestCase extends Assert implements Test { * * @param object Object to check or null */ - @SuppressWarnings("deprecation") public static void assertNull(Object object) { Assert.assertNull(object); } @@ -442,7 +414,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that an object is null. If it is not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertNull(String message, Object object) { Assert.assertNull(message, object); } @@ -451,7 +422,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two objects refer to the same object. If they are not * an AssertionFailedError is thrown with the given message. */ - @SuppressWarnings("deprecation") public static void assertSame(String message, Object expected, Object actual) { Assert.assertSame(message, expected, actual); } @@ -460,7 +430,6 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two objects refer to the same object. If they are not * the same an AssertionFailedError is thrown. */ - @SuppressWarnings("deprecation") public static void assertSame(Object expected, Object actual) { Assert.assertSame(expected, actual); } @@ -470,7 +439,6 @@ public abstract class TestCase extends Assert implements Test { * refer to the same object an AssertionFailedError is thrown with the * given message. */ - @SuppressWarnings("deprecation") public static void assertNotSame(String message, Object expected, Object actual) { Assert.assertNotSame(message, expected, actual); } @@ -479,27 +447,22 @@ public abstract class TestCase extends Assert implements Test { * Asserts that two objects do not refer to the same object. If they do * refer to the same object an AssertionFailedError is thrown. */ - @SuppressWarnings("deprecation") public static void assertNotSame(Object expected, Object actual) { Assert.assertNotSame(expected, actual); } - @SuppressWarnings("deprecation") public static void failSame(String message) { Assert.failSame(message); } - @SuppressWarnings("deprecation") public static void failNotSame(String message, Object expected, Object actual) { Assert.failNotSame(message, expected, actual); } - @SuppressWarnings("deprecation") public static void failNotEquals(String message, Object expected, Object actual) { Assert.failNotEquals(message, expected, actual); } - @SuppressWarnings("deprecation") public static String format(String message, Object expected, Object actual) { return Assert.format(message, expected, actual); } @@ -519,7 +482,7 @@ public abstract class TestCase extends Assert implements Test { } /** - * Returns a string representation of the test case + * Returns a string representation of the test case. */ @Override public String toString() { @@ -527,7 +490,7 @@ public abstract class TestCase extends Assert implements Test { } /** - * Gets the name of a TestCase + * Gets the name of a TestCase. * * @return the name of the TestCase */ @@ -536,7 +499,7 @@ public abstract class TestCase extends Assert implements Test { } /** - * Sets the name of a TestCase + * Sets the name of a TestCase. * * @param name the name to set */ diff --git a/src/main/java/junit/framework/TestFailure.java b/src/main/java/junit/framework/TestFailure.java index 6168b58..d1ddfbc 100644 --- a/src/main/java/junit/framework/TestFailure.java +++ b/src/main/java/junit/framework/TestFailure.java @@ -1,7 +1,6 @@ package junit.framework; -import java.io.PrintWriter; -import java.io.StringWriter; +import org.junit.internal.Throwables; /** @@ -49,10 +48,7 @@ public class TestFailure { * thrown by TestFailure. */ public String trace() { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - thrownException().printStackTrace(writer); - return stringWriter.toString(); + return Throwables.getStacktrace(thrownException()); } /** diff --git a/src/main/java/junit/framework/TestResult.java b/src/main/java/junit/framework/TestResult.java index 8332542..e01a2b0 100644 --- a/src/main/java/junit/framework/TestResult.java +++ b/src/main/java/junit/framework/TestResult.java @@ -52,14 +52,14 @@ public class TestResult { } /** - * Registers a TestListener + * Registers a TestListener. */ public synchronized void addListener(TestListener listener) { fListeners.add(listener); } /** - * Unregisters a TestListener + * Unregisters a TestListener. */ public synchronized void removeListener(TestListener listener) { fListeners.remove(listener); @@ -91,7 +91,7 @@ public class TestResult { } /** - * Returns an Enumeration for the errors + * Returns an Enumeration for the errors. */ public synchronized Enumeration errors() { return Collections.enumeration(fErrors); @@ -106,7 +106,7 @@ public class TestResult { } /** - * Returns an Enumeration for the failures + * Returns an Enumeration for the failures. */ public synchronized Enumeration failures() { return Collections.enumeration(fFailures); @@ -150,7 +150,7 @@ public class TestResult { } /** - * Checks whether the test run should stop + * Checks whether the test run should stop. */ public synchronized boolean shouldStop() { return fStop; diff --git a/src/main/java/junit/framework/TestSuite.java b/src/main/java/junit/framework/TestSuite.java index 366f1cf..50cd5f8 100644 --- a/src/main/java/junit/framework/TestSuite.java +++ b/src/main/java/junit/framework/TestSuite.java @@ -1,7 +1,5 @@ package junit.framework; -import java.io.PrintWriter; -import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -12,6 +10,7 @@ import java.util.List; import java.util.Vector; import org.junit.internal.MethodSorter; +import org.junit.internal.Throwables; /** * A TestSuite is a Composite of Tests. @@ -35,7 +34,7 @@ import org.junit.internal.MethodSorter; *

* A final option is to do the same for a large array of test classes. *

- * Class[] testClasses = { MathTest.class, AnotherTest.class }
+ * Class[] testClasses = { MathTest.class, AnotherTest.class };
  * TestSuite suite= new TestSuite(testClasses);
  * 
* @@ -65,11 +64,11 @@ public class TestSuite implements Test { test = constructor.newInstance(new Object[]{name}); } } catch (InstantiationException e) { - return (warning("Cannot instantiate test case: " + name + " (" + exceptionToString(e) + ")")); + return (warning("Cannot instantiate test case: " + name + " (" + Throwables.getStacktrace(e) + ")")); } catch (InvocationTargetException e) { - return (warning("Exception in constructor: " + name + " (" + exceptionToString(e.getTargetException()) + ")")); + return (warning("Exception in constructor: " + name + " (" + Throwables.getStacktrace(e.getTargetException()) + ")")); } catch (IllegalAccessException e) { - return (warning("Cannot access test case: " + name + " (" + exceptionToString(e) + ")")); + return (warning("Cannot access test case: " + name + " (" + Throwables.getStacktrace(e) + ")")); } return (Test) test; } @@ -99,16 +98,6 @@ public class TestSuite implements Test { }; } - /** - * Converts the stack trace into a string - */ - private static String exceptionToString(Throwable e) { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - e.printStackTrace(writer); - return stringWriter.toString(); - } - private String fName; private Vector fTests = new Vector(10); // Cannot convert this to List because it is used directly by some test runners @@ -210,7 +199,7 @@ public class TestSuite implements Test { } /** - * Adds the tests from the given class to the suite + * Adds the tests from the given class to the suite. */ public void addTestSuite(Class testClass) { addTest(new TestSuite(testClass)); @@ -262,21 +251,21 @@ public class TestSuite implements Test { } /** - * Returns the test at the given index + * Returns the test at the given index. */ public Test testAt(int index) { return fTests.get(index); } /** - * Returns the number of tests in this suite + * Returns the number of tests in this suite. */ public int testCount() { return fTests.size(); } /** - * Returns the tests as an enumeration + * Returns the tests as an enumeration. */ public Enumeration tests() { return fTests.elements(); diff --git a/src/main/java/junit/runner/BaseTestRunner.java b/src/main/java/junit/runner/BaseTestRunner.java index 8268323..d63fae7 100644 --- a/src/main/java/junit/runner/BaseTestRunner.java +++ b/src/main/java/junit/runner/BaseTestRunner.java @@ -20,6 +20,8 @@ import junit.framework.Test; import junit.framework.TestListener; import junit.framework.TestSuite; +import org.junit.internal.Throwables; + /** * Base class for all test runners. * This class was born live on stage in Sardinia during XP2000. @@ -233,6 +235,7 @@ public abstract class BaseTestRunner implements TestListener { setPreferences(new Properties(getPreferences())); getPreferences().load(is); } catch (IOException ignored) { + } catch (SecurityException ignored) { } finally { try { if (is != null) { @@ -264,11 +267,7 @@ public abstract class BaseTestRunner implements TestListener { * Returns a filtered stack trace */ public static String getFilteredTrace(Throwable e) { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - e.printStackTrace(writer); - String trace = stringWriter.toString(); - return BaseTestRunner.getFilteredTrace(trace); + return BaseTestRunner.getFilteredTrace(Throwables.getStacktrace(e)); } /** diff --git a/src/main/java/junit/runner/TestRunListener.java b/src/main/java/junit/runner/TestRunListener.java index b5e22f5..ce5b3d5 100644 --- a/src/main/java/junit/runner/TestRunListener.java +++ b/src/main/java/junit/runner/TestRunListener.java @@ -8,18 +8,18 @@ package junit.runner; */ public interface TestRunListener { /* test status constants*/ - public static final int STATUS_ERROR = 1; - public static final int STATUS_FAILURE = 2; + int STATUS_ERROR = 1; + int STATUS_FAILURE = 2; - public void testRunStarted(String testSuiteName, int testCount); + void testRunStarted(String testSuiteName, int testCount); - public void testRunEnded(long elapsedTime); + void testRunEnded(long elapsedTime); - public void testRunStopped(long elapsedTime); + void testRunStopped(long elapsedTime); - public void testStarted(String testName); + void testStarted(String testName); - public void testEnded(String testName); + void testEnded(String testName); - public void testFailed(int status, String testName, String trace); + void testFailed(int status, String testName, String trace); } diff --git a/src/main/java/junit/runner/Version.java b/src/main/java/junit/runner/Version.java index eaf3db7..6c7862e 100644 --- a/src/main/java/junit/runner/Version.java +++ b/src/main/java/junit/runner/Version.java @@ -9,7 +9,7 @@ public class Version { } public static String id() { - return "4.12-SNAPSHOT"; + return "4.13.3-SNAPSHOT"; } public static void main(String[] args) { diff --git a/src/main/java/junit/textui/TestRunner.java b/src/main/java/junit/textui/TestRunner.java index 4d78f77..913020a 100644 --- a/src/main/java/junit/textui/TestRunner.java +++ b/src/main/java/junit/textui/TestRunner.java @@ -131,7 +131,7 @@ public class TestRunner extends BaseTestRunner { } } - public static void main(String args[]) { + public static void main(String[] args) { TestRunner aTestRunner = new TestRunner(); try { TestResult r = aTestRunner.start(args); @@ -149,7 +149,7 @@ public class TestRunner extends BaseTestRunner { * Starts a test run. Analyzes the command line arguments and runs the given * test suite. */ - public TestResult start(String args[]) throws Exception { + public TestResult start(String[] args) throws Exception { String testCase = ""; String method = ""; boolean wait = false; diff --git a/src/main/java/org/junit/Assert.java b/src/main/java/org/junit/Assert.java old mode 100755 new mode 100644 index d7deb06..65bbc9d --- a/src/main/java/org/junit/Assert.java +++ b/src/main/java/org/junit/Assert.java @@ -2,6 +2,7 @@ package org.junit; import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; +import org.junit.function.ThrowingRunnable; import org.junit.internal.ArrayComparisonFailure; import org.junit.internal.ExactComparisonCriteria; import org.junit.internal.InexactComparisonCriteria; @@ -36,7 +37,7 @@ public class Assert { * okay) * @param condition condition to be checked */ - static public void assertTrue(String message, boolean condition) { + public static void assertTrue(String message, boolean condition) { if (!condition) { fail(message); } @@ -48,7 +49,7 @@ public class Assert { * * @param condition condition to be checked */ - static public void assertTrue(boolean condition) { + public static void assertTrue(boolean condition) { assertTrue(null, condition); } @@ -60,7 +61,7 @@ public class Assert { * okay) * @param condition condition to be checked */ - static public void assertFalse(String message, boolean condition) { + public static void assertFalse(String message, boolean condition) { assertTrue(message, !condition); } @@ -70,7 +71,7 @@ public class Assert { * * @param condition condition to be checked */ - static public void assertFalse(boolean condition) { + public static void assertFalse(boolean condition) { assertFalse(null, condition); } @@ -81,7 +82,7 @@ public class Assert { * okay) * @see AssertionError */ - static public void fail(String message) { + public static void fail(String message) { if (message == null) { throw new AssertionError(); } @@ -91,7 +92,7 @@ public class Assert { /** * Fails a test with no message. */ - static public void fail() { + public static void fail() { fail(null); } @@ -106,11 +107,12 @@ public class Assert { * @param expected expected value * @param actual actual value */ - static public void assertEquals(String message, Object expected, + public static void assertEquals(String message, Object expected, Object actual) { if (equalsRegardingNull(expected, actual)) { return; - } else if (expected instanceof String && actual instanceof String) { + } + if (expected instanceof String && actual instanceof String) { String cleanMessage = message == null ? "" : message; throw new ComparisonFailure(cleanMessage, (String) expected, (String) actual); @@ -140,7 +142,7 @@ public class Assert { * @param expected expected value * @param actual the value to check against expected */ - static public void assertEquals(Object expected, Object actual) { + public static void assertEquals(Object expected, Object actual) { assertEquals(null, expected, actual); } @@ -155,7 +157,7 @@ public class Assert { * @param unexpected unexpected value to check * @param actual the value to check against unexpected */ - static public void assertNotEquals(String message, Object unexpected, + public static void assertNotEquals(String message, Object unexpected, Object actual) { if (equalsRegardingNull(unexpected, actual)) { failEquals(message, actual); @@ -171,7 +173,7 @@ public class Assert { * @param unexpected unexpected value to check * @param actual the value to check against unexpected */ - static public void assertNotEquals(Object unexpected, Object actual) { + public static void assertNotEquals(Object unexpected, Object actual) { assertNotEquals(null, unexpected, actual); } @@ -194,7 +196,7 @@ public class Assert { * @param unexpected unexpected value to check * @param actual the value to check against unexpected */ - static public void assertNotEquals(String message, long unexpected, long actual) { + public static void assertNotEquals(String message, long unexpected, long actual) { if (unexpected == actual) { failEquals(message, Long.valueOf(actual)); } @@ -207,7 +209,7 @@ public class Assert { * @param unexpected unexpected value to check * @param actual the value to check against unexpected */ - static public void assertNotEquals(long unexpected, long actual) { + public static void assertNotEquals(long unexpected, long actual) { assertNotEquals(null, unexpected, actual); } @@ -226,7 +228,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertNotEquals(String message, double unexpected, + public static void assertNotEquals(String message, double unexpected, double actual, double delta) { if (!doubleIsDifferent(unexpected, actual, delta)) { failEquals(message, Double.valueOf(actual)); @@ -245,7 +247,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertNotEquals(double unexpected, double actual, double delta) { + public static void assertNotEquals(double unexpected, double actual, double delta) { assertNotEquals(null, unexpected, actual, delta); } @@ -261,7 +263,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertNotEquals(float unexpected, float actual, float delta) { + public static void assertNotEquals(float unexpected, float actual, float delta) { assertNotEquals(null, unexpected, actual, delta); } @@ -297,7 +299,7 @@ public class Assert { public static void assertArrayEquals(Object[] expecteds, Object[] actuals) { assertArrayEquals(null, expecteds, actuals); } - + /** * Asserts that two boolean arrays are equal. If they are not, an * {@link AssertionError} is thrown with the given message. If @@ -312,8 +314,8 @@ public class Assert { public static void assertArrayEquals(String message, boolean[] expecteds, boolean[] actuals) throws ArrayComparisonFailure { internalArrayEquals(message, expecteds, actuals); - } - + } + /** * Asserts that two boolean arrays are equal. If they are not, an * {@link AssertionError} is thrown. If expected and @@ -547,7 +549,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertEquals(String message, double expected, + public static void assertEquals(String message, double expected, double actual, double delta) { if (doubleIsDifferent(expected, actual, delta)) { failNotEquals(message, Double.valueOf(expected), Double.valueOf(actual)); @@ -569,7 +571,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertEquals(String message, float expected, + public static void assertEquals(String message, float expected, float actual, float delta) { if (floatIsDifferent(expected, actual, delta)) { failNotEquals(message, Float.valueOf(expected), Float.valueOf(actual)); @@ -591,14 +593,14 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertNotEquals(String message, float unexpected, + public static void assertNotEquals(String message, float unexpected, float actual, float delta) { if (!floatIsDifferent(unexpected, actual, delta)) { failEquals(message, Float.valueOf(actual)); } } - static private boolean doubleIsDifferent(double d1, double d2, double delta) { + private static boolean doubleIsDifferent(double d1, double d2, double delta) { if (Double.compare(d1, d2) == 0) { return false; } @@ -609,7 +611,7 @@ public class Assert { return true; } - static private boolean floatIsDifferent(float f1, float f2, float delta) { + private static boolean floatIsDifferent(float f1, float f2, float delta) { if (Float.compare(f1, f2) == 0) { return false; } @@ -627,7 +629,7 @@ public class Assert { * @param expected expected long value. * @param actual actual long value */ - static public void assertEquals(long expected, long actual) { + public static void assertEquals(long expected, long actual) { assertEquals(null, expected, actual); } @@ -640,7 +642,7 @@ public class Assert { * @param expected long expected value. * @param actual long actual value */ - static public void assertEquals(String message, long expected, long actual) { + public static void assertEquals(String message, long expected, long actual) { if (expected != actual) { failNotEquals(message, Long.valueOf(expected), Long.valueOf(actual)); } @@ -652,7 +654,7 @@ public class Assert { * instead */ @Deprecated - static public void assertEquals(double expected, double actual) { + public static void assertEquals(double expected, double actual) { assertEquals(null, expected, actual); } @@ -662,7 +664,7 @@ public class Assert { * instead */ @Deprecated - static public void assertEquals(String message, double expected, + public static void assertEquals(String message, double expected, double actual) { fail("Use assertEquals(expected, actual, delta) to compare floating-point numbers"); } @@ -679,7 +681,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - static public void assertEquals(double expected, double actual, double delta) { + public static void assertEquals(double expected, double actual, double delta) { assertEquals(null, expected, actual, delta); } @@ -695,8 +697,7 @@ public class Assert { * actual for which both numbers are still * considered equal. */ - - static public void assertEquals(float expected, float actual, float delta) { + public static void assertEquals(float expected, float actual, float delta) { assertEquals(null, expected, actual, delta); } @@ -708,7 +709,7 @@ public class Assert { * okay) * @param object Object to check or null */ - static public void assertNotNull(String message, Object object) { + public static void assertNotNull(String message, Object object) { assertTrue(message, object != null); } @@ -718,7 +719,7 @@ public class Assert { * * @param object Object to check or null */ - static public void assertNotNull(Object object) { + public static void assertNotNull(Object object) { assertNotNull(null, object); } @@ -730,7 +731,7 @@ public class Assert { * okay) * @param object Object to check or null */ - static public void assertNull(String message, Object object) { + public static void assertNull(String message, Object object) { if (object == null) { return; } @@ -743,11 +744,11 @@ public class Assert { * * @param object Object to check or null */ - static public void assertNull(Object object) { + public static void assertNull(Object object) { assertNull(null, object); } - static private void failNotNull(String message, Object actual) { + private static void failNotNull(String message, Object actual) { String formatted = ""; if (message != null) { formatted = message + " "; @@ -764,7 +765,7 @@ public class Assert { * @param expected the expected object * @param actual the object to compare to expected */ - static public void assertSame(String message, Object expected, Object actual) { + public static void assertSame(String message, Object expected, Object actual) { if (expected == actual) { return; } @@ -778,7 +779,7 @@ public class Assert { * @param expected the expected object * @param actual the object to compare to expected */ - static public void assertSame(Object expected, Object actual) { + public static void assertSame(Object expected, Object actual) { assertSame(null, expected, actual); } @@ -792,7 +793,7 @@ public class Assert { * @param unexpected the object you don't expect * @param actual the object to compare to unexpected */ - static public void assertNotSame(String message, Object unexpected, + public static void assertNotSame(String message, Object unexpected, Object actual) { if (unexpected == actual) { failSame(message); @@ -807,11 +808,11 @@ public class Assert { * @param unexpected the object you don't expect * @param actual the object to compare to unexpected */ - static public void assertNotSame(Object unexpected, Object actual) { + public static void assertNotSame(Object unexpected, Object actual) { assertNotSame(null, unexpected, actual); } - static private void failSame(String message) { + private static void failSame(String message) { String formatted = ""; if (message != null) { formatted = message + " "; @@ -819,7 +820,7 @@ public class Assert { fail(formatted + "expected not same"); } - static private void failNotSame(String message, Object expected, + private static void failNotSame(String message, Object expected, Object actual) { String formatted = ""; if (message != null) { @@ -829,19 +830,19 @@ public class Assert { + ">"); } - static private void failNotEquals(String message, Object expected, + private static void failNotEquals(String message, Object expected, Object actual) { fail(format(message, expected, actual)); } static String format(String message, Object expected, Object actual) { String formatted = ""; - if (message != null && !message.equals("")) { + if (message != null && !"".equals(message)) { formatted = message + " "; } String expectedString = String.valueOf(expected); String actualString = String.valueOf(actual); - if (expectedString.equals(actualString)) { + if (equalsRegardingNull(expectedString, actualString)) { return formatted + "expected: " + formatClassAndValue(expected, expectedString) + " but was: " + formatClassAndValue(actual, actualString); @@ -851,6 +852,11 @@ public class Assert { } } + private static String formatClass(Class value) { + String className = value.getCanonicalName(); + return className == null ? value.getName() : className; + } + private static String formatClassAndValue(Object value, String valueString) { String className = value == null ? "null" : value.getClass().getName(); return className + "<" + valueString + ">"; @@ -917,8 +923,9 @@ public class Assert { * @param matcher an expression, built of {@link Matcher}s, specifying allowed * values * @see org.hamcrest.CoreMatchers - * @see org.hamcrest.MatcherAssert + * @deprecated use {@code org.hamcrest.MatcherAssert.assertThat()} */ + @Deprecated public static void assertThat(T actual, Matcher matcher) { assertThat("", actual, matcher); } @@ -949,10 +956,79 @@ public class Assert { * @param matcher an expression, built of {@link Matcher}s, specifying allowed * values * @see org.hamcrest.CoreMatchers - * @see org.hamcrest.MatcherAssert + * @deprecated use {@code org.hamcrest.MatcherAssert.assertThat()} */ + @Deprecated public static void assertThat(String reason, T actual, Matcher matcher) { MatcherAssert.assertThat(reason, actual, matcher); } + + /** + * Asserts that {@code runnable} throws an exception of type {@code expectedThrowable} when + * executed. If it does, the exception object is returned. If it does not throw an exception, an + * {@link AssertionError} is thrown. If it throws the wrong type of exception, an {@code + * AssertionError} is thrown describing the mismatch; the exception that was actually thrown can + * be obtained by calling {@link AssertionError#getCause}. + * + * @param expectedThrowable the expected type of the exception + * @param runnable a function that is expected to throw an exception when executed + * @return the exception thrown by {@code runnable} + * @since 4.13 + */ + public static T assertThrows(Class expectedThrowable, + ThrowingRunnable runnable) { + return assertThrows(null, expectedThrowable, runnable); + } + + /** + * Asserts that {@code runnable} throws an exception of type {@code expectedThrowable} when + * executed. If it does, the exception object is returned. If it does not throw an exception, an + * {@link AssertionError} is thrown. If it throws the wrong type of exception, an {@code + * AssertionError} is thrown describing the mismatch; the exception that was actually thrown can + * be obtained by calling {@link AssertionError#getCause}. + * + * @param message the identifying message for the {@link AssertionError} (null + * okay) + * @param expectedThrowable the expected type of the exception + * @param runnable a function that is expected to throw an exception when executed + * @return the exception thrown by {@code runnable} + * @since 4.13 + */ + public static T assertThrows(String message, Class expectedThrowable, + ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (Throwable actualThrown) { + if (expectedThrowable.isInstance(actualThrown)) { + @SuppressWarnings("unchecked") T retVal = (T) actualThrown; + return retVal; + } else { + String expected = formatClass(expectedThrowable); + Class actualThrowable = actualThrown.getClass(); + String actual = formatClass(actualThrowable); + if (expected.equals(actual)) { + // There must be multiple class loaders. Add the identity hash code so the message + // doesn't say "expected: java.lang.String ..." + expected += "@" + Integer.toHexString(System.identityHashCode(expectedThrowable)); + actual += "@" + Integer.toHexString(System.identityHashCode(actualThrowable)); + } + String mismatchMessage = buildPrefix(message) + + format("unexpected exception type thrown;", expected, actual); + + // The AssertionError(String, Throwable) ctor is only available on JDK7. + AssertionError assertionError = new AssertionError(mismatchMessage); + assertionError.initCause(actualThrown); + throw assertionError; + } + } + String notThrownMessage = buildPrefix(message) + String + .format("expected %s to be thrown, but nothing was thrown", + formatClass(expectedThrowable)); + throw new AssertionError(notThrownMessage); + } + + private static String buildPrefix(String message) { + return message != null && message.length() != 0 ? message + ": " : ""; + } } diff --git a/src/main/java/org/junit/Assume.java b/src/main/java/org/junit/Assume.java index b7687f7..29b705b 100644 --- a/src/main/java/org/junit/Assume.java +++ b/src/main/java/org/junit/Assume.java @@ -14,7 +14,7 @@ import org.hamcrest.Matcher; * 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. *

- * A good example of using assumptions is in Theories where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case. + * A good example of using assumptions is in Theories where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case. *

* Failed assumptions are usually not logged, because there may be many tests that don't apply to certain * configurations. @@ -29,11 +29,20 @@ import org.hamcrest.Matcher; * *

* - * @see Theories + * @see Theories * * @since 4.4 */ public class Assume { + + /** + * Do not instantiate. + * @deprecated since 4.13. + */ + @Deprecated + public Assume() { + } + /** * If called with an expression evaluating to {@code false}, the test will halt and be ignored. */ @@ -45,7 +54,7 @@ public class Assume { * The inverse of {@link #assumeTrue(boolean)}. */ public static void assumeFalse(boolean b) { - assumeTrue(!b); + assumeThat(b, is(false)); } /** @@ -67,9 +76,11 @@ public class Assume { } /** - * If called with one or more null elements in objects, the test will halt and be ignored. + * If called with a {@code null} array or one or more {@code null} elements in {@code objects}, + * the test will halt and be ignored. */ public static void assumeNotNull(Object... objects) { + assumeThat(objects, notNullValue()); assumeThat(asList(objects), everyItem(notNullValue())); } diff --git a/src/main/java/org/junit/AssumptionViolatedException.java b/src/main/java/org/junit/AssumptionViolatedException.java index e48ddf0..1d62190 100644 --- a/src/main/java/org/junit/AssumptionViolatedException.java +++ b/src/main/java/org/junit/AssumptionViolatedException.java @@ -40,7 +40,7 @@ public class AssumptionViolatedException extends org.junit.internal.AssumptionVi /** * An assumption exception with the given message and a cause. */ - public AssumptionViolatedException(String assumption, Throwable t) { - super(assumption, t); + public AssumptionViolatedException(String message, Throwable t) { + super(message, t); } } diff --git a/src/main/java/org/junit/ClassRule.java b/src/main/java/org/junit/ClassRule.java index 02c40a7..94ee29f 100644 --- a/src/main/java/org/junit/ClassRule.java +++ b/src/main/java/org/junit/ClassRule.java @@ -28,7 +28,10 @@ import java.lang.annotation.Target; * 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. However, Rules defined by fields will always be applied - * before Rules defined by methods. + * after Rules defined by methods, i.e. the Statements returned by the former will + * be executed around those returned by the latter. + * + *

Usage

*

* 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: @@ -79,9 +82,37 @@ import java.lang.annotation.Target; *

* For more information and more examples, see {@link org.junit.rules.TestRule}. * + *

Ordering

+ *

+ * You can use {@link #order()} if you want to have control over the order in + * which the Rules are applied. + * + *

+ * public class ThreeClassRules {
+ *     @ClassRule(order = 0)
+ *     public static LoggingRule outer = new LoggingRule("outer rule");
+ *
+ *     @ClassRule(order = 1)
+ *     public static LoggingRule middle = new LoggingRule("middle rule");
+ *
+ *     @ClassRule(order = 2)
+ *     public static LoggingRule inner = new LoggingRule("inner rule");
+ *
+ *     // ...
+ * }
+ * 
+ * * @since 4.9 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface ClassRule { + + /** + * Specifies the order in which rules are applied. The rules with a higher value are inner. + * + * @since 4.13 + */ + int order() default Rule.DEFAULT_ORDER; + } diff --git a/src/main/java/org/junit/ComparisonFailure.java b/src/main/java/org/junit/ComparisonFailure.java index 9563e61..d1daa86 100644 --- a/src/main/java/org/junit/ComparisonFailure.java +++ b/src/main/java/org/junit/ComparisonFailure.java @@ -21,7 +21,7 @@ public class ComparisonFailure extends AssertionError { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private String fExpected; private String fActual; diff --git a/src/main/java/org/junit/Rule.java b/src/main/java/org/junit/Rule.java index 711235c..9370e94 100644 --- a/src/main/java/org/junit/Rule.java +++ b/src/main/java/org/junit/Rule.java @@ -16,12 +16,14 @@ import java.lang.annotation.Target; * 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 order of fields first, then methods. + * annotated {@link Rule}s on a class, they will be applied in order of methods first, then fields. * 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. 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. + * after Rules defined by methods, i.e. the Statements returned by the former will + * be executed around those returned by the latter. + * + *

Usage

*

* For example, here is a test class that creates a temporary folder before * each test method, and deletes it after each: @@ -61,10 +63,39 @@ import java.lang.annotation.Target; * For more information and more examples, see * {@link org.junit.rules.TestRule}. * + *

Ordering

+ *

+ * You can use {@link #order()} if you want to have control over the order in + * which the Rules are applied. + * + *

+ * public class ThreeRules {
+ *     @Rule(order = 0)
+ *     public LoggingRule outer = new LoggingRule("outer rule");
+ *
+ *     @Rule(order = 1)
+ *     public LoggingRule middle = new LoggingRule("middle rule");
+ *
+ *     @Rule(order = 2)
+ *     public LoggingRule inner = new LoggingRule("inner rule");
+ *
+ *     // ...
+ * }
+ * 
+ * * @since 4.7 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface Rule { -} \ No newline at end of file + int DEFAULT_ORDER = -1; + + /** + * Specifies the order in which rules are applied. The rules with a higher value are inner. + * + * @since 4.13 + */ + int order() default DEFAULT_ORDER; + +} diff --git a/src/main/java/org/junit/Test.java b/src/main/java/org/junit/Test.java index 71ac428..1db6fc7 100644 --- a/src/main/java/org/junit/Test.java +++ b/src/main/java/org/junit/Test.java @@ -1,5 +1,7 @@ package org.junit; +import org.junit.function.ThrowingRunnable; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -23,24 +25,40 @@ import java.lang.annotation.Target; * } * *

- * The Test annotation supports two optional parameters. - * The first, expected, declares that a test method should throw + * The Test annotation supports two optional parameters for + * exception testing and for limiting test execution time. + * + *

Exception Testing

+ *

+ * The parameter expected 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: *

- *    @Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
+ *    @Test(expected=IndexOutOfBoundsException.class)
+ *    public void outOfBounds() {
  *       new ArrayList<Object>().get(1);
  *    }
  * 
- * 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 + * + * Using the parameter expected for exception testing comes with + * some limitations: only the exception's type can be checked and it is not + * possible to precisely specify the code that throws the exception. Therefore + * JUnit 4 has improved its support for exception testing with + * {@link Assert#assertThrows(Class, ThrowingRunnable)} and the + * {@link org.junit.rules.ExpectedException ExpectedException} rule. + * With assertThrows the code that throws the exception can be + * precisely specified. If the exception's message or one of its properties + * should be verified, the ExpectedException rule can be used. Further * information about exception testing can be found at the - * JUnit Wiki. + * JUnit Wiki. + * + *

Timeout

*

- * The second optional parameter, timeout, causes a test to fail if it takes + * The parameter timeout causes a test to fail if it takes * longer than a specified amount of clock time (measured in milliseconds). The following test fails: *

- *    @Test(timeout=100) public void infinity() {
+ *    @Test(timeout=100)
+ *    public void infinity() {
  *       while(true);
  *    }
  * 
@@ -49,7 +67,8 @@ import java.lang.annotation.Target; * following test may or may not fail depending on how the operating system * schedules threads: *
- *    @Test(timeout=100) public void sleep100() {
+ *    @Test(timeout=100)
+ *    public void sleep100() {
  *       Thread.sleep(100);
  *    }
  * 
@@ -66,7 +85,7 @@ import java.lang.annotation.Target; public @interface Test { /** - * Default empty exception + * Default empty exception. */ static class None extends Throwable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/junit/TestCouldNotBeSkippedException.java b/src/main/java/org/junit/TestCouldNotBeSkippedException.java new file mode 100644 index 0000000..4804493 --- /dev/null +++ b/src/main/java/org/junit/TestCouldNotBeSkippedException.java @@ -0,0 +1,19 @@ +package org.junit; + +/** + * Indicates that a test that indicated that it should be skipped could not be skipped. + * This can be thrown if a test uses the methods in {@link Assume} to indicate that + * it should be skipped, but before processing of the test was completed, other failures + * occured. + * + * @see org.junit.Assume + * @since 4.13 + */ +public class TestCouldNotBeSkippedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** Creates an instance using the given assumption failure. */ + public TestCouldNotBeSkippedException(org.junit.internal.AssumptionViolatedException cause) { + super("Test could not be skipped due to other failures", cause); + } +} diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java index 290c180..0c73ed8 100644 --- a/src/main/java/org/junit/experimental/categories/Categories.java +++ b/src/main/java/org/junit/experimental/categories/Categories.java @@ -2,8 +2,10 @@ package org.junit.experimental.categories; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.junit.runner.Description; @@ -76,7 +78,7 @@ import org.junit.runners.model.RunnerBuilder; * * * @version 4.12 - * @see Categories at JUnit wiki + * @see Categories at JUnit wiki */ public class Categories extends Suite { @@ -86,13 +88,13 @@ public class Categories extends Suite { * 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 {}; + Class[] value() default {}; /** * If true, runs tests annotated with any of the categories in * {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with all of the categories. */ - public boolean matchAny() default true; + boolean matchAny() default true; } @Retention(RetentionPolicy.RUNTIME) @@ -101,13 +103,13 @@ public class Categories extends Suite { * 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 {}; + Class[] value() default {}; /** * If true, the tests annotated with any of the categories in {@link ExcludeCategory#value()} * do not run. Otherwise, the tests do not run if and only if annotated with all categories. */ - public boolean matchAny() default true; + boolean matchAny() default true; } public static class CategoryFilter extends Filter { @@ -117,10 +119,7 @@ public class Categories extends Suite { 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); + return new CategoryFilter(matchAny, categories, true, null); } public static CategoryFilter include(Class category) { @@ -132,10 +131,7 @@ public class Categories extends Suite { } public static CategoryFilter exclude(boolean matchAny, Class... categories) { - if (hasNull(categories)) { - throw new NullPointerException("has null category"); - } - return categoryFilter(true, null, matchAny, createSet(categories)); + return new CategoryFilter(true, null, matchAny, categories); } public static CategoryFilter exclude(Class category) { @@ -151,14 +147,30 @@ public class Categories extends Suite { return new CategoryFilter(matchAnyInclusions, inclusions, matchAnyExclusions, exclusions); } + @Deprecated + public CategoryFilter(Class includedCategory, Class excludedCategory) { + includedAny = true; + excludedAny = true; + included = nullableClassToSet(includedCategory); + excluded = nullableClassToSet(excludedCategory); + } + protected CategoryFilter(boolean matchAnyIncludes, Set> includes, - boolean matchAnyExcludes, Set> excludes) { + boolean matchAnyExcludes, Set> excludes) { includedAny = matchAnyIncludes; excludedAny = matchAnyExcludes; included = copyAndRefine(includes); excluded = copyAndRefine(excludes); } + private CategoryFilter(boolean matchAnyIncludes, Class[] inclusions, + boolean matchAnyExcludes, Class[] exclusions) { + includedAny = matchAnyIncludes; + excludedAny = matchAnyExcludes; + included = createSet(inclusions); + excluded = createSet(exclusions); + } + /** * @see #toString() */ @@ -284,23 +296,13 @@ public class Categories extends Suite { } private static Set> copyAndRefine(Set> classes) { - HashSet> c= new HashSet>(); + Set> c= new LinkedHashSet>(); 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 { @@ -315,7 +317,6 @@ public class Categories extends Suite { } catch (NoTestsRemainException e) { throw new InitializationError(e); } - assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); } private static Set> getIncludedCategory(Class klass) { @@ -338,34 +339,6 @@ public class Categories extends Suite { 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> assigns, Class to) { for (final Class from : assigns) { if (to.isAssignableFrom(from)) { @@ -375,11 +348,28 @@ public class Categories extends Suite { return false; } - private static Set> createSet(Class... t) { - final Set> set= new HashSet>(); - if (t != null) { - Collections.addAll(set, t); + private static Set> createSet(Class[] classes) { + // Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.12 + // for include(boolean, Class...) and exclude(boolean, Class...) + if (classes == null || classes.length == 0) { + return Collections.emptySet(); + } + for (Class category : classes) { + if (category == null) { + throw new NullPointerException("has null category"); + } } - return set; + + return classes.length == 1 + ? Collections.>singleton(classes[0]) + : new LinkedHashSet>(Arrays.asList(classes)); + } + + private static Set> nullableClassToSet(Class nullableClass) { + // Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.11 + // for CategoryFilter(Class includedCategory, Class excludedCategory) + return nullableClass == null + ? Collections.>emptySet() + : Collections.>singleton(nullableClass); } } diff --git a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java index cee1ae7..e9bdab7 100644 --- a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java +++ b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java @@ -37,7 +37,11 @@ abstract class CategoryFilterFactory implements FilterFactory { List> categoryClasses = new ArrayList>(); for (String category : categories.split(",")) { - Class categoryClass = Classes.getClass(category); + /* + * Load the category class using the context class loader. + * If there is no context class loader, use the class loader for this class. + */ + Class categoryClass = Classes.getClass(category, getClass()); categoryClasses.add(categoryClass); } diff --git a/src/main/java/org/junit/experimental/max/MaxHistory.java b/src/main/java/org/junit/experimental/max/MaxHistory.java index 45a4033..ab7443f 100644 --- a/src/main/java/org/junit/experimental/max/MaxHistory.java +++ b/src/main/java/org/junit/experimental/max/MaxHistory.java @@ -64,7 +64,7 @@ public class MaxHistory implements Serializable { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final Map fDurations = new HashMap(); private final Map fFailureTimestamps = new HashMap(); @@ -75,10 +75,15 @@ public class MaxHistory implements Serializable { } private void save() throws IOException { - ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream( - fHistoryStore)); - stream.writeObject(this); - stream.close(); + ObjectOutputStream stream = null; + try { + stream = new ObjectOutputStream(new FileOutputStream(fHistoryStore)); + stream.writeObject(this); + } finally { + if (stream != null) { + stream.close(); + } + } } Long getFailureTimestamp(Description key) { diff --git a/src/main/java/org/junit/experimental/results/PrintableResult.java b/src/main/java/org/junit/experimental/results/PrintableResult.java index ffe22f0..0f67766 100644 --- a/src/main/java/org/junit/experimental/results/PrintableResult.java +++ b/src/main/java/org/junit/experimental/results/PrintableResult.java @@ -54,6 +54,15 @@ public class PrintableResult { return result.getFailures().size(); } + /** + * Returns the failures in this result. + * + * @since 4.13 + */ + public List failures() { + return result.getFailures(); + } + @Override public String toString() { ByteArrayOutputStream stream = new ByteArrayOutputStream(); diff --git a/src/main/java/org/junit/experimental/results/ResultMatchers.java b/src/main/java/org/junit/experimental/results/ResultMatchers.java index cf58f1b..92f2e6b 100644 --- a/src/main/java/org/junit/experimental/results/ResultMatchers.java +++ b/src/main/java/org/junit/experimental/results/ResultMatchers.java @@ -14,6 +14,15 @@ import org.hamcrest.TypeSafeMatcher; * */ public class ResultMatchers { + + /** + * Do not instantiate. + * @deprecated will be private soon. + */ + @Deprecated + public ResultMatchers() { + } + /** * Matches if the tests are all successful */ @@ -52,14 +61,34 @@ public class ResultMatchers { }; } + /** + * Matches if the result has exactly one failure matching the given matcher. + * + * @since 4.13 + */ + public static Matcher hasSingleFailureMatching(final Matcher matcher) { + return new TypeSafeMatcher() { + @Override + public boolean matchesSafely(PrintableResult item) { + return item.failureCount() == 1 && matcher.matches(item.failures().get(0).getException()); + } + + public void describeTo(Description description) { + description.appendText("has failure with exception matching "); + matcher.describeTo(description); + } + }; + } + /** * Matches if the result has one or more failures, and at least one of them * contains {@code string} */ public static Matcher hasFailureContaining(final String string) { - return new BaseMatcher() { - public boolean matches(Object item) { - return item.toString().contains(string); + return new TypeSafeMatcher() { + @Override + public boolean matchesSafely(PrintableResult item) { + return item.failureCount() > 0 && item.toString().contains(string); } public void describeTo(Description description) { diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java index 15b5d95..846a39e 100644 --- a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java +++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java @@ -17,7 +17,7 @@ import java.lang.annotation.Target; * * In addition, annotations themselves can be annotated with * @ParametersSuppliedBy, and then used similarly. ParameterSuppliedBy - * annotations on parameters are detected by searching up this heirarchy such + * annotations on parameters are detected by searching up this hierarchy such * that these act as syntactic sugar, making: * *
diff --git a/src/main/java/org/junit/experimental/theories/Theories.java b/src/main/java/org/junit/experimental/theories/Theories.java
index 817f553..ac88a36 100644
--- a/src/main/java/org/junit/experimental/theories/Theories.java
+++ b/src/main/java/org/junit/experimental/theories/Theories.java
@@ -51,11 +51,11 @@ import org.junit.runners.model.TestClass;
  *      }
  * }
  * 
- * This makes it clear that the user's filename should be included in the config file name, + * This makes it clear that the username 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. UserTest will attempt to run filenameIncludesUsername 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. + * assumptions pass, but an assertion fails, the test fails. If no parameters can be found that satisfy all assumptions, the test fails. *

* 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. @@ -73,6 +73,11 @@ public class Theories extends BlockJUnit4ClassRunner { super(klass); } + /** @since 4.13 */ + protected Theories(TestClass testClass) throws InitializationError { + super(testClass); + } + @Override protected void collectInitializationErrors(List errors) { super.collectInitializationErrors(errors); @@ -215,7 +220,7 @@ public class Theories extends BlockJUnit4ClassRunner { protected void runWithCompleteAssignment(final Assignments complete) throws Throwable { - new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) { + new BlockJUnit4ClassRunner(getTestClass()) { @Override protected void collectInitializationErrors( List errors) { 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 a94c8a5..6626797 100644 --- a/src/main/java/org/junit/experimental/theories/internal/Assignments.java +++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java @@ -47,7 +47,7 @@ public class Assignments { } public boolean isComplete() { - return unassigned.size() == 0; + return unassigned.isEmpty(); } public ParameterSignature nextUnassigned() { @@ -55,11 +55,10 @@ public class Assignments { } public Assignments assignNext(PotentialAssignment source) { - List assigned = new ArrayList( - this.assigned); - assigned.add(source); + List potentialAssignments = new ArrayList(assigned); + potentialAssignments.add(source); - return new Assignments(assigned, unassigned.subList(1, + return new Assignments(potentialAssignments, unassigned.subList(1, unassigned.size()), clazz); } @@ -77,7 +76,7 @@ public class Assignments { ParameterSignature unassigned = nextUnassigned(); List assignments = getSupplier(unassigned).getValueSources(unassigned); - if (assignments.size() == 0) { + if (assignments.isEmpty()) { assignments = generateAssignmentsFromTypeAlone(unassigned); } diff --git a/src/main/java/org/junit/function/ThrowingRunnable.java b/src/main/java/org/junit/function/ThrowingRunnable.java new file mode 100644 index 0000000..d0eb782 --- /dev/null +++ b/src/main/java/org/junit/function/ThrowingRunnable.java @@ -0,0 +1,14 @@ +package org.junit.function; + +/** + * This interface facilitates the use of + * {@link org.junit.Assert#assertThrows(Class, ThrowingRunnable)} from Java 8. It allows method + * references to void methods (that declare checked exceptions) to be passed directly into + * {@code assertThrows} + * without wrapping. It is not meant to be implemented directly. + * + * @since 4.13 + */ +public interface ThrowingRunnable { + void run() throws Throwable; +} diff --git a/src/main/java/org/junit/internal/ArrayComparisonFailure.java b/src/main/java/org/junit/internal/ArrayComparisonFailure.java index 8627d6e..d300e7e 100644 --- a/src/main/java/org/junit/internal/ArrayComparisonFailure.java +++ b/src/main/java/org/junit/internal/ArrayComparisonFailure.java @@ -16,11 +16,12 @@ public class ArrayComparisonFailure extends AssertionError { /* * 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 + * serialization compatibility. + * See https://github.com/junit-team/junit4/issues/976 */ private final List fIndices = new ArrayList(); private final String fMessage; + private final AssertionError fCause; /** * Construct a new ArrayComparisonFailure with an error text and the array's @@ -32,7 +33,8 @@ public class ArrayComparisonFailure extends AssertionError { */ public ArrayComparisonFailure(String message, AssertionError cause, int index) { this.fMessage = message; - initCause(cause); + this.fCause = cause; + initCause(fCause); addDimension(index); } @@ -40,6 +42,11 @@ public class ArrayComparisonFailure extends AssertionError { fIndices.add(0, index); } + @Override + public synchronized Throwable getCause() { + return super.getCause() == null ? fCause : super.getCause(); + } + @Override public String getMessage() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/junit/internal/AssumptionViolatedException.java b/src/main/java/org/junit/internal/AssumptionViolatedException.java index 880d73f..0e79b56 100644 --- a/src/main/java/org/junit/internal/AssumptionViolatedException.java +++ b/src/main/java/org/junit/internal/AssumptionViolatedException.java @@ -1,5 +1,8 @@ package org.junit.internal; +import java.io.IOException; +import java.io.ObjectOutputStream; + import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.SelfDescribing; @@ -18,7 +21,7 @@ public class AssumptionViolatedException extends RuntimeException implements Sel /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final String fAssumption; private final boolean fValueMatcher; @@ -108,4 +111,29 @@ public class AssumptionViolatedException extends RuntimeException implements Sel } } } + + /** + * Override default Java object serialization to correctly deal with potentially unserializable matchers or values. + * By not implementing readObject, we assure ourselves of backwards compatibility and compatibility with the + * standard way of Java serialization. + * + * @param objectOutputStream The outputStream to write our representation to + * @throws IOException When serialization fails + */ + private void writeObject(ObjectOutputStream objectOutputStream) throws IOException { + ObjectOutputStream.PutField putField = objectOutputStream.putFields(); + putField.put("fAssumption", fAssumption); + putField.put("fValueMatcher", fValueMatcher); + + // We have to wrap the matcher into a serializable form. + putField.put("fMatcher", SerializableMatcherDescription.asSerializableMatcher(fMatcher)); + + // We have to wrap the value inside a non-String class (instead of serializing the String value directly) as + // A Description will handle a String and non-String object differently (1st is surrounded by '"' while the + // latter will be surrounded by '<' '>'. Wrapping it makes sure that the description of a serialized and + // non-serialized instance produce the exact same description + putField.put("fValue", SerializableValueDescription.asSerializableValue(fValue)); + + objectOutputStream.writeFields(); + } } diff --git a/src/main/java/org/junit/internal/Checks.java b/src/main/java/org/junit/internal/Checks.java new file mode 100644 index 0000000..9724947 --- /dev/null +++ b/src/main/java/org/junit/internal/Checks.java @@ -0,0 +1,37 @@ +package org.junit.internal; + +/** @since 4.13 */ +public final class Checks { + + private Checks() {} + + /** + * Checks that the given value is not {@code null}. + * + * @param value object reference to check + * @return the passed-in value, if not {@code null} + * @throws NullPointerException if {@code value} is {@code null} + */ + public static T notNull(T value) { + if (value == null) { + throw new NullPointerException(); + } + return value; + } + + /** + * Checks that the given value is not {@code null}, using the given message + * as the exception message if an exception is thrown. + * + * @param value object reference to check + * @param message message to use if {@code value} is {@code null} + * @return the passed-in value, if not {@code null} + * @throws NullPointerException if {@code value} is {@code null} + */ + public static T notNull(T value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + return value; + } +} diff --git a/src/main/java/org/junit/internal/Classes.java b/src/main/java/org/junit/internal/Classes.java index 154603d..e8404f6 100644 --- a/src/main/java/org/junit/internal/Classes.java +++ b/src/main/java/org/junit/internal/Classes.java @@ -6,13 +6,39 @@ import static java.lang.Thread.currentThread; * Miscellaneous functions dealing with classes. */ public class Classes { + + /** + * Do not instantiate. + * @deprecated will be private soon. + */ + @Deprecated + public Classes() { + } + /** * Returns Class.forName for {@code className} using the current thread's class loader. + * If the current thread does not have a class loader, falls back to the class loader for + * {@link Classes}. * * @param className Name of the class. * @throws ClassNotFoundException */ public static Class getClass(String className) throws ClassNotFoundException { - return Class.forName(className, true, currentThread().getContextClassLoader()); + return getClass(className, Classes.class); + } + + /** + * Returns Class.forName for {@code className} using the current thread's class loader. + * If the current thread does not have a class loader, falls back to the class loader for the + * passed-in class. + * + * @param className Name of the class. + * @param callingClass Class that is requesting a the class + * @throws ClassNotFoundException + * @since 4.13 + */ + public static Class getClass(String className, Class callingClass) throws ClassNotFoundException { + ClassLoader classLoader = currentThread().getContextClassLoader(); + return Class.forName(className, true, classLoader == null ? callingClass.getClassLoader() : classLoader); } } diff --git a/src/main/java/org/junit/internal/ComparisonCriteria.java b/src/main/java/org/junit/internal/ComparisonCriteria.java index e6d49a4..ed1c674 100644 --- a/src/main/java/org/junit/internal/ComparisonCriteria.java +++ b/src/main/java/org/junit/internal/ComparisonCriteria.java @@ -25,6 +25,11 @@ public abstract class ComparisonCriteria { */ public void arrayEquals(String message, Object expecteds, Object actuals) throws ArrayComparisonFailure { + arrayEquals(message, expecteds, actuals, true); + } + + private void arrayEquals(String message, Object expecteds, Object actuals, boolean outer) + 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 @@ -34,19 +39,37 @@ public abstract class ComparisonCriteria { } String header = message == null ? "" : message + ": "; - int expectedsLength = assertArraysAreSameLength(expecteds, - actuals, header); + // Only include the user-provided message in the outer exception. + String exceptionMessage = outer ? header : ""; + + if (expecteds == null) { + Assert.fail(exceptionMessage + "expected array was null"); + } + if (actuals == null) { + Assert.fail(exceptionMessage + "actual array was null"); + } + + int actualsLength = Array.getLength(actuals); + int expectedsLength = Array.getLength(expecteds); + if (actualsLength != expectedsLength) { + header += "array lengths differed, expected.length=" + + expectedsLength + " actual.length=" + actualsLength + "; "; + } + int prefixLength = Math.min(actualsLength, expectedsLength); - for (int i = 0; i < expectedsLength; i++) { + for (int i = 0; i < prefixLength; i++) { Object expected = Array.get(expecteds, i); Object actual = Array.get(actuals, i); if (isArray(expected) && isArray(actual)) { try { - arrayEquals(message, expected, actual); + arrayEquals(message, expected, actual, false); } catch (ArrayComparisonFailure e) { e.addDimension(i); throw e; + } catch (AssertionError e) { + // Array lengths differed. + throw new ArrayComparisonFailure(header, e, i); } } else { try { @@ -56,27 +79,53 @@ public abstract class ComparisonCriteria { } } } - } - private boolean isArray(Object expected) { - return expected != null && expected.getClass().isArray(); + if (actualsLength != expectedsLength) { + Object expected = getToStringableArrayElement(expecteds, expectedsLength, prefixLength); + Object actual = getToStringableArrayElement(actuals, actualsLength, prefixLength); + try { + Assert.assertEquals(expected, actual); + } catch (AssertionError e) { + throw new ArrayComparisonFailure(header, e, prefixLength); + } + } } - 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"); + private static final Object END_OF_ARRAY_SENTINEL = objectWithToString("end of array"); + + private Object getToStringableArrayElement(Object array, int length, int index) { + if (index < length) { + Object element = Array.get(array, index); + if (isArray(element)) { + return objectWithToString(componentTypeName(element.getClass()) + "[" + Array.getLength(element) + "]"); + } else { + return element; + } + } else { + return END_OF_ARRAY_SENTINEL; } - 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); + } + + private static Object objectWithToString(final String string) { + return new Object() { + @Override + public String toString() { + return string; + } + }; + } + + private String componentTypeName(Class arrayClass) { + Class componentType = arrayClass.getComponentType(); + if (componentType.isArray()) { + return componentTypeName(componentType) + "[]"; + } else { + return componentType.getName(); } - return expectedsLength; + } + + private boolean isArray(Object expected) { + return expected != null && expected.getClass().isArray(); } protected abstract void assertElementsEqual(Object expected, Object actual); diff --git a/src/main/java/org/junit/internal/SerializableMatcherDescription.java b/src/main/java/org/junit/internal/SerializableMatcherDescription.java new file mode 100644 index 0000000..e036557 --- /dev/null +++ b/src/main/java/org/junit/internal/SerializableMatcherDescription.java @@ -0,0 +1,47 @@ +package org.junit.internal; + +import java.io.Serializable; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.StringDescription; + +/** + * This class exists solely to provide a serializable description of a matcher to be serialized as a field in + * {@link AssumptionViolatedException}. Being a {@link Throwable}, it is required to be {@link Serializable}, but most + * implementations of {@link Matcher} are not. This class works around that limitation as + * {@link AssumptionViolatedException} only every uses the description of the {@link Matcher}, while still retaining + * backwards compatibility with classes compiled against its class signature before 4.14 and/or deserialization of + * previously serialized instances. + */ +class SerializableMatcherDescription extends BaseMatcher implements Serializable { + + private final String matcherDescription; + + private SerializableMatcherDescription(Matcher matcher) { + matcherDescription = StringDescription.asString(matcher); + } + + public boolean matches(Object o) { + throw new UnsupportedOperationException("This Matcher implementation only captures the description"); + } + + public void describeTo(Description description) { + description.appendText(matcherDescription); + } + + /** + * Factory method that checks to see if the matcher is already serializable. + * @param matcher the matcher to make serializable + * @return The provided matcher if it is null or already serializable, + * the SerializableMatcherDescription representation of it if it is not. + */ + static Matcher asSerializableMatcher(Matcher matcher) { + if (matcher == null || matcher instanceof Serializable) { + return matcher; + } else { + return new SerializableMatcherDescription(matcher); + } + } +} diff --git a/src/main/java/org/junit/internal/SerializableValueDescription.java b/src/main/java/org/junit/internal/SerializableValueDescription.java new file mode 100644 index 0000000..4d055d7 --- /dev/null +++ b/src/main/java/org/junit/internal/SerializableValueDescription.java @@ -0,0 +1,38 @@ +package org.junit.internal; + +import java.io.Serializable; + +/** + * This class exists solely to provide a serializable description of a value to be serialized as a field in + * {@link AssumptionViolatedException}. Being a {@link Throwable}, it is required to be {@link Serializable}, but a + * value of type Object provides no guarantee to be serializable. This class works around that limitation as + * {@link AssumptionViolatedException} only every uses the string representation of the value, while still retaining + * backwards compatibility with classes compiled against its class signature before 4.14 and/or deserialization of + * previously serialized instances. + */ +class SerializableValueDescription implements Serializable { + private final String value; + + private SerializableValueDescription(Object value) { + this.value = String.valueOf(value); + } + + /** + * Factory method that checks to see if the value is already serializable. + * @param value the value to make serializable + * @return The provided value if it is null or already serializable, + * the SerializableValueDescription representation of it if it is not. + */ + static Object asSerializableValue(Object value) { + if (value == null || value instanceof Serializable) { + return value; + } else { + return new SerializableValueDescription(value); + } + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/org/junit/internal/TextListener.java b/src/main/java/org/junit/internal/TextListener.java index 9aa56c7..d548aeb 100644 --- a/src/main/java/org/junit/internal/TextListener.java +++ b/src/main/java/org/junit/internal/TextListener.java @@ -58,7 +58,7 @@ public class TextListener extends RunListener { protected void printFailures(Result result) { List failures = result.getFailures(); - if (failures.size() == 0) { + if (failures.isEmpty()) { return; } if (failures.size() == 1) { @@ -74,7 +74,7 @@ public class TextListener extends RunListener { protected void printFailure(Failure each, String prefix) { getWriter().println(prefix + ") " + each.getTestHeader()); - getWriter().print(each.getTrace()); + getWriter().print(each.getTrimmedTrace()); } protected void printFooter(Result result) { diff --git a/src/main/java/org/junit/internal/Throwables.java b/src/main/java/org/junit/internal/Throwables.java index 86dceef..3f0f7a3 100644 --- a/src/main/java/org/junit/internal/Throwables.java +++ b/src/main/java/org/junit/internal/Throwables.java @@ -1,5 +1,17 @@ package org.junit.internal; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * Miscellaneous functions dealing with {@code Throwable}. * @@ -39,4 +51,223 @@ public final class Throwables { private static void rethrow(Throwable e) throws T { throw (T) e; } + + /** + * Returns the stacktrace of the given Throwable as a String. + * + * @since 4.13 + */ + public static String getStacktrace(Throwable exception) { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + exception.printStackTrace(writer); + return stringWriter.toString(); + } + + /** + * Gets a trimmed version of the stack trace of the given exception. Stack trace + * elements that are below the test method are filtered out. + * + * @return a trimmed stack trace, or the original trace if trimming wasn't possible + */ + public static String getTrimmedStackTrace(Throwable exception) { + List trimmedStackTraceLines = getTrimmedStackTraceLines(exception); + if (trimmedStackTraceLines.isEmpty()) { + return getFullStackTrace(exception); + } + + StringBuilder result = new StringBuilder(exception.toString()); + appendStackTraceLines(trimmedStackTraceLines, result); + appendStackTraceLines(getCauseStackTraceLines(exception), result); + return result.toString(); + } + + private static List getTrimmedStackTraceLines(Throwable exception) { + List stackTraceElements = Arrays.asList(exception.getStackTrace()); + int linesToInclude = stackTraceElements.size(); + + State state = State.PROCESSING_OTHER_CODE; + for (StackTraceElement stackTraceElement : asReversedList(stackTraceElements)) { + state = state.processStackTraceElement(stackTraceElement); + if (state == State.DONE) { + List trimmedLines = new ArrayList(linesToInclude + 2); + trimmedLines.add(""); + for (StackTraceElement each : stackTraceElements.subList(0, linesToInclude)) { + trimmedLines.add("\tat " + each); + } + if (exception.getCause() != null) { + trimmedLines.add("\t... " + (stackTraceElements.size() - trimmedLines.size()) + " trimmed"); + } + return trimmedLines; + } + linesToInclude--; + } + return Collections.emptyList(); + } + + private static final Method getSuppressed = initGetSuppressed(); + + private static Method initGetSuppressed() { + try { + return Throwable.class.getMethod("getSuppressed"); + } catch (Throwable e) { + return null; + } + } + + private static boolean hasSuppressed(Throwable exception) { + if (getSuppressed == null) { + return false; + } + try { + Throwable[] suppressed = (Throwable[]) getSuppressed.invoke(exception); + return suppressed.length != 0; + } catch (Throwable e) { + return false; + } + } + + private static List getCauseStackTraceLines(Throwable exception) { + if (exception.getCause() != null || hasSuppressed(exception)) { + String fullTrace = getFullStackTrace(exception); + BufferedReader reader = new BufferedReader( + new StringReader(fullTrace.substring(exception.toString().length()))); + List causedByLines = new ArrayList(); + + try { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("Caused by: ") || line.trim().startsWith("Suppressed: ")) { + causedByLines.add(line); + while ((line = reader.readLine()) != null) { + causedByLines.add(line); + } + return causedByLines; + } + } + } catch (IOException e) { + // We should never get here, because we are reading from a StringReader + } + } + + return Collections.emptyList(); + } + + private static String getFullStackTrace(Throwable exception) { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + exception.printStackTrace(writer); + return stringWriter.toString(); + } + + private static void appendStackTraceLines( + List stackTraceLines, StringBuilder destBuilder) { + for (String stackTraceLine : stackTraceLines) { + destBuilder.append(String.format("%s%n", stackTraceLine)); + } + } + + private static List asReversedList(final List list) { + return new AbstractList() { + + @Override + public T get(int index) { + return list.get(list.size() - index - 1); + } + + @Override + public int size() { + return list.size(); + } + }; + } + + private enum State { + PROCESSING_OTHER_CODE { + @Override public State processLine(String methodName) { + if (isTestFrameworkMethod(methodName)) { + return PROCESSING_TEST_FRAMEWORK_CODE; + } + return this; + } + }, + PROCESSING_TEST_FRAMEWORK_CODE { + @Override public State processLine(String methodName) { + if (isReflectionMethod(methodName)) { + return PROCESSING_REFLECTION_CODE; + } else if (isTestFrameworkMethod(methodName)) { + return this; + } + return PROCESSING_OTHER_CODE; + } + }, + PROCESSING_REFLECTION_CODE { + @Override public State processLine(String methodName) { + if (isReflectionMethod(methodName)) { + return this; + } else if (isTestFrameworkMethod(methodName)) { + // This is here to handle TestCase.runBare() calling TestCase.runTest(). + return PROCESSING_TEST_FRAMEWORK_CODE; + } + return DONE; + } + }, + DONE { + @Override public State processLine(String methodName) { + return this; + } + }; + + /** Processes a stack trace element method name, possibly moving to a new state. */ + protected abstract State processLine(String methodName); + + /** Processes a stack trace element, possibly moving to a new state. */ + public final State processStackTraceElement(StackTraceElement element) { + return processLine(element.getClassName() + "." + element.getMethodName() + "()"); + } + } + + private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = { + "org.junit.runner.", + "org.junit.runners.", + "org.junit.experimental.runners.", + "org.junit.internal.", + "junit.extensions", + "junit.framework", + "junit.runner", + "junit.textui", + }; + + private static final String[] TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES = { + "org.junit.internal.StackTracesTest", + }; + + private static boolean isTestFrameworkMethod(String methodName) { + return isMatchingMethod(methodName, TEST_FRAMEWORK_METHOD_NAME_PREFIXES) && + !isMatchingMethod(methodName, TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES); + } + + private static final String[] REFLECTION_METHOD_NAME_PREFIXES = { + "sun.reflect.", + "java.lang.reflect.", + "jdk.internal.reflect.", + "org.junit.rules.RunRules.(", + "org.junit.rules.RunRules.applyAll(", // calls TestRules + "org.junit.runners.RuleContainer.apply(", // calls MethodRules & TestRules + "junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown() + }; + + private static boolean isReflectionMethod(String methodName) { + return isMatchingMethod(methodName, REFLECTION_METHOD_NAME_PREFIXES); + } + + private static boolean isMatchingMethod(String methodName, String[] methodNamePrefixes) { + for (String methodNamePrefix : methodNamePrefixes) { + if (methodName.startsWith(methodNamePrefix)) { + return true; + } + } + + return false; + } } diff --git a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java index d86ec95..8704a54 100644 --- a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java +++ b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java @@ -9,6 +9,17 @@ import org.junit.runners.model.RunnerBuilder; public class AllDefaultPossibilitiesBuilder extends RunnerBuilder { private final boolean canUseSuiteMethod; + /** + * @since 4.13 + */ + public AllDefaultPossibilitiesBuilder() { + canUseSuiteMethod = true; + } + + /** + * @deprecated used {@link #AllDefaultPossibilitiesBuilder()}. + */ + @Deprecated public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) { this.canUseSuiteMethod = canUseSuiteMethod; } diff --git a/src/main/java/org/junit/internal/builders/JUnit4Builder.java b/src/main/java/org/junit/internal/builders/JUnit4Builder.java index 6a00678..7959e75 100644 --- a/src/main/java/org/junit/internal/builders/JUnit4Builder.java +++ b/src/main/java/org/junit/internal/builders/JUnit4Builder.java @@ -1,12 +1,12 @@ package org.junit.internal.builders; import org.junit.runner.Runner; -import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.JUnit4; import org.junit.runners.model.RunnerBuilder; public class JUnit4Builder extends RunnerBuilder { @Override public Runner runnerForClass(Class testClass) throws Throwable { - return new BlockJUnit4ClassRunner(testClass); + return new JUnit4(testClass); } -} \ No newline at end of file +} diff --git a/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java b/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java new file mode 100644 index 0000000..477b150 --- /dev/null +++ b/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java @@ -0,0 +1,21 @@ +package org.junit.internal.management; + +import java.util.Collections; +import java.util.List; + +/** + * No-op implementation of RuntimeMXBean when the platform doesn't provide it. + */ +class FakeRuntimeMXBean implements RuntimeMXBean { + + /** + * {@inheritDoc} + * + *

Always returns an empty list. + */ + public List getInputArguments() { + return Collections.emptyList(); + } + +} + diff --git a/src/main/java/org/junit/internal/management/FakeThreadMXBean.java b/src/main/java/org/junit/internal/management/FakeThreadMXBean.java new file mode 100644 index 0000000..893f2e3 --- /dev/null +++ b/src/main/java/org/junit/internal/management/FakeThreadMXBean.java @@ -0,0 +1,27 @@ +package org.junit.internal.management; + +/** + * No-op implementation of ThreadMXBean when the platform doesn't provide it. + */ +final class FakeThreadMXBean implements ThreadMXBean { + + /** + * {@inheritDoc} + * + *

Always throws an {@link UnsupportedOperationException} + */ + public long getThreadCpuTime(long id) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + *

Always returns false. + */ + public boolean isThreadCpuTimeSupported() { + return false; + } + +} + diff --git a/src/main/java/org/junit/internal/management/ManagementFactory.java b/src/main/java/org/junit/internal/management/ManagementFactory.java new file mode 100644 index 0000000..5be1447 --- /dev/null +++ b/src/main/java/org/junit/internal/management/ManagementFactory.java @@ -0,0 +1,77 @@ +package org.junit.internal.management; + +import org.junit.internal.Classes; + +import java.lang.reflect.InvocationTargetException; + +/** + * Reflective wrapper around {@link java.lang.management.ManagementFactory} + */ +public class ManagementFactory { + private static final class FactoryHolder { + private static final Class MANAGEMENT_FACTORY_CLASS; + + static { + Class managementFactoryClass = null; + try { + managementFactoryClass = Classes.getClass("java.lang.management.ManagementFactory"); + } catch (ClassNotFoundException e) { + // do nothing, managementFactoryClass will be none on failure + } + MANAGEMENT_FACTORY_CLASS = managementFactoryClass; + } + + static Object getBeanObject(String methodName) { + if (MANAGEMENT_FACTORY_CLASS != null) { + try { + return MANAGEMENT_FACTORY_CLASS.getMethod(methodName).invoke(null); + } catch (IllegalAccessException e) { + // fallthrough + } catch (IllegalArgumentException e) { + // fallthrough + } catch (InvocationTargetException e) { + // fallthrough + } catch (NoSuchMethodException e) { + // fallthrough + } catch (SecurityException e) { + // fallthrough + } + } + return null; + } + } + + private static final class RuntimeHolder { + private static final RuntimeMXBean RUNTIME_MX_BEAN = + getBean(FactoryHolder.getBeanObject("getRuntimeMXBean")); + + private static final RuntimeMXBean getBean(Object runtimeMxBean) { + return runtimeMxBean != null + ? new ReflectiveRuntimeMXBean(runtimeMxBean) : new FakeRuntimeMXBean(); + } + } + + private static final class ThreadHolder { + private static final ThreadMXBean THREAD_MX_BEAN = + getBean(FactoryHolder.getBeanObject("getThreadMXBean")); + + private static final ThreadMXBean getBean(Object threadMxBean) { + return threadMxBean != null + ? new ReflectiveThreadMXBean(threadMxBean) : new FakeThreadMXBean(); + } + } + + /** + * @see java.lang.management.ManagementFactory#getRuntimeMXBean() + */ + public static RuntimeMXBean getRuntimeMXBean() { + return RuntimeHolder.RUNTIME_MX_BEAN; + } + + /** + * @see java.lang.management.ManagementFactory#getThreadMXBean() + */ + public static ThreadMXBean getThreadMXBean() { + return ThreadHolder.THREAD_MX_BEAN; + } +} diff --git a/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java b/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java new file mode 100644 index 0000000..289587a --- /dev/null +++ b/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java @@ -0,0 +1,61 @@ +package org.junit.internal.management; + +import org.junit.internal.Classes; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of {@link RuntimeMXBean} using the JVM reflectively. + */ +final class ReflectiveRuntimeMXBean implements RuntimeMXBean { + private final Object runtimeMxBean; + + private static final class Holder { + private static final Method getInputArgumentsMethod; + static { + Method inputArguments = null; + try { + Class threadMXBeanClass = Classes.getClass("java.lang.management.RuntimeMXBean"); + inputArguments = threadMXBeanClass.getMethod("getInputArguments"); + } catch (ClassNotFoundException e) { + // do nothing, input arguments will be null on failure + } catch (NoSuchMethodException e) { + // do nothing, input arguments will be null on failure + } catch (SecurityException e) { + // do nothing, input arguments will be null on failure + } + getInputArgumentsMethod = inputArguments; + } + } + + ReflectiveRuntimeMXBean(Object runtimeMxBean) { + super(); + this.runtimeMxBean = runtimeMxBean; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List getInputArguments() { + if (Holder.getInputArgumentsMethod != null) { + try { + return (List) Holder.getInputArgumentsMethod.invoke(runtimeMxBean); + } catch (ClassCastException e) { // no multi-catch with source level 6 + // fallthrough + } catch (IllegalAccessException e) { + // fallthrough + } catch (IllegalArgumentException e) { + // fallthrough + } catch (InvocationTargetException e) { + // fallthrough + } + } + return Collections.emptyList(); + } + +} + diff --git a/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java b/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java new file mode 100644 index 0000000..bc741be --- /dev/null +++ b/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java @@ -0,0 +1,92 @@ +package org.junit.internal.management; + +import org.junit.internal.Classes; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Implementation of {@link ThreadMXBean} using the JVM reflectively. + */ +final class ReflectiveThreadMXBean implements ThreadMXBean { + private final Object threadMxBean; + + + private static final class Holder { + static final Method getThreadCpuTimeMethod; + static final Method isThreadCpuTimeSupportedMethod; + + private static final String FAILURE_MESSAGE = "Unable to access ThreadMXBean"; + + static { + Method threadCpuTime = null; + Method threadCpuTimeSupported = null; + try { + Class threadMXBeanClass = Classes.getClass("java.lang.management.ThreadMXBean"); + threadCpuTime = threadMXBeanClass.getMethod("getThreadCpuTime", long.class); + threadCpuTimeSupported = threadMXBeanClass.getMethod("isThreadCpuTimeSupported"); + } catch (ClassNotFoundException e) { + // do nothing, the methods will be null on failure + } catch (NoSuchMethodException e) { + // do nothing, the methods will be null on failure + } catch (SecurityException e) { + // do nothing, the methods will be null on failure + } + getThreadCpuTimeMethod = threadCpuTime; + isThreadCpuTimeSupportedMethod = threadCpuTimeSupported; + } + } + + ReflectiveThreadMXBean(Object threadMxBean) { + super(); + this.threadMxBean = threadMxBean; + } + + /** + * {@inheritDoc} + */ + public long getThreadCpuTime(long id) { + if (Holder.getThreadCpuTimeMethod != null) { + Exception error = null; + try { + return (Long) Holder.getThreadCpuTimeMethod.invoke(threadMxBean, id); + } catch (ClassCastException e) { + error = e; + // fallthrough + } catch (IllegalAccessException e) { + error = e; + // fallthrough + } catch (IllegalArgumentException e) { + error = e; + // fallthrough + } catch (InvocationTargetException e) { + error = e; + // fallthrough + } + throw new UnsupportedOperationException(Holder.FAILURE_MESSAGE, error); + } + throw new UnsupportedOperationException(Holder.FAILURE_MESSAGE); + } + + /** + * {@inheritDoc} + */ + public boolean isThreadCpuTimeSupported() { + if (Holder.isThreadCpuTimeSupportedMethod != null) { + try { + return (Boolean) Holder.isThreadCpuTimeSupportedMethod.invoke(threadMxBean); + } catch (ClassCastException e) { + // fallthrough + } catch (IllegalAccessException e) { + // fallthrough + } catch (IllegalArgumentException e) { + // fallthrough + } catch (InvocationTargetException e) { + // fallthrough + } + } + return false; + } + +} + diff --git a/src/main/java/org/junit/internal/management/RuntimeMXBean.java b/src/main/java/org/junit/internal/management/RuntimeMXBean.java new file mode 100644 index 0000000..84f8861 --- /dev/null +++ b/src/main/java/org/junit/internal/management/RuntimeMXBean.java @@ -0,0 +1,14 @@ +package org.junit.internal.management; + +import java.util.List; + +/** + * Wrapper for {@link java.lang.management.RuntimeMXBean}. + */ +public interface RuntimeMXBean { + + /** + * @see java.lang.management.RuntimeMXBean#getInputArguments() + */ + List getInputArguments(); +} diff --git a/src/main/java/org/junit/internal/management/ThreadMXBean.java b/src/main/java/org/junit/internal/management/ThreadMXBean.java new file mode 100644 index 0000000..f9225c9 --- /dev/null +++ b/src/main/java/org/junit/internal/management/ThreadMXBean.java @@ -0,0 +1,17 @@ +package org.junit.internal.management; + +/** + * Wrapper for {@link java.lang.management.ThreadMXBean}. + */ +public interface ThreadMXBean { + /** + * @see java.lang.management.ThreadMXBean#getThreadCpuTime(long) + */ + long getThreadCpuTime(long id); + + /** + * @see java.lang.management.ThreadMXBean#isThreadCpuTimeSupported() + */ + boolean isThreadCpuTimeSupported(); +} + diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java index 5d45ba3..93a6827 100644 --- a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java +++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java @@ -1,12 +1,11 @@ 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; +import org.junit.internal.Throwables; + /** * A matcher that delegates to throwableMatcher and in addition appends the * stacktrace of the actual Throwable in case of a mismatch. @@ -37,9 +36,7 @@ public class StacktracePrintingMatcher extends } private String readStacktrace(Throwable throwable) { - StringWriter stringWriter = new StringWriter(); - throwable.printStackTrace(new PrintWriter(stringWriter)); - return stringWriter.toString(); + return Throwables.getStacktrace(throwable); } @Factory diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java index 22ce8bd..6e2ff5e 100644 --- a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java +++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java @@ -14,9 +14,9 @@ import org.hamcrest.TypeSafeMatcher; public class ThrowableCauseMatcher extends TypeSafeMatcher { - private final Matcher causeMatcher; + private final Matcher causeMatcher; - public ThrowableCauseMatcher(Matcher causeMatcher) { + public ThrowableCauseMatcher(Matcher causeMatcher) { this.causeMatcher = causeMatcher; } @@ -44,7 +44,7 @@ public class ThrowableCauseMatcher extends * @param type of the outer exception */ @Factory - public static Matcher hasCause(final Matcher matcher) { + public static Matcher hasCause(final Matcher matcher) { return new ThrowableCauseMatcher(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 4e2cc12..fb25982 100644 --- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java +++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java @@ -40,7 +40,7 @@ public abstract class TypeSafeMatcher extends BaseMatcher { } private static boolean isMatchesSafelyMethod(Method method) { - return method.getName().equals("matchesSafely") + return "matchesSafely".equals(method.getName()) && method.getParameterTypes().length == 1 && !method.isSynthetic(); } diff --git a/src/main/java/org/junit/internal/requests/ClassRequest.java b/src/main/java/org/junit/internal/requests/ClassRequest.java index 3d6b100..d60e360 100644 --- a/src/main/java/org/junit/internal/requests/ClassRequest.java +++ b/src/main/java/org/junit/internal/requests/ClassRequest.java @@ -1,20 +1,18 @@ package org.junit.internal.requests; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; -import org.junit.runner.Request; +import org.junit.internal.builders.SuiteMethodBuilder; import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; -public class ClassRequest extends Request { - private final Object runnerLock = new Object(); - +public class ClassRequest extends MemoizingRequest { /* * 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 + * https://github.com/junit-team/junit4/issues/960 */ private final Class fTestClass; private final boolean canUseSuiteMethod; - private volatile Runner runner; public ClassRequest(Class testClass, boolean canUseSuiteMethod) { this.fTestClass = testClass; @@ -26,14 +24,31 @@ public class ClassRequest extends Request { } @Override - public Runner getRunner() { - if (runner == null) { - synchronized (runnerLock) { - if (runner == null) { - runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass); - } + protected Runner createRunner() { + return new CustomAllDefaultPossibilitiesBuilder().safeRunnerForClass(fTestClass); + } + + private class CustomAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder { + + @Override + protected RunnerBuilder suiteMethodBuilder() { + return new CustomSuiteMethodBuilder(); + } + } + + /* + * Customization of {@link SuiteMethodBuilder} that prevents use of the + * suite method when creating a runner for fTestClass when canUseSuiteMethod + * is false. + */ + private class CustomSuiteMethodBuilder extends SuiteMethodBuilder { + + @Override + public Runner runnerForClass(Class testClass) throws Throwable { + if (testClass == fTestClass && !canUseSuiteMethod) { + return null; } + return super.runnerForClass(testClass); } - 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 066cba3..5f00399 100644 --- a/src/main/java/org/junit/internal/requests/FilterRequest.java +++ b/src/main/java/org/junit/internal/requests/FilterRequest.java @@ -14,7 +14,7 @@ public final class FilterRequest extends 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 + * https://github.com/junit-team/junit4/issues/960 */ private final Filter fFilter; diff --git a/src/main/java/org/junit/internal/requests/MemoizingRequest.java b/src/main/java/org/junit/internal/requests/MemoizingRequest.java new file mode 100644 index 0000000..191c230 --- /dev/null +++ b/src/main/java/org/junit/internal/requests/MemoizingRequest.java @@ -0,0 +1,30 @@ +package org.junit.internal.requests; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.junit.runner.Request; +import org.junit.runner.Runner; + +abstract class MemoizingRequest extends Request { + private final Lock runnerLock = new ReentrantLock(); + private volatile Runner runner; + + @Override + public final Runner getRunner() { + if (runner == null) { + runnerLock.lock(); + try { + if (runner == null) { + runner = createRunner(); + } + } finally { + runnerLock.unlock(); + } + } + return runner; + } + + /** Creates the {@link Runner} to return from {@link #getRunner()}. Called at most once. */ + protected abstract Runner createRunner(); +} diff --git a/src/main/java/org/junit/internal/requests/OrderingRequest.java b/src/main/java/org/junit/internal/requests/OrderingRequest.java new file mode 100644 index 0000000..441e595 --- /dev/null +++ b/src/main/java/org/junit/internal/requests/OrderingRequest.java @@ -0,0 +1,29 @@ +package org.junit.internal.requests; + +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.InvalidOrderingException; +import org.junit.runner.manipulation.Ordering; + +/** @since 4.13 */ +public class OrderingRequest extends MemoizingRequest { + private final Request request; + private final Ordering ordering; + + public OrderingRequest(Request request, Ordering ordering) { + this.request = request; + this.ordering = ordering; + } + + @Override + protected Runner createRunner() { + Runner runner = request.getRunner(); + try { + ordering.apply(runner); + } catch (InvalidOrderingException e) { + return new ErrorReportingRunner(ordering.getClass(), e); + } + return runner; + } +} diff --git a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java index 1d32beb..f52abab 100644 --- a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java +++ b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java @@ -1,33 +1,44 @@ package org.junit.internal.runners; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; import java.util.List; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InvalidTestClassError; import org.junit.runners.model.InitializationError; +import static java.util.Collections.singletonList; + public class ErrorReportingRunner extends Runner { private final List causes; - private final Class testClass; + private final String classNames; public ErrorReportingRunner(Class testClass, Throwable cause) { - if (testClass == null) { - throw new NullPointerException("Test class cannot be null"); + this(cause, testClass); + } + + public ErrorReportingRunner(Throwable cause, Class... testClasses) { + if (testClasses == null || testClasses.length == 0) { + throw new NullPointerException("Test classes cannot be null or empty"); } - this.testClass = testClass; + for (Class testClass : testClasses) { + if (testClass == null) { + throw new NullPointerException("Test class cannot be null"); + } + } + classNames = getClassNames(testClasses); causes = getCauses(cause); } - + @Override public Description getDescription() { - Description description = Description.createSuiteDescription(testClass); + Description description = Description.createSuiteDescription(classNames); for (Throwable each : causes) { - description.addChild(describeCause(each)); + description.addChild(describeCause()); } return description; } @@ -39,11 +50,25 @@ public class ErrorReportingRunner extends Runner { } } + private String getClassNames(Class... testClasses) { + final StringBuilder builder = new StringBuilder(); + for (Class testClass : testClasses) { + if (builder.length() != 0) { + builder.append(", "); + } + builder.append(testClass.getName()); + } + return builder.toString(); + } + @SuppressWarnings("deprecation") private List getCauses(Throwable cause) { if (cause instanceof InvocationTargetException) { return getCauses(cause.getCause()); } + if (cause instanceof InvalidTestClassError) { + return singletonList(cause); + } if (cause instanceof InitializationError) { return ((InitializationError) cause).getCauses(); } @@ -51,16 +76,15 @@ public class ErrorReportingRunner extends Runner { return ((org.junit.internal.runners.InitializationError) cause) .getCauses(); } - return Arrays.asList(cause); + return singletonList(cause); } - private Description describeCause(Throwable child) { - return Description.createTestDescription(testClass, - "initializationError"); + private Description describeCause() { + return Description.createTestDescription(classNames, "initializationError"); } private void runCause(Throwable child, RunNotifier notifier) { - Description description = describeCause(child); + Description description = describeCause(); notifier.fireTestStarted(description); notifier.fireTestFailure(new Failure(description, child)); notifier.fireTestFinished(description); diff --git a/src/main/java/org/junit/internal/runners/InitializationError.java b/src/main/java/org/junit/internal/runners/InitializationError.java index 52065ec..484f58d 100644 --- a/src/main/java/org/junit/internal/runners/InitializationError.java +++ b/src/main/java/org/junit/internal/runners/InitializationError.java @@ -15,7 +15,7 @@ public class InitializationError extends Exception { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final List fErrors; diff --git a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java index 631fcf2..0d51541 100644 --- a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java +++ b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java @@ -1,5 +1,8 @@ package org.junit.internal.runners; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + import junit.extensions.TestDecorator; import junit.framework.AssertionFailedError; import junit.framework.Test; @@ -12,15 +15,16 @@ import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.Orderer; +import org.junit.runner.manipulation.InvalidOrderingException; import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Orderable; 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 { +public class JUnit38ClassRunner extends Runner implements Filterable, Orderable { private static final class OldTestClassAdaptingListener implements TestListener { private final RunNotifier notifier; @@ -170,6 +174,18 @@ public class JUnit38ClassRunner extends Runner implements Filterable, Sortable { } } + /** + * {@inheritDoc} + * + * @since 4.13 + */ + public void order(Orderer orderer) throws InvalidOrderingException { + if (getTest() instanceof Orderable) { + Orderable adapter = (Orderable) getTest(); + adapter.order(orderer); + } + } + private void setTest(Test test) { this.test = test; } diff --git a/src/main/java/org/junit/internal/runners/MethodValidator.java b/src/main/java/org/junit/internal/runners/MethodValidator.java index ba9c9d1..e656ee5 100644 --- a/src/main/java/org/junit/internal/runners/MethodValidator.java +++ b/src/main/java/org/junit/internal/runners/MethodValidator.java @@ -86,7 +86,7 @@ public class MethodValidator { } if (each.getReturnType() != Void.TYPE) { errors.add(new Exception("Method " + each.getName() - + " should be void")); + + "should have a return type of void")); } if (each.getParameterTypes().length != 0) { errors.add(new Exception("Method " + each.getName() diff --git a/src/main/java/org/junit/internal/runners/TestClass.java b/src/main/java/org/junit/internal/runners/TestClass.java index 1abaeea..6d24f4f 100644 --- a/src/main/java/org/junit/internal/runners/TestClass.java +++ b/src/main/java/org/junit/internal/runners/TestClass.java @@ -85,7 +85,7 @@ public class TestClass { } private List> getSuperClasses(Class testClass) { - ArrayList> results = new ArrayList>(); + List> results = new ArrayList>(); Class current = testClass; while (current != null) { results.add(current); 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 e094809..c5a0764 100644 --- a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java +++ b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java @@ -45,4 +45,27 @@ public class EachTestNotifier { public void fireTestIgnored() { notifier.fireTestIgnored(description); } + + /** + * Calls {@link RunNotifier#fireTestSuiteStarted(Description)}, passing the + * {@link Description} that was passed to the {@code EachTestNotifier} constructor. + * This should be called when a test suite is about to be started. + * @see RunNotifier#fireTestSuiteStarted(Description) + * @since 4.13 + */ + public void fireTestSuiteStarted() { + notifier.fireTestSuiteStarted(description); + } + + /** + * Calls {@link RunNotifier#fireTestSuiteFinished(Description)}, passing the + * {@link Description} that was passed to the {@code EachTestNotifier} constructor. + * This should be called when a test suite has finished, whether the test suite succeeds + * or fails. + * @see RunNotifier#fireTestSuiteFinished(Description) + * @since 4.13 + */ + public void fireTestSuiteFinished() { + notifier.fireTestSuiteFinished(description); + } } \ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/rules/ValidationError.java b/src/main/java/org/junit/internal/runners/rules/ValidationError.java index d1af8ae..31bd660 100644 --- a/src/main/java/org/junit/internal/runners/rules/ValidationError.java +++ b/src/main/java/org/junit/internal/runners/rules/ValidationError.java @@ -5,6 +5,9 @@ import org.junit.runners.model.FrameworkMember; import java.lang.annotation.Annotation; class ValidationError extends Exception { + + private static final long serialVersionUID = 3176511008672645574L; + public ValidationError(FrameworkMember member, Class 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 d0636bd..9a2a952 100644 --- a/src/main/java/org/junit/internal/runners/statements/ExpectException.java +++ b/src/main/java/org/junit/internal/runners/statements/ExpectException.java @@ -19,7 +19,9 @@ public class ExpectException extends Statement { next.evaluate(); complete = true; } catch (AssumptionViolatedException e) { - throw e; + if (!expected.isAssignableFrom(e.getClass())) { + throw e; + } } catch (Throwable e) { if (!expected.isAssignableFrom(e.getClass())) { String message = "Unexpected exception, expected<" 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 9fad35b..9362cc1 100644 --- a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java +++ b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java @@ -1,5 +1,8 @@ package org.junit.internal.runners.statements; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -7,6 +10,9 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.internal.management.ManagementFactory; +import org.junit.internal.management.ThreadMXBean; +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; import org.junit.runners.model.TestTimedOutException; @@ -14,6 +20,7 @@ public class FailOnTimeout extends Statement { private final Statement originalStatement; private final TimeUnit timeUnit; private final long timeout; + private final boolean lookForStuckThread; /** * Returns a new builder for building an instance. @@ -40,6 +47,7 @@ public class FailOnTimeout extends Statement { originalStatement = statement; timeout = builder.timeout; timeUnit = builder.unit; + lookForStuckThread = builder.lookForStuckThread; } /** @@ -48,6 +56,7 @@ public class FailOnTimeout extends Statement { * @since 4.12 */ public static class Builder { + private boolean lookForStuckThread = false; private long timeout = 0; private TimeUnit unit = TimeUnit.SECONDS; @@ -79,6 +88,20 @@ public class FailOnTimeout extends Statement { 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. @@ -97,7 +120,8 @@ public class FailOnTimeout extends Statement { public void evaluate() throws Throwable { CallableStatement callable = new CallableStatement(); FutureTask task = new FutureTask(callable); - Thread thread = new Thread(task, "Time-limited test"); + ThreadGroup threadGroup = threadGroupForNewThread(); + Thread thread = new Thread(threadGroup, task, "Time-limited test"); thread.setDaemon(true); thread.start(); callable.awaitStarted(); @@ -107,6 +131,31 @@ public class FailOnTimeout extends Statement { } } + private ThreadGroup threadGroupForNewThread() { + if (!lookForStuckThread) { + // Use the default ThreadGroup (usually the one from the current + // thread). + return null; + } + + // Create the thread in a new ThreadGroup, so if the time-limited thread + // becomes stuck, getStuckThread() can find the thread likely to be the + // culprit. + ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup"); + if (!threadGroup.isDaemon()) { + // Mark the new ThreadGroup as a daemon thread group, so it will be + // destroyed after the time-limited thread completes. By ensuring the + // ThreadGroup is destroyed, any data associated with the ThreadGroup + // (ex: via java.beans.ThreadGroupContext) is destroyed. + try { + threadGroup.setDaemon(true); + } catch (SecurityException e) { + // Swallow the exception to keep the same behavior as in JUnit 4.12. + } + } + return threadGroup; + } + /** * 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 @@ -131,12 +180,114 @@ public class FailOnTimeout extends Statement { 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(); } - return currThreadException; + if (stuckThread != null) { + Exception stuckThreadException = + new Exception("Appears to be stuck in thread " + + stuckThread.getName()); + stuckThreadException.setStackTrace(getStackTrace(stuckThread)); + return new MultipleFailureException( + Arrays.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) { + List threadsInGroup = getThreadsInGroup(mainThread.getThreadGroup()); + if (threadsInGroup.isEmpty()) { + 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 an empty list + * if this cannot be determined, e.g. because new threads are being created at an + * extremely fast rate. + */ + private List getThreadsInGroup(ThreadGroup group) { + final int activeThreadCount = group.activeCount(); // this is just an estimate + int threadArraySize = Math.max(activeThreadCount * 2, 100); + for (int loopCount = 0; loopCount < 5; loopCount++) { + Thread[] threads = new Thread[threadArraySize]; + int enumCount = group.enumerate(threads); + if (enumCount < threadArraySize) { + return Arrays.asList(threads).subList(0, enumCount); + } + // 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. + threadArraySize += 100; + } + // threads are proliferating too fast for us. Bail before we get into + // trouble. + return Collections.emptyList(); + } + + /** + * 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 { 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 7512a7d..5e56c33 100644 --- a/src/main/java/org/junit/internal/runners/statements/RunAfters.java +++ b/src/main/java/org/junit/internal/runners/statements/RunAfters.java @@ -30,7 +30,7 @@ public class RunAfters extends Statement { } finally { for (FrameworkMethod each : afters) { try { - each.invokeExplosively(target); + invokeMethod(each); } catch (Throwable e) { errors.add(e); } @@ -38,4 +38,11 @@ public class RunAfters extends Statement { } MultipleFailureException.assertEmpty(errors); } + + /** + * @since 4.13 + */ + protected void invokeMethod(FrameworkMethod method) throws Throwable { + method.invokeExplosively(target); + } } \ 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 238fbe7..bd835c7 100644 --- a/src/main/java/org/junit/internal/runners/statements/RunBefores.java +++ b/src/main/java/org/junit/internal/runners/statements/RunBefores.java @@ -21,8 +21,15 @@ public class RunBefores extends Statement { @Override public void evaluate() throws Throwable { for (FrameworkMethod before : befores) { - before.invokeExplosively(target); + invokeMethod(before); } next.evaluate(); } + + /** + * @since 4.13 + */ + protected void invokeMethod(FrameworkMethod method) throws Throwable { + method.invokeExplosively(target); + } } \ 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 5bb48d7..13407cc 100644 --- a/src/main/java/org/junit/matchers/JUnitMatchers.java +++ b/src/main/java/org/junit/matchers/JUnitMatchers.java @@ -48,7 +48,7 @@ public class JUnitMatchers { */ @Deprecated public static Matcher> hasItems(Matcher... elementMatchers) { - return CoreMatchers.hasItems(elementMatchers); + return CoreMatchers.hasItems(elementMatchers); } /** @@ -57,7 +57,7 @@ public class JUnitMatchers { */ @Deprecated public static Matcher> everyItem(final Matcher elementMatcher) { - return CoreMatchers.everyItem((Matcher) elementMatcher); + return CoreMatchers.everyItem(elementMatcher); } /** 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..3bca103 --- /dev/null +++ b/src/main/java/org/junit/rules/DisableOnDebug.java @@ -0,0 +1,125 @@ +package org.junit.rules; + +import java.util.List; + +import org.junit.internal.management.ManagementFactory; +import org.junit.internal.management.RuntimeMXBean; +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. + *

+ * 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}. + *

+ * 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. + *

+ * This does nothing to tackle timeouts or time sensitive code under test when + * debugging and may make this less useful in such circumstances. + *

+ * Example usage: + * + *

+ * public static class DisableTimeoutOnDebugSampleTest {
+ * 
+ *     @Rule
+ *     public TestRule timeout = new DisableOnDebug(new Timeout(20));
+ * 
+ *     @Test
+ *     public void myTest() {
+ *         int i = 0;
+ *         assertEquals(0, i); // suppose you had a break point here to inspect i
+ *     }
+ * }
+ * 
+ * + * @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 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 + *

+ * Options specified in: + *

    + *
  • + * javase-6
  • + *
  • javase-7
  • + *
  • javase-8
  • + * + * + * @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 arguments) { + for (final String argument : arguments) { + if ("-Xdebug".equals(argument) || 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 8c6600e..9711e50 100644 --- a/src/main/java/org/junit/rules/ErrorCollector.java +++ b/src/main/java/org/junit/rules/ErrorCollector.java @@ -1,11 +1,14 @@ package org.junit.rules; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertThrows; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; +import org.junit.function.ThrowingRunnable; +import org.junit.internal.AssumptionViolatedException; import org.hamcrest.Matcher; import org.junit.runners.model.MultipleFailureException; @@ -43,7 +46,16 @@ public class ErrorCollector extends Verifier { * Adds a Throwable to the table. Execution continues, but the test will fail at the end. */ public void addError(Throwable error) { - errors.add(error); + if (error == null) { + throw new NullPointerException("Error cannot be null"); + } + if (error instanceof AssumptionViolatedException) { + AssertionError e = new AssertionError(error.getMessage()); + e.initCause(error); + errors.add(e); + } else { + errors.add(error); + } } /** @@ -76,9 +88,33 @@ public class ErrorCollector extends Verifier { public T checkSucceeds(Callable callable) { try { return callable.call(); + } catch (AssumptionViolatedException e) { + AssertionError error = new AssertionError("Callable threw AssumptionViolatedException"); + error.initCause(e); + addError(error); + return null; } catch (Throwable e) { addError(e); return null; } } + + /** + * Adds a failure to the table if {@code runnable} does not throw an + * exception of type {@code expectedThrowable} when executed. + * Execution continues, but the test will fail at the end if the runnable + * does not throw an exception, or if it throws a different exception. + * + * @param expectedThrowable the expected type of the exception + * @param runnable a function that is expected to throw an exception when executed + * @since 4.13 + */ + public void checkThrows(Class expectedThrowable, ThrowingRunnable runnable) { + try { + assertThrows(expectedThrowable, runnable); + } catch (AssertionError e) { + addError(e); + } + } + } diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java index 4d61712..431ad49 100644 --- a/src/main/java/org/junit/rules/ExpectedException.java +++ b/src/main/java/org/junit/rules/ExpectedException.java @@ -7,7 +7,6 @@ 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.AssumptionViolatedException; @@ -21,7 +20,7 @@ import org.junit.runners.model.Statement; * *
     public class SimpleExpectedExceptionTest {
      *     @Rule
    - *     public ExpectedException thrown= ExpectedException.none();
    + *     public ExpectedException thrown = ExpectedException.none();
      *
      *     @Test
      *     public void throwsNothing() {
    @@ -35,16 +34,19 @@ import org.junit.runners.model.Statement;
      *     }
      * }
    * - *

    - * You have to add the {@code ExpectedException} rule to your test. + *

    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 + * After specifying 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. * - *

    - * Instead of specifying the exception's type you can characterize the - * expected exception based on other criterias, too: + *

    This rule does not perform any special magic to make execution continue + * as if the exception had not been thrown. So it is nearly always a mistake + * for a test method to have statements after the one that is expected to + * throw the exception. + * + *

    Instead of specifying the exception's type you can characterize the + * expected exception based on other criteria, too: * *

      *
    • The exception's message contains a specific text: {@link #expectMessage(String)}
    • @@ -53,8 +55,7 @@ import org.junit.runners.model.Statement; *
    • The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}
    • *
    * - *

    - * You can combine any of the presented expect-methods. The test is + *

    You can combine any of the presented expect-methods. The test is * successful if all specifications are met. *

     @Test
      * public void throwsException() {
    @@ -63,9 +64,15 @@ import org.junit.runners.model.Statement;
      *     throw new NullPointerException("What happened?");
      * }
    * + *

    It is recommended to set the {@link org.junit.Rule#order() order} of the + * {@code ExpectedException} to {@code Integer.MAX_VALUE} if it is used together + * with another rule that handles exceptions, e.g. {@link ErrorCollector}. + * Otherwise failing tests may be successful. + *

     @Rule(order = Integer.MAX_VALUE)
    + * public ExpectedException thrown = ExpectedException.none();
    + * *

    AssumptionViolatedExceptions

    - *

    - * JUnit uses {@link AssumptionViolatedException}s for indicating that a test + *

    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 @@ -80,8 +87,7 @@ import org.junit.runners.model.Statement; * *

    AssertionErrors

    * - *

    - * JUnit uses {@link AssertionError}s for indicating that a test is failing. You + *

    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. @@ -93,8 +99,7 @@ import org.junit.runners.model.Statement; * } * *

    Missing Exceptions

    - *

    - * By default missing exceptions are reported with an error message + *

    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 @@ -107,7 +112,13 @@ public class ExpectedException implements TestRule { /** * Returns a {@linkplain TestRule rule} that expects no exception to * be thrown (identical to behavior without this rule). + * + * @deprecated Since 4.13 + * {@link org.junit.Assert#assertThrows(Class, org.junit.function.ThrowingRunnable) + * Assert.assertThrows} can be used to verify that your code throws a specific + * exception. */ + @Deprecated public static ExpectedException none() { return new ExpectedException(); } @@ -222,10 +233,18 @@ public class ExpectedException implements TestRule { * throw new IllegalArgumentException("What happened?", cause); * } */ - public void expectCause(Matcher expectedCause) { + public void expectCause(Matcher expectedCause) { expect(hasCause(expectedCause)); } + /** + * Check if any Exception is expected. + * @since 4.13 + */ + public final boolean isAnyExceptionExpected() { + return matcherBuilder.expectsThrowable(); + } + private class ExpectedExceptionStatement extends Statement { private final Statement next; @@ -255,10 +274,6 @@ public class ExpectedException implements TestRule { } } - private boolean isAnyExceptionExpected() { - return matcherBuilder.expectsThrowable(); - } - private void failDueToMissingException() throws AssertionError { fail(missingExceptionMessage()); } diff --git a/src/main/java/org/junit/rules/ExternalResource.java b/src/main/java/org/junit/rules/ExternalResource.java index 71ca287..71fc842 100644 --- a/src/main/java/org/junit/rules/ExternalResource.java +++ b/src/main/java/org/junit/rules/ExternalResource.java @@ -1,6 +1,10 @@ package org.junit.rules; +import java.util.ArrayList; +import java.util.List; + import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; /** @@ -44,11 +48,20 @@ public abstract class ExternalResource implements TestRule { @Override public void evaluate() throws Throwable { before(); + + List errors = new ArrayList(); try { base.evaluate(); + } catch (Throwable t) { + errors.add(t); } finally { - after(); + try { + after(); + } catch (Throwable t) { + errors.add(t); + } } + MultipleFailureException.assertEmpty(errors); } }; } diff --git a/src/main/java/org/junit/rules/MethodRule.java b/src/main/java/org/junit/rules/MethodRule.java index 823ee78..94608f5 100644 --- a/src/main/java/org/junit/rules/MethodRule.java +++ b/src/main/java/org/junit/rules/MethodRule.java @@ -10,21 +10,9 @@ import org.junit.runners.model.Statement; * {@link Statement} that executes the method is passed to each annotated * {@link Rule} in turn, and each may return a substitute or modified * {@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: + * an example of how this can be useful, see {@link TestWatchman}. * - *

      - *
    • {@link ErrorCollector}: collect multiple errors in one test method
    • - *
    • {@link ExpectedException}: make flexible assertions about thrown exceptions
    • - *
    • {@link ExternalResource}: start and stop a server, for example
    • - *
    • {@link TemporaryFolder}: create fresh files, and delete after test
    • - *
    • {@link TestName}: remember the test name for use during the method
    • - *
    • {@link TestWatchman}: add logic at events during method execution
    • - *
    • {@link Timeout}: cause test to fail after a set time
    • - *
    • {@link Verifier}: fail test if object state ends up incorrect
    • - *
    - * - * Note that {@link MethodRule} has been replaced by {@link TestRule}, + *

    Note that {@link MethodRule} has been replaced by {@link TestRule}, * which has the added benefit of supporting class rules. * * @since 4.7 diff --git a/src/main/java/org/junit/rules/RuleChain.java b/src/main/java/org/junit/rules/RuleChain.java index f43d8f5..bf93aae 100644 --- a/src/main/java/org/junit/rules/RuleChain.java +++ b/src/main/java/org/junit/rules/RuleChain.java @@ -4,26 +4,34 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.Rule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** - * The RuleChain rule allows ordering of TestRules. You create a + * The {@code RuleChain} can be used for creating composite rules. You create a * {@code RuleChain} with {@link #outerRule(TestRule)} and subsequent calls of * {@link #around(TestRule)}: * *

    - * public static class UseRuleChain {
    - * 	@Rule
    - * 	public RuleChain chain= RuleChain
    - * 	                       .outerRule(new LoggingRule("outer rule")
    - * 	                       .around(new LoggingRule("middle rule")
    - * 	                       .around(new LoggingRule("inner rule");
    + * public abstract class CompositeRules {
    + *   public static TestRule extendedLogging() {
    + *     return RuleChain.outerRule(new LoggingRule("outer rule"))
    + *                     .around(new LoggingRule("middle rule"))
    + *                     .around(new LoggingRule("inner rule"));
    + *   }
    + * }
    + * 
    + * + *
    + * public class UseRuleChain {
    + *   @Rule
    + *   public final TestRule extendedLogging = CompositeRules.extendedLogging();
      *
    - * 	@Test
    - * 	public void example() {
    - * 		assertTrue(true);
    - *     }
    + *   @Test
    + *   public void example() {
    + *     assertTrue(true);
    + *   }
      * }
      * 
    * @@ -38,6 +46,13 @@ import org.junit.runners.model.Statement; * finished outer rule * * + * In older versions of JUnit (before 4.13) {@code RuleChain} was used for + * ordering rules. We recommend to not use it for this purpose anymore. You can + * use the attribute {@code order} of the annotation {@link Rule#order() Rule} + * or {@link org.junit.ClassRule#order() ClassRule} for ordering rules. + * + * @see org.junit.Rule#order() + * @see org.junit.ClassRule#order() * @since 4.10 */ public class RuleChain implements TestRule { @@ -72,13 +87,17 @@ public class RuleChain implements TestRule { } /** - * Create a new {@code RuleChain}, which encloses the {@code nextRule} with + * Create a new {@code RuleChain}, which encloses the given {@link TestRule} with * the rules of the current {@code RuleChain}. * - * @param enclosedRule the rule to enclose. + * @param enclosedRule the rule to enclose; must not be {@code null}. * @return a new {@code RuleChain}. + * @throws NullPointerException if the argument {@code enclosedRule} is {@code null} */ public RuleChain around(TestRule enclosedRule) { + if (enclosedRule == null) { + throw new NullPointerException("The enclosed rule must not be null"); + } List rulesOfNewChain = new ArrayList(); rulesOfNewChain.add(enclosedRule); rulesOfNewChain.addAll(rulesStartingWithInnerMost); @@ -89,9 +108,6 @@ public class RuleChain implements TestRule { * {@inheritDoc} */ public Statement apply(Statement base, Description description) { - for (TestRule each : rulesStartingWithInnerMost) { - base = each.apply(base, description); - } - return base; + return new RunRules(base, rulesStartingWithInnerMost, description); } } \ No newline at end of file diff --git a/src/main/java/org/junit/rules/Stopwatch.java b/src/main/java/org/junit/rules/Stopwatch.java index 5d34e7f..6900a48 100644 --- a/src/main/java/org/junit/rules/Stopwatch.java +++ b/src/main/java/org/junit/rules/Stopwatch.java @@ -76,7 +76,7 @@ import java.util.concurrent.TimeUnit; * @author tibor17 * @since 4.12 */ -public abstract class Stopwatch implements TestRule { +public class Stopwatch implements TestRule { private final Clock clock; private volatile long startNanos; private volatile long endNanos; diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java index dc75c93..a726c66 100644 --- a/src/main/java/org/junit/rules/TemporaryFolder.java +++ b/src/main/java/org/junit/rules/TemporaryFolder.java @@ -1,15 +1,20 @@ package org.junit.rules; +import static org.junit.Assert.fail; + import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import org.junit.Rule; /** * 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. + * fails). + * By default no exception will be thrown in case the deletion fails. * *

    Example of usage: *

    @@ -26,18 +31,104 @@ import org.junit.Rule;
      * }
      * 
    * + *

    TemporaryFolder rule supports assured deletion mode, which + * will fail the test in case deletion fails with {@link AssertionError}. + * + *

    Creating TemporaryFolder with assured deletion: + *

    + *  @Rule
    + *  public TemporaryFolder folder= TemporaryFolder.builder().assureDeletion().build();
    + * 
    + * * @since 4.7 */ public class TemporaryFolder extends ExternalResource { private final File parentFolder; + private final boolean assureDeletion; private File folder; + private static final int TEMP_DIR_ATTEMPTS = 10000; + private static final String TMP_PREFIX = "junit"; + + /** + * Create a temporary folder which uses system default temporary-file + * directory to create temporary resources. + */ public TemporaryFolder() { - this(null); + this((File) null); } + /** + * Create a temporary folder which uses the specified directory to create + * temporary resources. + * + * @param parentFolder folder where temporary resources will be created. + * If {@code null} then system default temporary-file directory is used. + */ public TemporaryFolder(File parentFolder) { this.parentFolder = parentFolder; + this.assureDeletion = false; + } + + /** + * Create a {@link TemporaryFolder} initialized with + * values from a builder. + */ + protected TemporaryFolder(Builder builder) { + this.parentFolder = builder.parentFolder; + this.assureDeletion = builder.assureDeletion; + } + + /** + * Returns a new builder for building an instance of {@link TemporaryFolder}. + * + * @since 4.13 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds an instance of {@link TemporaryFolder}. + * + * @since 4.13 + */ + public static class Builder { + private File parentFolder; + private boolean assureDeletion; + + protected Builder() {} + + /** + * Specifies which folder to use for creating temporary resources. + * If {@code null} then system default temporary-file directory is + * used. + * + * @return this + */ + public Builder parentFolder(File parentFolder) { + this.parentFolder = parentFolder; + return this; + } + + /** + * Setting this flag assures that no resources are left undeleted. Failure + * to fulfill the assurance results in failure of tests with an + * {@link AssertionError}. + * + * @return this + */ + public Builder assureDeletion() { + this.assureDeletion = true; + return this; + } + + /** + * Builds a {@link TemporaryFolder} instance using the values in this builder. + */ + public TemporaryFolder build() { + return new TemporaryFolder(this); + } } @Override @@ -75,52 +166,63 @@ public class TemporaryFolder extends ExternalResource { * Returns a new fresh file with a random name under the temporary folder. */ public File newFile() throws IOException { - return File.createTempFile("junit", null, getRoot()); + return File.createTempFile(TMP_PREFIX, null, getRoot()); } /** - * Returns a new fresh folder with the given name under the temporary + * Returns a new fresh folder with the given path under the temporary * folder. */ - public File newFolder(String folder) throws IOException { - return newFolder(new String[]{folder}); + public File newFolder(String path) throws IOException { + return newFolder(new String[]{path}); } /** - * Returns a new fresh folder with the given name(s) under the temporary - * folder. + * Returns a new fresh folder with the given paths under the temporary + * folder. For example, if you pass in the strings {@code "parent"} and {@code "child"} + * then a directory named {@code "parent"} will be created under the temporary folder + * and a directory named {@code "child"} will be created under the newly-created + * {@code "parent"} directory. */ - 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"); - } + public File newFolder(String... paths) throws IOException { + if (paths.length == 0) { + throw new IllegalArgumentException("must pass at least one path"); } - 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); + + /* + * Before checking if the paths are absolute paths, check if create() was ever called, + * and if it wasn't, throw IllegalStateException. + */ + File root = getRoot(); + for (String path : paths) { + if (new File(path).isAbsolute()) { + throw new IOException("folder path \'" + path + "\' is not a relative path"); + } } - } - private boolean isLastElementInArray(int index, String[] array) { - return index == array.length - 1; + File relativePath = null; + File file = root; + boolean lastMkdirsCallSuccessful = true; + for (String path : paths) { + relativePath = new File(relativePath, path); + file = new File(root, relativePath.getPath()); + + lastMkdirsCallSuccessful = file.mkdirs(); + if (!lastMkdirsCallSuccessful && !file.isDirectory()) { + if (file.exists()) { + throw new IOException( + "a file with the path \'" + relativePath.getPath() + "\' exists"); + } else { + throw new IOException( + "could not create a folder with the path \'" + relativePath.getPath() + "\'"); + } + } + } + if (!lastMkdirsCallSuccessful) { + throw new IOException( + "a folder with the path \'" + relativePath.getPath() + "\' already exists"); + } + return file; } /** @@ -130,11 +232,63 @@ public class TemporaryFolder extends ExternalResource { return createTemporaryFolderIn(getRoot()); } - private File createTemporaryFolderIn(File parentFolder) throws IOException { - File createdFolder = File.createTempFile("junit", "", parentFolder); - createdFolder.delete(); - createdFolder.mkdir(); - return createdFolder; + private static File createTemporaryFolderIn(File parentFolder) throws IOException { + try { + return createTemporaryFolderWithNioApi(parentFolder); + } catch (ClassNotFoundException ignore) { + // Fallback for Java 5 and 6 + return createTemporaryFolderWithFileApi(parentFolder); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + IOException exception = new IOException("Failed to create temporary folder in " + parentFolder); + exception.initCause(cause); + throw exception; + } catch (Exception e) { + throw new RuntimeException("Failed to create temporary folder in " + parentFolder, e); + } + } + + private static File createTemporaryFolderWithNioApi(File parentFolder) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class filesClass = Class.forName("java.nio.file.Files"); + Object fileAttributeArray = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0); + Class pathClass = Class.forName("java.nio.file.Path"); + Object tempDir; + if (parentFolder != null) { + Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", pathClass, String.class, fileAttributeArray.getClass()); + Object parentPath = File.class.getDeclaredMethod("toPath").invoke(parentFolder); + tempDir = createTempDirectoryMethod.invoke(null, parentPath, TMP_PREFIX, fileAttributeArray); + } else { + Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", String.class, fileAttributeArray.getClass()); + tempDir = createTempDirectoryMethod.invoke(null, TMP_PREFIX, fileAttributeArray); + } + return (File) pathClass.getDeclaredMethod("toFile").invoke(tempDir); + } + + private static File createTemporaryFolderWithFileApi(File parentFolder) throws IOException { + File createdFolder = null; + for (int i = 0; i < TEMP_DIR_ATTEMPTS; ++i) { + // Use createTempFile to get a suitable folder name. + String suffix = ".tmp"; + File tmpFile = File.createTempFile(TMP_PREFIX, suffix, parentFolder); + String tmpName = tmpFile.toString(); + // Discard .tmp suffix of tmpName. + String folderName = tmpName.substring(0, tmpName.length() - suffix.length()); + createdFolder = new File(folderName); + if (createdFolder.mkdir()) { + tmpFile.delete(); + return createdFolder; + } + tmpFile.delete(); + } + throw new IOException("Unable to create temporary directory in: " + + parentFolder.toString() + ". Tried " + TEMP_DIR_ATTEMPTS + " times. " + + "Last attempted to create: " + createdFolder.toString()); } /** @@ -150,21 +304,48 @@ public class TemporaryFolder extends ExternalResource { /** * Delete all files and folders under the temporary folder. Usually not - * called directly, since it is automatically applied by the {@link Rule} + * called directly, since it is automatically applied by the {@link Rule}. + * + * @throws AssertionError if unable to clean up resources + * and deletion of resources is assured. */ public void delete() { - if (folder != null) { - recursiveDelete(folder); + if (!tryDelete()) { + if (assureDeletion) { + fail("Unable to clean up temporary folder " + folder); + } + } + } + + /** + * Tries to delete all files and folders under the temporary folder and + * returns whether deletion was successful or not. + * + * @return {@code true} if all resources are deleted successfully, + * {@code false} otherwise. + */ + private boolean tryDelete() { + if (folder == null) { + return true; } + + return recursiveDelete(folder); } - private void recursiveDelete(File file) { + private boolean recursiveDelete(File file) { + // Try deleting file before assuming file is a directory + // to prevent following symbolic links. + if (file.delete()) { + return true; + } File[] files = file.listFiles(); if (files != null) { for (File each : files) { - recursiveDelete(each); + if (!recursiveDelete(each)) { + return false; + } } } - file.delete(); + return file.delete(); } } diff --git a/src/main/java/org/junit/rules/TestName.java b/src/main/java/org/junit/rules/TestName.java index bf72602..e2ebc2e 100644 --- a/src/main/java/org/junit/rules/TestName.java +++ b/src/main/java/org/junit/rules/TestName.java @@ -25,7 +25,7 @@ import org.junit.runner.Description; * @since 4.7 */ public class TestName extends TestWatcher { - private String name; + private volatile String name; @Override protected void starting(Description d) { diff --git a/src/main/java/org/junit/rules/TestWatcher.java b/src/main/java/org/junit/rules/TestWatcher.java index 5492b6b..a28514d 100644 --- a/src/main/java/org/junit/rules/TestWatcher.java +++ b/src/main/java/org/junit/rules/TestWatcher.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import org.junit.AssumptionViolatedException; +import org.junit.Rule; import org.junit.runner.Description; import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; @@ -17,7 +18,7 @@ import org.junit.runners.model.Statement; * public static class WatchmanTest { * private static String watchedLog; * - * @Rule + * @Rule(order = Integer.MIN_VALUE) * public TestWatcher watchman= new TestWatcher() { * @Override * protected void failed(Throwable e, Description description) { @@ -40,6 +41,11 @@ import org.junit.runners.model.Statement; * } * } * + *

    It is recommended to always set the {@link Rule#order() order} of the + * {@code TestWatcher} to {@code Integer.MIN_VALUE} so that it encloses all + * other rules. Otherwise it may see failed tests as successful and vice versa + * if some rule changes the result of a test (e.g. {@link ErrorCollector} or + * {@link ExpectedException}). * * @since 4.9 */ @@ -54,7 +60,7 @@ public abstract class TestWatcher implements TestRule { try { base.evaluate(); succeededQuietly(description, errors); - } catch (@SuppressWarnings("deprecation") org.junit.internal.AssumptionViolatedException e) { + } catch (org.junit.internal.AssumptionViolatedException e) { errors.add(e); skippedQuietly(e, description, errors); } catch (Throwable e) { @@ -87,7 +93,6 @@ public abstract class TestWatcher implements TestRule { } } - @SuppressWarnings("deprecation") private void skippedQuietly( org.junit.internal.AssumptionViolatedException e, Description description, List errors) { @@ -135,7 +140,6 @@ public abstract class TestWatcher implements TestRule { /** * 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; diff --git a/src/main/java/org/junit/rules/Timeout.java b/src/main/java/org/junit/rules/Timeout.java index 8d382df..334a923 100644 --- a/src/main/java/org/junit/rules/Timeout.java +++ b/src/main/java/org/junit/rules/Timeout.java @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit; * public static class HasGlobalLongTimeout { * * @Rule - * public Timeout globalTimeout= new Timeout(20); + * public Timeout globalTimeout = Timeout.millis(20); * * @Test * public void run1() throws InterruptedException { @@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit; public class Timeout implements TestRule { private final long timeout; private final TimeUnit timeUnit; + private final boolean lookForStuckThread; /** * Returns a new builder for building an instance. @@ -79,10 +80,11 @@ public class Timeout implements TestRule { public Timeout(long timeout, TimeUnit timeUnit) { this.timeout = timeout; this.timeUnit = timeUnit; + lookForStuckThread = false; } /** - * Create a {@code Timeout} instance initialized with values form + * Create a {@code Timeout} instance initialized with values from * a builder. * * @since 4.12 @@ -90,6 +92,7 @@ public class Timeout implements TestRule { protected Timeout(Builder builder) { timeout = builder.getTimeout(); timeUnit = builder.getTimeUnit(); + lookForStuckThread = builder.getLookingForStuckThread(); } /** @@ -121,6 +124,16 @@ public class Timeout implements TestRule { 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 @@ -133,6 +146,7 @@ public class Timeout implements TestRule { Statement statement) throws Exception { return FailOnTimeout.builder() .withTimeout(timeout, timeUnit) + .withLookingForStuckThread(lookForStuckThread) .build(statement); } @@ -190,6 +204,25 @@ public class Timeout implements TestRule { 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., */ diff --git a/src/main/java/org/junit/runner/Computer.java b/src/main/java/org/junit/runner/Computer.java index 8bb4b20..18d0d31 100644 --- a/src/main/java/org/junit/runner/Computer.java +++ b/src/main/java/org/junit/runner/Computer.java @@ -30,7 +30,17 @@ public class Computer { public Runner runnerForClass(Class testClass) throws Throwable { return getRunner(builder, testClass); } - }, classes); + }, classes) { + @Override + protected String getName() { + /* + * #1320 The generated suite is not based on a real class so + * only a 'null' description can be generated from it. This name + * will be overridden here. + */ + return "classes"; + } + }; } /** diff --git a/src/main/java/org/junit/runner/Describable.java b/src/main/java/org/junit/runner/Describable.java index 1514141..293fdb3 100644 --- a/src/main/java/org/junit/runner/Describable.java +++ b/src/main/java/org/junit/runner/Describable.java @@ -10,5 +10,5 @@ public interface Describable { /** * @return a {@link Description} showing the tests to be run by the receiver */ - public abstract Description getDescription(); + Description getDescription(); } \ No newline at end of file diff --git a/src/main/java/org/junit/runner/Description.java b/src/main/java/org/junit/runner/Description.java index fe47eac..0846a1e 100644 --- a/src/main/java/org/junit/runner/Description.java +++ b/src/main/java/org/junit/runner/Description.java @@ -124,6 +124,17 @@ public class Description implements Serializable { return new Description(testClass, testClass.getName(), testClass.getAnnotations()); } + /** + * Create a Description named after testClass + * + * @param testClass A not null {@link Class} containing tests + * @param annotations meta-data about the test, for downstream interpreters + * @return a Description of testClass + */ + public static Description createSuiteDescription(Class testClass, Annotation... annotations) { + return new Description(testClass, testClass.getName(), annotations); + } + /** * Describes a Runner which runs no tests */ @@ -139,7 +150,7 @@ public class Description implements Serializable { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final Collection fChildren = new ConcurrentLinkedQueue(); private final String fDisplayName; diff --git a/src/main/java/org/junit/runner/FilterFactory.java b/src/main/java/org/junit/runner/FilterFactory.java index 57b4eaa..e2bfb73 100644 --- a/src/main/java/org/junit/runner/FilterFactory.java +++ b/src/main/java/org/junit/runner/FilterFactory.java @@ -16,7 +16,8 @@ public interface FilterFactory { /** * Exception thrown if the {@link Filter} cannot be created. */ - public static class FilterNotCreatedException extends Exception { + @SuppressWarnings("serial") + class FilterNotCreatedException extends Exception { public FilterNotCreatedException(Exception exception) { super(exception.getMessage(), exception); } diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java index 434157c..3383407 100644 --- a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java +++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java @@ -85,13 +85,11 @@ class JUnitCommandLineParseResult { } private String[] copyArray(String[] args, int from, int to) { - ArrayList result = new ArrayList(); - + String[] result = new String[to - from]; for (int j = from; j != to; ++j) { - result.add(args[j]); + result[j - from] = args[j]; } - - return result.toArray(new String[result.size()]); + return result; } void parseParameters(String[] args) { diff --git a/src/main/java/org/junit/runner/OrderWith.java b/src/main/java/org/junit/runner/OrderWith.java new file mode 100644 index 0000000..e8470c9 --- /dev/null +++ b/src/main/java/org/junit/runner/OrderWith.java @@ -0,0 +1,28 @@ +package org.junit.runner; + +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 org.junit.runner.manipulation.Ordering; +import org.junit.validator.ValidateWith; + +/** + * When a test class is annotated with @OrderWith or extends a class annotated + * with @OrderWith, JUnit will order the tests in the test class (and child + * test classes, if any) using the ordering defined by the {@link Ordering} class. + * + * @since 4.13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@ValidateWith(OrderWithValidator.class) +public @interface OrderWith { + /** + * Gets a class that extends {@link Ordering}. The class must have a public no-arg constructor. + */ + Class value(); +} diff --git a/src/main/java/org/junit/runner/OrderWithValidator.java b/src/main/java/org/junit/runner/OrderWithValidator.java new file mode 100644 index 0000000..f8eab25 --- /dev/null +++ b/src/main/java/org/junit/runner/OrderWithValidator.java @@ -0,0 +1,38 @@ +package org.junit.runner; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.List; + +import org.junit.FixMethodOrder; +import org.junit.runners.model.TestClass; +import org.junit.validator.AnnotationValidator; + +/** + * Validates that there are no errors in the use of the {@code OrderWith} + * annotation. If there is, a {@code Throwable} object will be added to the list + * of errors. + * + * @since 4.13 + */ +public final class OrderWithValidator extends AnnotationValidator { + + /** + * Adds to {@code errors} a throwable for each problem detected. Looks for + * {@code FixMethodOrder} annotations. + * + * @param testClass that is being validated + * @return A list of exceptions detected + * + * @since 4.13 + */ + @Override + public List validateAnnotatedClass(TestClass testClass) { + if (testClass.getAnnotation(FixMethodOrder.class) != null) { + return singletonList( + new Exception("@FixMethodOrder cannot be combined with @OrderWith")); + } + return emptyList(); + } +} diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java index 79c0f1e..7b9a990 100644 --- a/src/main/java/org/junit/runner/Request.java +++ b/src/main/java/org/junit/runner/Request.java @@ -5,9 +5,11 @@ import java.util.Comparator; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.internal.requests.ClassRequest; import org.junit.internal.requests.FilterRequest; +import org.junit.internal.requests.OrderingRequest; import org.junit.internal.requests.SortingRequest; import org.junit.internal.runners.ErrorReportingRunner; import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Ordering; import org.junit.runners.model.InitializationError; /** @@ -71,12 +73,11 @@ public abstract class Request { */ public static Request classes(Computer computer, Class... classes) { try { - AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); + AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(); 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"); + return runner(new ErrorReportingRunner(e, classes)); } } @@ -132,13 +133,16 @@ public abstract class Request { } /** - * Returns a Request that only runs contains tests whose {@link Description} - * equals desiredDescription + * Returns a Request that only runs tests whose {@link Description} + * matches the given description. * - * @param desiredDescription {@link Description} of those tests that should be run + *

    Returns an empty {@code Request} if {@code desiredDescription} is not a single test and filters all but the single + * test if {@code desiredDescription} is a single test.

    + * + * @param desiredDescription {@code Description} of those tests that should be run * @return the filtered Request */ - public Request filterWith(final Description desiredDescription) { + public Request filterWith(Description desiredDescription) { return filterWith(Filter.matchMethodDescription(desiredDescription)); } @@ -149,15 +153,15 @@ public abstract class Request { * For example, here is code to run a test suite in alphabetical order: *
          * private static Comparator<Description> forward() {
    -     * return new Comparator<Description>() {
    -     * public int compare(Description o1, Description o2) {
    -     * return o1.getDisplayName().compareTo(o2.getDisplayName());
    -     * }
    -     * };
    +     *   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()));
    +     *   new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
          * }
          * 
    * @@ -167,4 +171,32 @@ public abstract class Request { public Request sortWith(Comparator comparator) { return new SortingRequest(this, comparator); } + + /** + * Returns a Request whose Tests can be run in a certain order, defined by + * ordering + *

    + * For example, here is code to run a test suite in reverse order: + *

    +     * private static Ordering reverse() {
    +     *   return new Ordering() {
    +     *     public List<Description> orderItems(Collection<Description> descriptions) {
    +     *       List<Description> ordered = new ArrayList<>(descriptions);
    +     *       Collections.reverse(ordered);
    +     *       return ordered;
    +     *     }
    +     *   }
    +     * }
    +     *     
    +     * public static main() {
    +     *   new JUnitCore().run(Request.aClass(AllTests.class).orderWith(reverse()));
    +     * }
    +     * 
    + * + * @return a Request with ordered Tests + * @since 4.13 + */ + public Request orderWith(Ordering ordering) { + return new OrderingRequest(this, ordering); + } } diff --git a/src/main/java/org/junit/runner/Result.java b/src/main/java/org/junit/runner/Result.java index 73ad059..4b5f4a4 100644 --- a/src/main/java/org/junit/runner/Result.java +++ b/src/main/java/org/junit/runner/Result.java @@ -28,6 +28,7 @@ public class Result implements Serializable { ObjectStreamClass.lookup(SerializedForm.class).getFields(); private final AtomicInteger count; private final AtomicInteger ignoreCount; + private final AtomicInteger assumptionFailureCount; private final CopyOnWriteArrayList failures; private final AtomicLong runTime; private final AtomicLong startTime; @@ -38,6 +39,7 @@ public class Result implements Serializable { public Result() { count = new AtomicInteger(); ignoreCount = new AtomicInteger(); + assumptionFailureCount = new AtomicInteger(); failures = new CopyOnWriteArrayList(); runTime = new AtomicLong(); startTime = new AtomicLong(); @@ -46,34 +48,35 @@ public class Result implements Serializable { private Result(SerializedForm serializedForm) { count = serializedForm.fCount; ignoreCount = serializedForm.fIgnoreCount; + assumptionFailureCount = serializedForm.assumptionFailureCount; failures = new CopyOnWriteArrayList(serializedForm.fFailures); runTime = new AtomicLong(serializedForm.fRunTime); startTime = new AtomicLong(serializedForm.fStartTime); } /** - * @return the number of tests run + * Returns the number of tests run */ public int getRunCount() { return count.get(); } /** - * @return the number of tests that failed during the run + * Returns 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 + * Returns 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 + * Returns the {@link Failure}s describing tests that failed and the problems they encountered */ public List getFailures() { return failures; @@ -86,6 +89,20 @@ public class Result implements Serializable { return ignoreCount.get(); } + /** + * Returns the number of tests skipped because of an assumption failure + * + * @throws UnsupportedOperationException if the result was serialized in a version before JUnit 4.13 + * @since 4.13 + */ + public int getAssumptionFailureCount() { + if (assumptionFailureCount == null) { + throw new UnsupportedOperationException( + "Result was serialized from a version of JUnit that doesn't support this method"); + } + return assumptionFailureCount.get(); + } + /** * @return true if all tests succeeded */ @@ -137,7 +154,7 @@ public class Result implements Serializable { @Override public void testAssumptionFailure(Failure failure) { - // do nothing: same as passing (for 4.5; may change in 4.6) + assumptionFailureCount.getAndIncrement(); } } @@ -156,6 +173,7 @@ public class Result implements Serializable { private static final long serialVersionUID = 1L; private final AtomicInteger fCount; private final AtomicInteger fIgnoreCount; + private final AtomicInteger assumptionFailureCount; private final List fFailures; private final long fRunTime; private final long fStartTime; @@ -163,6 +181,7 @@ public class Result implements Serializable { public SerializedForm(Result result) { fCount = result.count; fIgnoreCount = result.ignoreCount; + assumptionFailureCount = result.assumptionFailureCount; fFailures = Collections.synchronizedList(new ArrayList(result.failures)); fRunTime = result.runTime.longValue(); fStartTime = result.startTime.longValue(); @@ -172,6 +191,7 @@ public class Result implements Serializable { private SerializedForm(ObjectInputStream.GetField fields) throws IOException { fCount = (AtomicInteger) fields.get("fCount", null); fIgnoreCount = (AtomicInteger) fields.get("fIgnoreCount", null); + assumptionFailureCount = (AtomicInteger) fields.get("assumptionFailureCount", null); fFailures = (List) fields.get("fFailures", null); fRunTime = fields.get("fRunTime", 0L); fStartTime = fields.get("fStartTime", 0L); @@ -184,6 +204,7 @@ public class Result implements Serializable { fields.put("fFailures", fFailures); fields.put("fRunTime", fRunTime); fields.put("fStartTime", fStartTime); + fields.put("assumptionFailureCount", assumptionFailureCount); s.writeFields(); } diff --git a/src/main/java/org/junit/runner/manipulation/Alphanumeric.java b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java new file mode 100644 index 0000000..8388d21 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java @@ -0,0 +1,27 @@ +package org.junit.runner.manipulation; + +import java.util.Comparator; + +import org.junit.runner.Description; + +/** + * A sorter that orders tests alphanumerically by test name. + * + * @since 4.13 + */ +public final class Alphanumeric extends Sorter implements Ordering.Factory { + + public Alphanumeric() { + super(COMPARATOR); + } + + public Ordering create(Context context) { + return this; + } + + private static final Comparator COMPARATOR = new Comparator() { + public int compare(Description o1, Description o2) { + return o1.getDisplayName().compareTo(o2.getDisplayName()); + } + }; +} diff --git a/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java new file mode 100644 index 0000000..d9d60f7 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java @@ -0,0 +1,21 @@ +package org.junit.runner.manipulation; + +/** + * Thrown when an ordering does something invalid (like remove or add children) + * + * @since 4.13 + */ +public class InvalidOrderingException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidOrderingException() { + } + + public InvalidOrderingException(String message) { + super(message); + } + + public InvalidOrderingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Orderable.java b/src/main/java/org/junit/runner/manipulation/Orderable.java new file mode 100644 index 0000000..9a12a3b --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Orderable.java @@ -0,0 +1,21 @@ +package org.junit.runner.manipulation; + +/** + * Interface for runners that allow ordering of tests. + * + *

    Beware of using this interface 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.13 + */ +public interface Orderable extends Sortable { + + /** + * Orders the tests using orderer + * + * @throws InvalidOrderingException if orderer does something invalid (like remove or add + * children) + */ + void order(Orderer orderer) throws InvalidOrderingException; +} diff --git a/src/main/java/org/junit/runner/manipulation/Orderer.java b/src/main/java/org/junit/runner/manipulation/Orderer.java new file mode 100644 index 0000000..eb13054 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Orderer.java @@ -0,0 +1,62 @@ +package org.junit.runner.manipulation; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.runner.Description; + +/** + * Orders tests. + * + * @since 4.13 + */ +public final class Orderer { + private final Ordering ordering; + + Orderer(Ordering delegate) { + this.ordering = delegate; + } + + /** + * Orders the descriptions. + * + * @return descriptions in order + */ + public List order(Collection descriptions) + throws InvalidOrderingException { + List inOrder = ordering.orderItems( + Collections.unmodifiableCollection(descriptions)); + if (!ordering.validateOrderingIsCorrect()) { + return inOrder; + } + + Set uniqueDescriptions = new HashSet(descriptions); + if (!uniqueDescriptions.containsAll(inOrder)) { + throw new InvalidOrderingException("Ordering added items"); + } + Set resultAsSet = new HashSet(inOrder); + if (resultAsSet.size() != inOrder.size()) { + throw new InvalidOrderingException("Ordering duplicated items"); + } else if (!resultAsSet.containsAll(uniqueDescriptions)) { + throw new InvalidOrderingException("Ordering removed items"); + } + + return inOrder; + } + + /** + * Order the tests in target. + * + * @throws InvalidOrderingException if ordering does something invalid (like remove or add + * children) + */ + public void apply(Object target) throws InvalidOrderingException { + if (target instanceof Orderable) { + Orderable orderable = (Orderable) target; + orderable.order(this); + } + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Ordering.java b/src/main/java/org/junit/runner/manipulation/Ordering.java new file mode 100644 index 0000000..0d0ce93 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Ordering.java @@ -0,0 +1,172 @@ +package org.junit.runner.manipulation; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.junit.runner.Description; +import org.junit.runner.OrderWith; + +/** + * Reorders tests. An {@code Ordering} can reverse the order of tests, sort the + * order or even shuffle the order. + * + *

    In general you will not need to use a Ordering directly. + * Instead, use {@link org.junit.runner.Request#orderWith(Ordering)}. + * + * @since 4.13 + */ +public abstract class Ordering { + private static final String CONSTRUCTOR_ERROR_FORMAT + = "Ordering class %s should have a public constructor with signature " + + "%s(Ordering.Context context)"; + + /** + * Creates an {@link Ordering} that shuffles the items using the given + * {@link Random} instance. + */ + public static Ordering shuffledBy(final Random random) { + return new Ordering() { + @Override + boolean validateOrderingIsCorrect() { + return false; + } + + @Override + protected List orderItems(Collection descriptions) { + List shuffled = new ArrayList(descriptions); + Collections.shuffle(shuffled, random); + return shuffled; + } + }; + } + + /** + * Creates an {@link Ordering} from the given factory class. The class must have a public no-arg + * constructor. + * + * @param factoryClass class to use to create the ordering + * @param annotatedTestClass test class that is annotated with {@link OrderWith}. + * @throws InvalidOrderingException if the instance could not be created + */ + public static Ordering definedBy( + Class factoryClass, Description annotatedTestClass) + throws InvalidOrderingException { + if (factoryClass == null) { + throw new NullPointerException("factoryClass cannot be null"); + } + if (annotatedTestClass == null) { + throw new NullPointerException("annotatedTestClass cannot be null"); + } + + Ordering.Factory factory; + try { + Constructor constructor = factoryClass.getConstructor(); + factory = constructor.newInstance(); + } catch (NoSuchMethodException e) { + throw new InvalidOrderingException(String.format( + CONSTRUCTOR_ERROR_FORMAT, + getClassName(factoryClass), + factoryClass.getSimpleName())); + } catch (Exception e) { + throw new InvalidOrderingException( + "Could not create ordering for " + annotatedTestClass, e); + } + return definedBy(factory, annotatedTestClass); + } + + /** + * Creates an {@link Ordering} from the given factory. + * + * @param factory factory to use to create the ordering + * @param annotatedTestClass test class that is annotated with {@link OrderWith}. + * @throws InvalidOrderingException if the instance could not be created + */ + public static Ordering definedBy( + Ordering.Factory factory, Description annotatedTestClass) + throws InvalidOrderingException { + if (factory == null) { + throw new NullPointerException("factory cannot be null"); + } + if (annotatedTestClass == null) { + throw new NullPointerException("annotatedTestClass cannot be null"); + } + + return factory.create(new Ordering.Context(annotatedTestClass)); + } + + private static String getClassName(Class clazz) { + String name = clazz.getCanonicalName(); + if (name == null) { + return clazz.getName(); + } + return name; + } + + /** + * Order the tests in target using this ordering. + * + * @throws InvalidOrderingException if ordering does something invalid (like remove or add + * children) + */ + public void apply(Object target) throws InvalidOrderingException { + /* + * Note that some subclasses of Ordering override apply(). The Sorter + * subclass of Ordering overrides apply() to apply the sort (this is + * done because sorting is more efficient than ordering). + */ + if (target instanceof Orderable) { + Orderable orderable = (Orderable) target; + orderable.order(new Orderer(this)); + } + } + + /** + * Returns {@code true} if this ordering could produce invalid results (i.e. + * if it could add or remove values). + */ + boolean validateOrderingIsCorrect() { + return true; + } + + /** + * Implemented by sub-classes to order the descriptions. + * + * @return descriptions in order + */ + protected abstract List orderItems(Collection descriptions); + + /** Context about the ordering being applied. */ + public static class Context { + private final Description description; + + /** + * Gets the description for the top-level target being ordered. + */ + public Description getTarget() { + return description; + } + + private Context(Description description) { + this.description = description; + } + } + + /** + * Factory for creating {@link Ordering} instances. + * + *

    For a factory to be used with {@code @OrderWith} it needs to have a public no-arg + * constructor. + */ + public interface Factory { + /** + * Creates an Ordering instance using the given context. Implementations + * of this method that do not need to use the context can return the + * same instance every time. + */ + Ordering create(Context context); + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Sortable.java b/src/main/java/org/junit/runner/manipulation/Sortable.java index 9ac864c..0c59f33 100644 --- a/src/main/java/org/junit/runner/manipulation/Sortable.java +++ b/src/main/java/org/junit/runner/manipulation/Sortable.java @@ -15,6 +15,6 @@ public interface Sortable { * * @param sorter the {@link Sorter} to use for sorting the tests */ - public void sort(Sorter sorter); + 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 20192d0..4b5274c 100644 --- a/src/main/java/org/junit/runner/manipulation/Sorter.java +++ b/src/main/java/org/junit/runner/manipulation/Sorter.java @@ -1,16 +1,21 @@ package org.junit.runner.manipulation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import org.junit.runner.Description; /** * A Sorter orders tests. In general you will not need - * to use a Sorter directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * to use a Sorter directly. Instead, use + * {@link org.junit.runner.Request#sortWith(Comparator)}. * * @since 4.0 */ -public class Sorter implements Comparator { +public class Sorter extends Ordering implements Comparator { /** * NULL is a Sorter that leaves elements in an undefined order */ @@ -27,17 +32,26 @@ public class Sorter implements Comparator { * to sort tests * * @param comparator the {@link Comparator} to use when sorting tests + * @since 4.0 */ public Sorter(Comparator comparator) { this.comparator = comparator; } /** - * Sorts the test in runner using comparator + * Sorts the tests in target using comparator. + * + * @since 4.0 */ - public void apply(Object object) { - if (object instanceof Sortable) { - Sortable sortable = (Sortable) object; + @Override + public void apply(Object target) { + /* + * Note that all runners that are Orderable are also Sortable (because + * Orderable extends Sortable). Sorting is more efficient than ordering, + * so we override the parent behavior so we sort instead. + */ + if (target instanceof Sortable) { + Sortable sortable = (Sortable) target; sortable.sort(this); } } @@ -45,4 +59,32 @@ public class Sorter implements Comparator { public int compare(Description o1, Description o2) { return comparator.compare(o1, o2); } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + @Override + protected final List orderItems(Collection descriptions) { + /* + * In practice, we will never get here--Sorters do their work in the + * compare() method--but the Liskov substitution principle demands that + * we obey the general contract of Orderable. Luckily, it's trivial to + * implement. + */ + List sorted = new ArrayList(descriptions); + Collections.sort(sorted, this); // Note: it would be incorrect to pass in "comparator" + return sorted; + } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + @Override + boolean validateOrderingIsCorrect() { + return false; + } } diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java index c03b4c1..4551302 100644 --- a/src/main/java/org/junit/runner/notification/Failure.java +++ b/src/main/java/org/junit/runner/notification/Failure.java @@ -1,9 +1,8 @@ package org.junit.runner.notification; -import java.io.PrintWriter; import java.io.Serializable; -import java.io.StringWriter; +import org.junit.internal.Throwables; import org.junit.runner.Description; /** @@ -21,7 +20,7 @@ public class Failure implements Serializable { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final Description fDescription; private final Throwable fThrownException; @@ -65,15 +64,19 @@ public class Failure implements Serializable { } /** - * Convenience method - * - * @return the printed form of the exception + * Gets the printed form of the exception and its stack trace. */ public String getTrace() { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - getException().printStackTrace(writer); - return stringWriter.toString(); + return Throwables.getStacktrace(getException()); + } + + /** + * Gets a the printed form of the exception, with a trimmed version of the stack trace. + * This method will attempt to filter out frames of the stack trace that are below + * the test method call. + */ + public String getTrimmedTrace() { + return Throwables.getTrimmedStackTrace(getException()); } /** diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java index db9d8c1..d7cac00 100644 --- a/src/main/java/org/junit/runner/notification/RunListener.java +++ b/src/main/java/org/junit/runner/notification/RunListener.java @@ -69,6 +69,34 @@ public class RunListener { public void testRunFinished(Result result) throws Exception { } + /** + * Called when a test suite is about to be started. If this method is + * called for a given {@link Description}, then {@link #testSuiteFinished(Description)} + * will also be called for the same {@code Description}. + * + *

    Note that not all runners will call this method, so runners should + * be prepared to handle {@link #testStarted(Description)} calls for tests + * where there was no corresponding {@code testSuiteStarted()} call for + * the parent {@code Description}. + * + * @param description the description of the test suite that is about to be run + * (generally a class name) + * @since 4.13 + */ + public void testSuiteStarted(Description description) throws Exception { + } + + /** + * Called when a test suite has finished, whether the test suite succeeds or fails. + * This method will not be called for a given {@link Description} unless + * {@link #testSuiteStarted(Description)} was called for the same @code Description}. + * + * @param description the description of the test suite that just ran + * @since 4.13 + */ + public void testSuiteFinished(Description description) throws Exception { + } + /** * Called when an atomic test is about to be started. * diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java index 6875f76..752fa3b 100644 --- a/src/main/java/org/junit/runner/notification/RunNotifier.java +++ b/src/main/java/org/junit/runner/notification/RunNotifier.java @@ -65,8 +65,8 @@ public class RunNotifier { void run() { int capacity = currentListeners.size(); - ArrayList safeListeners = new ArrayList(capacity); - ArrayList failures = new ArrayList(capacity); + List safeListeners = new ArrayList(capacity); + List failures = new ArrayList(capacity); for (RunListener listener : currentListeners) { try { notifyListener(listener); @@ -78,7 +78,7 @@ public class RunNotifier { fireTestFailures(safeListeners, failures); } - abstract protected void notifyListener(RunListener each) throws Exception; + protected abstract void notifyListener(RunListener each) throws Exception; } /** @@ -105,6 +105,41 @@ public class RunNotifier { }.run(); } + /** + * Invoke to tell listeners that a test suite is about to start. Runners are strongly + * encouraged--but not required--to call this method. If this method is called for + * a given {@link Description} then {@link #fireTestSuiteFinished(Description)} MUST + * be called for the same {@code Description}. + * + * @param description the description of the suite test (generally a class name) + * @since 4.13 + */ + public void fireTestSuiteStarted(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testSuiteStarted(description); + } + }.run(); + } + + /** + * Invoke to tell listeners that a test suite is about to finish. Always invoke + * this method if you invoke {@link #fireTestSuiteStarted(Description)} + * as listeners are likely to expect them to come in pairs. + * + * @param description the description of the suite test (generally a class name) + * @since 4.13 + */ + public void fireTestSuiteFinished(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testSuiteFinished(description); + } + }.run(); + } + /** * Invoke to tell listeners that an atomic test is about to start. * diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java index c53c1ee..400fed8 100644 --- a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java +++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java @@ -10,7 +10,7 @@ import org.junit.runner.Result; *

    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 + * synchronized, clients that added multiple listeners that called common code might see * issues due to the reduced synchronization. * * @author Tibor Digana (tibor17) @@ -43,6 +43,37 @@ final class SynchronizedRunListener extends RunListener { } } + /** + * {@inheritDoc} + *

    + * Synchronized decorator for {@link RunListener#testSuiteStarted(Description)}. + * @param description the description of the test suite that is about to be run + * (generally a class name). + * @throws Exception if any occurs. + * @since 4.13 + */ + @Override + public void testSuiteStarted(Description description) throws Exception { + synchronized (monitor) { + listener.testSuiteStarted(description); + } + } + + /** + * {@inheritDoc} + *

    + * Synchronized decorator for {@link RunListener#testSuiteFinished(Description)}. + * @param description the description of the test suite that just ran. + * @throws Exception + * @since 4.13 + */ + @Override + public void testSuiteFinished(Description description) throws Exception { + synchronized (monitor) { + listener.testSuiteFinished(description); + } + } + @Override public void testStarted(Description description) throws Exception { synchronized (monitor) { diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java index 4d06199..455341a 100644 --- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -3,8 +3,10 @@ package org.junit.runners; import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR; import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.junit.After; @@ -21,14 +23,18 @@ 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; import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; +import org.junit.runners.model.MemberValueConsumer; import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; +import org.junit.validator.PublicClassValidator; +import org.junit.validator.TestClassValidator; /** * Implements the JUnit 4 standard test case class model, as defined by the @@ -55,14 +61,27 @@ import org.junit.runners.model.Statement; * @since 4.5 */ public class BlockJUnit4ClassRunner extends ParentRunner { - private final ConcurrentHashMap methodDescriptions = new ConcurrentHashMap(); + private static TestClassValidator PUBLIC_CLASS_VALIDATOR = new PublicClassValidator(); + + private final ConcurrentMap methodDescriptions = new ConcurrentHashMap(); + + /** + * Creates a BlockJUnit4ClassRunner to run {@code testClass} + * + * @throws InitializationError if the test class is malformed. + */ + public BlockJUnit4ClassRunner(Class testClass) throws InitializationError { + super(testClass); + } + /** - * Creates a BlockJUnit4ClassRunner to run {@code klass} + * Creates a BlockJUnit4ClassRunner to run {@code testClass}. * * @throws InitializationError if the test class is malformed. + * @since 4.13 */ - public BlockJUnit4ClassRunner(Class klass) throws InitializationError { - super(klass); + protected BlockJUnit4ClassRunner(TestClass testClass) throws InitializationError { + super(testClass); } // @@ -75,10 +94,16 @@ public class BlockJUnit4ClassRunner extends ParentRunner { if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { - runLeaf(methodBlock(method), description, notifier); + Statement statement = new Statement() { + @Override + public void evaluate() throws Throwable { + methodBlock(method).evaluate(); + } + }; + runLeaf(statement, description, notifier); } } - + /** * Evaluates whether {@link FrameworkMethod}s are ignored based on the * {@link Ignore} annotation. @@ -123,6 +148,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner { protected void collectInitializationErrors(List errors) { super.collectInitializationErrors(errors); + validatePublicConstructor(errors); validateNoNonStaticInnerClass(errors); validateConstructor(errors); validateInstanceMethods(errors); @@ -130,6 +156,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner { validateMethods(errors); } + private void validatePublicConstructor(List errors) { + if (getTestClass().getJavaClass() != null) { + errors.addAll(PUBLIC_CLASS_VALIDATOR.validateTestClass(getTestClass())); + } + } + protected void validateNoNonStaticInnerClass(List errors) { if (getTestClass().isANonStaticInnerClass()) { String gripe = "The inner class " + getTestClass().getName() @@ -180,6 +212,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner { * 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 */ @Deprecated protected void validateInstanceMethods(List errors) { @@ -187,7 +220,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner { validatePublicVoidNoArgMethods(Before.class, false, errors); validateTestMethods(errors); - if (computeTestMethods().size() == 0) { + if (computeTestMethods().isEmpty()) { errors.add(new Exception("No runnable methods")); } } @@ -217,6 +250,16 @@ public class BlockJUnit4ClassRunner extends ParentRunner { return getTestClass().getOnlyConstructor().newInstance(); } + /** + * Returns a new fixture to run a particular test {@code method} against. + * Default implementation executes the no-argument {@link #createTest()} method. + * + * @since 4.13 + */ + protected Object createTest(FrameworkMethod method) throws Exception { + return createTest(); + } + /** * Returns the name that describes {@code method} for {@link Description}s. * Default implementation is the method's name @@ -232,10 +275,10 @@ public class BlockJUnit4ClassRunner extends ParentRunner { * Here is an outline of the default implementation: * *

      - *
    • Invoke {@code method} on the result of {@code createTest()}, and + *
    • Invoke {@code method} on the result of {@link #createTest(org.junit.runners.model.FrameworkMethod)}, and * throw any exceptions thrown by either operation. - *
    • HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code - * expecting} attribute, return normally only if the previous step threw an + *
    • HOWEVER, if {@code method}'s {@code @Test} annotation has the {@link Test#expected()} + * attribute, return normally only if the previous step threw an * exception of the correct type, and throw an exception otherwise. *
    • HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code * timeout} attribute, throw an exception if the previous step takes more @@ -257,13 +300,13 @@ public class BlockJUnit4ClassRunner extends ParentRunner { * This can be overridden in subclasses, either by overriding this method, * or the implementations creating each sub-statement. */ - protected Statement methodBlock(FrameworkMethod method) { + protected Statement methodBlock(final FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { - return createTest(); + return createTest(method); } }.run(); } catch (Throwable e) { @@ -276,6 +319,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner { statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); + statement = withInterruptIsolation(statement); return statement; } @@ -292,21 +336,22 @@ public class BlockJUnit4ClassRunner extends ParentRunner { /** * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation - * has the {@code expecting} attribute, return normally only if {@code next} + * has the {@link Test#expected()} 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; + Class expectedExceptionClass = getExpectedException(annotation); + return expectedExceptionClass != null ? new ExpectException(next, expectedExceptionClass) : 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 */ @Deprecated protected Statement withPotentialTimeout(FrameworkMethod method, @@ -348,28 +393,23 @@ public class BlockJUnit4ClassRunner extends ParentRunner { target); } - private Statement withRules(FrameworkMethod method, Object target, - Statement statement) { - List 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 testRules, - Object target, Statement result) { - for (org.junit.rules.MethodRule each : getMethodRules(target)) { - if (!testRules.contains(each)) { - result = each.apply(result, method, target); + private Statement withRules(FrameworkMethod method, Object target, Statement statement) { + RuleContainer ruleContainer = new RuleContainer(); + CURRENT_RULE_CONTAINER.set(ruleContainer); + try { + List testRules = getTestRules(target); + for (MethodRule each : rules(target)) { + if (!(each instanceof TestRule && testRules.contains(each))) { + ruleContainer.add(each); + } } + for (TestRule rule : testRules) { + ruleContainer.add(rule); + } + } finally { + CURRENT_RULE_CONTAINER.remove(); } - return result; - } - - private List getMethodRules(Object target) { - return rules(target); + return ruleContainer.apply(method, describeChild(method), target, statement); } /** @@ -378,27 +418,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner { * test */ protected List rules(Object target) { - List 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 testRules, - Statement statement) { - return testRules.isEmpty() ? statement : - new RunRules(statement, testRules, describeChild(method)); + RuleCollector collector = new RuleCollector(); + getTestClass().collectAnnotatedMethodValues(target, Rule.class, MethodRule.class, + collector); + getTestClass().collectAnnotatedFieldValues(target, Rule.class, MethodRule.class, + collector); + return collector.result; } /** @@ -407,13 +432,10 @@ public class BlockJUnit4ClassRunner extends ParentRunner { * test */ protected List getTestRules(Object target) { - List result = getTestClass().getAnnotatedMethodValues(target, - Rule.class, TestRule.class); - - result.addAll(getTestClass().getAnnotatedFieldValues(target, - Rule.class, TestRule.class)); - - return result; + RuleCollector collector = new RuleCollector(); + getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector); + getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector); + return collector.result; } private Class getExpectedException(Test annotation) { @@ -424,14 +446,28 @@ public class BlockJUnit4ClassRunner extends ParentRunner { } } - private boolean expectsException(Test annotation) { - return getExpectedException(annotation) != null; - } - private long getTimeout(Test annotation) { if (annotation == null) { return 0; } return annotation.timeout(); } + + private static final ThreadLocal CURRENT_RULE_CONTAINER = + new ThreadLocal(); + + private static class RuleCollector implements MemberValueConsumer { + final List result = new ArrayList(); + + public void accept(FrameworkMember member, T value) { + Rule rule = member.getAnnotation(Rule.class); + if (rule != null) { + RuleContainer container = CURRENT_RULE_CONTAINER.get(); + if (container != null) { + container.setOrder(value, rule.order()); + } + } + result.add(value); + } + } } diff --git a/src/main/java/org/junit/runners/JUnit4.java b/src/main/java/org/junit/runners/JUnit4.java index 6ba28c2..28eafb3 100644 --- a/src/main/java/org/junit/runners/JUnit4.java +++ b/src/main/java/org/junit/runners/JUnit4.java @@ -1,6 +1,7 @@ package org.junit.runners; import org.junit.runners.model.InitializationError; +import org.junit.runners.model.TestClass; /** * Aliases the current default JUnit 4 class runner, for future-proofing. If @@ -19,6 +20,6 @@ public final class JUnit4 extends BlockJUnit4ClassRunner { * Constructs a new instance of the default runner */ public JUnit4(Class klass) throws InitializationError { - super(klass); + super(new TestClass(klass)); } } diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java index 829c8f0..d11b66a 100644 --- a/src/main/java/org/junit/runners/Parameterized.java +++ b/src/main/java/org/junit/runners/Parameterized.java @@ -1,5 +1,6 @@ package org.junit.runners; +import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -8,12 +9,18 @@ import java.lang.annotation.Target; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.Result; import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; +import org.junit.runners.model.InvalidTestClassError; import org.junit.runners.model.TestClass; import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; import org.junit.runners.parameterized.ParametersRunnerFactory; @@ -24,34 +31,37 @@ import org.junit.runners.parameterized.TestWithParameters; * When running a parameterized test class, instances are created for the * cross-product of the test methods and the test data elements. *

      - * For example, to test a Fibonacci function, write: + * For example, to test the + operator, write: *

        * @RunWith(Parameterized.class)
      - * public class FibonacciTest {
      - *     @Parameters(name= "{index}: fib[{0}]={1}")
      + * public class AdditionTest {
      + *     @Parameters(name = "{index}: {0} + {1} = {2}")
        *     public static Iterable<Object[]> data() {
      - *         return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
      - *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
      + *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
      + *                 { 3, 2, 5 }, { 4, 3, 7 } });
        *     }
        *
      - *     private int fInput;
      + *     private int firstSummand;
        *
      - *     private int fExpected;
      + *     private int secondSummand;
        *
      - *     public FibonacciTest(int input, int expected) {
      - *         fInput= input;
      - *         fExpected= expected;
      + *     private int sum;
      + *
      + *     public AdditionTest(int firstSummand, int secondSummand, int sum) {
      + *         this.firstSummand = firstSummand;
      + *         this.secondSummand = secondSummand;
      + *         this.sum = sum;
        *     }
        *
        *     @Test
        *     public void test() {
      - *         assertEquals(fExpected, Fibonacci.compute(fInput));
      + *         assertEquals(sum, firstSummand + secondSummand);
        *     }
        * }
        * 
      *

      - * Each instance of FibonacciTest will be constructed using the - * two-argument constructor and the data values in the + * Each instance of AdditionTest will be constructed using the + * three-argument constructor and the data values in the * @Parameters method. *

      * In order that you can easily identify the individual tests, you may provide a @@ -69,33 +79,36 @@ import org.junit.runners.parameterized.TestWithParameters; * *

      * In the example given above, the Parameterized runner creates - * names like [1: fib(3)=2]. If you don't use the name parameter, + * names like [2: 3 + 2 = 5]. If you don't use the name parameter, * then the current parameter index is used as name. *

      * You can also write: *

        * @RunWith(Parameterized.class)
      - * public class FibonacciTest {
      - *  @Parameters
      - *  public static Iterable<Object[]> data() {
      - *      return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
      - *                 { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
      - *  }
      - *  
      - *  @Parameter(0)
      - *  public int fInput;
      + * public class AdditionTest {
      + *     @Parameters(name = "{index}: {0} + {1} = {2}")
      + *     public static Iterable<Object[]> data() {
      + *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
      + *                 { 3, 2, 5 }, { 4, 3, 7 } });
      + *     }
      + *
      + *     @Parameter(0)
      + *     public int firstSummand;
        *
      - *  @Parameter(1)
      - *  public int fExpected;
      + *     @Parameter(1)
      + *     public int secondSummand;
        *
      - *  @Test
      - *  public void test() {
      - *      assertEquals(fExpected, Fibonacci.compute(fInput));
      - *  }
      + *     @Parameter(2)
      + *     public int sum;
      + *
      + *     @Test
      + *     public void test() {
      + *         assertEquals(sum, firstSummand + secondSummand);
      + *     }
        * }
        * 
      *

      - * Each instance of FibonacciTest will be constructed with the default constructor + * Each instance of AdditionTest will be constructed with the default constructor * and fields annotated by @Parameter will be initialized * with the data values in the @Parameters method. * @@ -105,8 +118,7 @@ import org.junit.runners.parameterized.TestWithParameters; *

        * @Parameters
        * public static Object[][] data() {
      - * 	return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
      - * 			{ 5, 5 }, { 6, 8 } };
      + * 	return new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } };
        * }
        * 
      * @@ -130,6 +142,19 @@ import org.junit.runners.parameterized.TestWithParameters; * } * * + *

      Executing code before/after executing tests for specific parameters

      + *

      + * If your test needs to perform some preparation or cleanup based on the + * parameters, this can be done by adding public static methods annotated with + * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no + * parameters or the same parameters as the test. + *

      + * @BeforeParam
      + * public static void beforeTestsForParameter(String onlyParameter) {
      + *     System.out.println("Testing " + onlyParameter);
      + * }
      + * 
      + * *

      Create different runners

      *

      * By default the {@code Parameterized} runner creates a slightly modified @@ -141,7 +166,7 @@ import org.junit.runners.parameterized.TestWithParameters; * The factory must have a public zero-arg constructor. * *

      - * public class YourRunnerFactory implements ParameterizedRunnerFactory {
      + * public class YourRunnerFactory implements ParametersRunnerFactory {
        *     public Runner createRunnerForTestWithParameters(TestWithParameters test)
        *             throws InitializationError {
        *         return YourRunner(test);
      @@ -160,6 +185,21 @@ import org.junit.runners.parameterized.TestWithParameters;
        * }
        * 
      * + *

      Avoid creating parameters

      + *

      With {@link org.junit.Assume assumptions} you can dynamically skip tests. + * Assumptions are also supported by the @Parameters method. + * Creating parameters is stopped when the assumption fails and none of the + * tests in the test class is executed. JUnit reports a + * {@link Result#getAssumptionFailureCount() single assumption failure} for the + * whole test class in this case. + *

      + * @Parameters
      + * public static Iterable<? extends Object> data() {
      + * 	String os = System.getProperty("os.name").toLowerCase()
      + * 	Assume.assumeTrue(os.contains("win"));
      + * 	return Arrays.asList("first test", "second test");
      + * }
      + * 
      * @since 4.0 */ public class Parameterized extends Suite { @@ -170,7 +210,7 @@ public class Parameterized extends Suite { */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) - public static @interface Parameters { + public @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 @@ -201,7 +241,7 @@ public class Parameterized extends Suite { */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) - public static @interface Parameter { + public @interface Parameter { /** * Method that returns the index of the parameter in the array * returned by the method annotated by Parameters. @@ -230,122 +270,235 @@ public class Parameterized extends Suite { Class value() default BlockJUnit4ClassRunnerWithParametersFactory.class; } - private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); - - private static final List NO_RUNNERS = Collections.emptyList(); + /** + * Annotation for {@code public static void} methods which should be executed before + * evaluating tests with particular parameters. + * + * @see org.junit.BeforeClass + * @see org.junit.Before + * @since 4.13 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface BeforeParam { + } - private final List runners; + /** + * Annotation for {@code public static void} methods which should be executed after + * evaluating tests with particular parameters. + * + * @see org.junit.AfterClass + * @see org.junit.After + * @since 4.13 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface AfterParam { + } /** * 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)); + this(klass, new RunnersFactory(klass)); } - private ParametersRunnerFactory getParametersRunnerFactory(Class klass) - throws InstantiationException, IllegalAccessException { - UseParametersRunnerFactory annotation = klass - .getAnnotation(UseParametersRunnerFactory.class); - if (annotation == null) { - return DEFAULT_FACTORY; - } else { - Class factoryClass = annotation - .value(); - return factoryClass.newInstance(); - } + private Parameterized(Class klass, RunnersFactory runnersFactory) throws Exception { + super(klass, runnersFactory.createRunners()); + validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount); } - @Override - protected List getChildren() { - return runners; + private void validateBeforeParamAndAfterParamMethods(Integer parameterCount) + throws InvalidTestClassError { + List errors = new ArrayList(); + validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors); + validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors); + if (!errors.isEmpty()) { + throw new InvalidTestClassError(getTestClass().getJavaClass(), errors); + } } - 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); + private void validatePublicStaticVoidMethods( + Class annotation, Integer parameterCount, + List errors) { + List methods = getTestClass().getAnnotatedMethods(annotation); + for (FrameworkMethod fm : methods) { + fm.validatePublicVoid(true, errors); + if (parameterCount != null) { + int methodParameterCount = fm.getMethod().getParameterTypes().length; + if (methodParameterCount != 0 && methodParameterCount != parameterCount) { + errors.add(new Exception("Method " + fm.getName() + + "() should have 0 or " + parameterCount + " parameter(s)")); + } + } + } } - @SuppressWarnings("unchecked") - private Iterable allParameters() throws Throwable { - Object parameters = getParametersMethod().invokeExplosively(null); - if (parameters instanceof Iterable) { - return (Iterable) parameters; - } else if (parameters instanceof Object[]) { - return Arrays.asList((Object[]) parameters); - } else { - throw parametersMethodReturnedWrongType(); + private static class AssumptionViolationRunner extends Runner { + private final Description description; + private final AssumptionViolatedException exception; + + AssumptionViolationRunner(TestClass testClass, String methodName, + AssumptionViolatedException exception) { + this.description = Description + .createTestDescription(testClass.getJavaClass(), + methodName + "() assumption violation"); + this.exception = exception; + } + + @Override + public Description getDescription() { + return description; + } + + @Override + public void run(RunNotifier notifier) { + notifier.fireTestAssumptionFailed(new Failure(description, exception)); } } - private FrameworkMethod getParametersMethod() throws Exception { - List methods = getTestClass().getAnnotatedMethods( - Parameters.class); - for (FrameworkMethod each : methods) { - if (each.isStatic() && each.isPublic()) { - return each; + private static class RunnersFactory { + private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); + + private final TestClass testClass; + private final FrameworkMethod parametersMethod; + private final List allParameters; + private final int parameterCount; + private final Runner runnerOverride; + + private RunnersFactory(Class klass) throws Throwable { + testClass = new TestClass(klass); + parametersMethod = getParametersMethod(testClass); + List allParametersResult; + AssumptionViolationRunner assumptionViolationRunner = null; + try { + allParametersResult = allParameters(testClass, parametersMethod); + } catch (AssumptionViolatedException e) { + allParametersResult = Collections.emptyList(); + assumptionViolationRunner = new AssumptionViolationRunner(testClass, + parametersMethod.getName(), e); } + allParameters = allParametersResult; + runnerOverride = assumptionViolationRunner; + parameterCount = + allParameters.isEmpty() ? 0 : normalizeParameters(allParameters.get(0)).length; } - throw new Exception("No public static parameters method on class " - + getTestClass().getName()); - } + private List createRunners() throws Exception { + if (runnerOverride != null) { + return Collections.singletonList(runnerOverride); + } + Parameters parameters = parametersMethod.getAnnotation(Parameters.class); + return Collections.unmodifiableList(createRunnersForParameters( + allParameters, parameters.name(), + getParametersRunnerFactory())); + } - private List createRunnersForParameters( - Iterable allParameters, String namePattern, - ParametersRunnerFactory runnerFactory) - throws InitializationError, - Exception { - try { - List tests = createTestsForParameters( - allParameters, namePattern); - List runners = new ArrayList(); - for (TestWithParameters test : tests) { - runners.add(runnerFactory - .createRunnerForTestWithParameters(test)); + private ParametersRunnerFactory getParametersRunnerFactory() + throws InstantiationException, IllegalAccessException { + UseParametersRunnerFactory annotation = testClass + .getAnnotation(UseParametersRunnerFactory.class); + if (annotation == null) { + return DEFAULT_FACTORY; + } else { + Class factoryClass = annotation + .value(); + return factoryClass.newInstance(); } - return runners; - } catch (ClassCastException e) { - throw parametersMethodReturnedWrongType(); } - } - private List createTestsForParameters( - Iterable allParameters, String namePattern) - throws Exception { - int i = 0; - List children = new ArrayList(); - for (Object parametersOfSingleTest : allParameters) { - children.add(createTestWithNotNormalizedParameters(namePattern, - i++, parametersOfSingleTest)); + private TestWithParameters createTestWithNotNormalizedParameters( + String pattern, int index, Object parametersOrSingleParameter) { + Object[] parameters = normalizeParameters(parametersOrSingleParameter); + return createTestWithParameters(testClass, pattern, index, parameters); } - 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 Object[] normalizeParameters(Object parametersOrSingleParameter) { + return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter + : new Object[] { parametersOrSingleParameter }; + } - 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)); + @SuppressWarnings("unchecked") + private static List allParameters( + TestClass testClass, FrameworkMethod parametersMethod) throws Throwable { + Object parameters = parametersMethod.invokeExplosively(null); + if (parameters instanceof List) { + return (List) parameters; + } else if (parameters instanceof Collection) { + return new ArrayList((Collection) parameters); + } else if (parameters instanceof Iterable) { + List result = new ArrayList(); + for (Object entry : ((Iterable) parameters)) { + result.add(entry); + } + return result; + } else if (parameters instanceof Object[]) { + return Arrays.asList((Object[]) parameters); + } else { + throw parametersMethodReturnedWrongType(testClass, parametersMethod); + } + } + + private static FrameworkMethod getParametersMethod(TestClass testClass) throws Exception { + List methods = testClass + .getAnnotatedMethods(Parameters.class); + for (FrameworkMethod each : methods) { + if (each.isStatic() && each.isPublic()) { + return each; + } + } + + throw new Exception("No public static parameters method on class " + + testClass.getName()); + } + + private List createRunnersForParameters( + Iterable allParameters, String namePattern, + ParametersRunnerFactory runnerFactory) throws Exception { + try { + List tests = createTestsForParameters( + allParameters, namePattern); + List runners = new ArrayList(); + for (TestWithParameters test : tests) { + runners.add(runnerFactory + .createRunnerForTestWithParameters(test)); + } + return runners; + } catch (ClassCastException e) { + throw parametersMethodReturnedWrongType(testClass, parametersMethod); + } + } + + private List createTestsForParameters( + Iterable allParameters, String namePattern) + throws Exception { + int i = 0; + List children = new ArrayList(); + for (Object parametersOfSingleTest : allParameters) { + children.add(createTestWithNotNormalizedParameters(namePattern, + i++, parametersOfSingleTest)); + } + return children; + } + + private static Exception parametersMethodReturnedWrongType( + TestClass testClass, FrameworkMethod parametersMethod) throws Exception { + String className = testClass.getName(); + String methodName = parametersMethod.getName(); + String message = MessageFormat.format( + "{0}.{1}() must return an Iterable of arrays.", className, + methodName); + return new Exception(message); + } + + private 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 old mode 100755 new mode 100644 index 92641bf..0a0e7cb --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -1,21 +1,25 @@ package org.junit.runners; +import static org.junit.internal.Checks.notNull; 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; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Rule; import org.junit.internal.AssumptionViolatedException; @@ -28,18 +32,22 @@ import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.Orderer; +import org.junit.runner.manipulation.InvalidOrderingException; import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Orderable; import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; +import org.junit.runners.model.InvalidTestClassError; +import org.junit.runners.model.MemberValueConsumer; 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; /** @@ -56,15 +64,15 @@ import org.junit.validator.TestClassValidator; * @since 4.5 */ public abstract class ParentRunner extends Runner implements Filterable, - Sortable { - private static final List VALIDATORS = Arrays.asList( - new AnnotationsValidator(), new PublicClassValidator()); + Orderable { + private static final List VALIDATORS = Collections.singletonList( + new AnnotationsValidator()); - private final Object childrenLock = new Object(); + private final Lock childrenLock = new ReentrantLock(); private final TestClass testClass; // Guarded by childrenLock - private volatile Collection filteredChildren = null; + private volatile List filteredChildren = null; private volatile RunnerScheduler scheduler = new RunnerScheduler() { public void schedule(Runnable childStatement) { @@ -84,6 +92,21 @@ public abstract class ParentRunner extends Runner implements Filterable, validate(); } + /** + * Constructs a new {@code ParentRunner} that will run the {@code TestClass}. + * + * @since 4.13 + */ + protected ParentRunner(TestClass testClass) throws InitializationError { + this.testClass = notNull(testClass); + validate(); + } + + /** + * @deprecated Please use {@link #ParentRunner(org.junit.runners.model.TestClass)}. + * @since 4.12 + */ + @Deprecated protected TestClass createTestClass(Class testClass) { return new TestClass(testClass); } @@ -192,6 +215,7 @@ public abstract class ParentRunner extends Runner implements Filterable, statement = withBeforeClasses(statement); statement = withAfterClasses(statement); statement = withClassRules(statement); + statement = withInterruptIsolation(statement); } return statement; } @@ -219,7 +243,7 @@ public abstract class ParentRunner extends Runner implements Filterable, /** * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class - * and superclasses before executing {@code statement}; all AfterClass methods are + * and superclasses after 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}. @@ -251,9 +275,10 @@ public abstract class ParentRunner extends Runner implements Filterable, * each method in the tested class. */ protected List classRules() { - List result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class); - result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class)); - return result; + ClassRuleCollector collector = new ClassRuleCollector(); + testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector); + testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector); + return collector.getOrderedRules(); } /** @@ -270,6 +295,22 @@ public abstract class ParentRunner extends Runner implements Filterable, }; } + /** + * @return a {@link Statement}: clears interrupt status of current thread after execution of statement + */ + protected final Statement withInterruptIsolation(final Statement statement) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + statement.evaluate(); + } finally { + Thread.interrupted(); // clearing thread interrupted status for isolation + } + } + }; + } + /** * Evaluates whether a child is ignored. The default implementation always * returns false. @@ -346,8 +387,16 @@ public abstract class ParentRunner extends Runner implements Filterable, @Override public Description getDescription() { - Description description = Description.createSuiteDescription(getName(), - getRunnerAnnotations()); + Class clazz = getTestClass().getJavaClass(); + Description description; + // if subclass overrides `getName()` then we should use it + // to maintain backwards compatibility with JUnit 4.12 + if (clazz == null || !clazz.getName().equals(getName())) { + description = Description.createSuiteDescription(getName(), getRunnerAnnotations()); + } else { + description = Description.createSuiteDescription(clazz, getRunnerAnnotations()); + } + for (T child : getFilteredChildren()) { description.addChild(describeChild(child)); } @@ -358,6 +407,7 @@ public abstract class ParentRunner extends Runner implements Filterable, public void run(final RunNotifier notifier) { EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription()); + testNotifier.fireTestSuiteStarted(); try { Statement statement = classBlock(notifier); statement.evaluate(); @@ -367,6 +417,8 @@ public abstract class ParentRunner extends Runner implements Filterable, throw e; } catch (Throwable e) { testNotifier.addFailure(e); + } finally { + testNotifier.fireTestSuiteFinished(); } } @@ -375,7 +427,8 @@ public abstract class ParentRunner extends Runner implements Filterable, // public void filter(Filter filter) throws NoTestsRemainException { - synchronized (childrenLock) { + childrenLock.lock(); + try { List children = new ArrayList(getFilteredChildren()); for (Iterator iter = children.iterator(); iter.hasNext(); ) { T each = iter.next(); @@ -389,21 +442,70 @@ public abstract class ParentRunner extends Runner implements Filterable, iter.remove(); } } - filteredChildren = Collections.unmodifiableCollection(children); + filteredChildren = Collections.unmodifiableList(children); if (filteredChildren.isEmpty()) { throw new NoTestsRemainException(); } + } finally { + childrenLock.unlock(); } } public void sort(Sorter sorter) { - synchronized (childrenLock) { + if (shouldNotReorder()) { + return; + } + + childrenLock.lock(); + try { for (T each : getFilteredChildren()) { sorter.apply(each); } List sortedChildren = new ArrayList(getFilteredChildren()); Collections.sort(sortedChildren, comparator(sorter)); - filteredChildren = Collections.unmodifiableCollection(sortedChildren); + filteredChildren = Collections.unmodifiableList(sortedChildren); + } finally { + childrenLock.unlock(); + } + } + + /** + * Implementation of {@link Orderable#order(Orderer)}. + * + * @since 4.13 + */ + public void order(Orderer orderer) throws InvalidOrderingException { + if (shouldNotReorder()) { + return; + } + + childrenLock.lock(); + try { + List children = getFilteredChildren(); + // In theory, we could have duplicate Descriptions. De-dup them before ordering, + // and add them back at the end. + Map> childMap = new LinkedHashMap>( + children.size()); + for (T child : children) { + Description description = describeChild(child); + List childrenWithDescription = childMap.get(description); + if (childrenWithDescription == null) { + childrenWithDescription = new ArrayList(1); + childMap.put(description, childrenWithDescription); + } + childrenWithDescription.add(child); + orderer.apply(child); + } + + List inOrder = orderer.order(childMap.keySet()); + + children = new ArrayList(children.size()); + for (Description description : inOrder) { + children.addAll(childMap.get(description)); + } + filteredChildren = Collections.unmodifiableList(children); + } finally { + childrenLock.unlock(); } } @@ -411,20 +513,29 @@ public abstract class ParentRunner extends Runner implements Filterable, // Private implementation // + private boolean shouldNotReorder() { + // If the test specifies a specific order, do not reorder. + return getDescription().getAnnotation(FixMethodOrder.class) != null; + } + private void validate() throws InitializationError { List errors = new ArrayList(); collectInitializationErrors(errors); if (!errors.isEmpty()) { - throw new InitializationError(errors); + throw new InvalidTestClassError(testClass.getJavaClass(), errors); } } - private Collection getFilteredChildren() { + private List getFilteredChildren() { if (filteredChildren == null) { - synchronized (childrenLock) { + childrenLock.lock(); + try { if (filteredChildren == null) { - filteredChildren = Collections.unmodifiableCollection(getChildren()); + filteredChildren = Collections.unmodifiableList( + new ArrayList(getChildren())); } + } finally { + childrenLock.unlock(); } } return filteredChildren; @@ -449,4 +560,23 @@ public abstract class ParentRunner extends Runner implements Filterable, public void setScheduler(RunnerScheduler scheduler) { this.scheduler = scheduler; } + + private static class ClassRuleCollector implements MemberValueConsumer { + final List entries = new ArrayList(); + + public void accept(FrameworkMember member, TestRule value) { + ClassRule rule = member.getAnnotation(ClassRule.class); + entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE, + rule != null ? rule.order() : null)); + } + + public List getOrderedRules() { + Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR); + List result = new ArrayList(entries.size()); + for (RuleContainer.RuleEntry entry : entries) { + result.add((TestRule) entry.rule); + } + return result; + } + } } diff --git a/src/main/java/org/junit/runners/RuleContainer.java b/src/main/java/org/junit/runners/RuleContainer.java new file mode 100644 index 0000000..30ddd8d --- /dev/null +++ b/src/main/java/org/junit/runners/RuleContainer.java @@ -0,0 +1,113 @@ +package org.junit.runners; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.List; + +import org.junit.Rule; +import org.junit.rules.MethodRule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * Data structure for ordering of {@link TestRule}/{@link MethodRule} instances. + * + * @since 4.13 + */ +class RuleContainer { + private final IdentityHashMap orderValues = new IdentityHashMap(); + private final List testRules = new ArrayList(); + private final List methodRules = new ArrayList(); + + /** + * Sets order value for the specified rule. + */ + public void setOrder(Object rule, int order) { + orderValues.put(rule, order); + } + + public void add(MethodRule methodRule) { + methodRules.add(methodRule); + } + + public void add(TestRule testRule) { + testRules.add(testRule); + } + + static final Comparator ENTRY_COMPARATOR = new Comparator() { + public int compare(RuleEntry o1, RuleEntry o2) { + int result = compareInt(o1.order, o2.order); + return result != 0 ? result : o1.type - o2.type; + } + + private int compareInt(int a, int b) { + return (a < b) ? 1 : (a == b ? 0 : -1); + } + }; + + /** + * Returns entries in the order how they should be applied, i.e. inner-to-outer. + */ + private List getSortedEntries() { + List ruleEntries = new ArrayList( + methodRules.size() + testRules.size()); + for (MethodRule rule : methodRules) { + ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_METHOD_RULE, orderValues.get(rule))); + } + for (TestRule rule : testRules) { + ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_TEST_RULE, orderValues.get(rule))); + } + Collections.sort(ruleEntries, ENTRY_COMPARATOR); + return ruleEntries; + } + + /** + * Applies all the rules ordered accordingly to the specified {@code statement}. + */ + public Statement apply(FrameworkMethod method, Description description, Object target, + Statement statement) { + if (methodRules.isEmpty() && testRules.isEmpty()) { + return statement; + } + Statement result = statement; + for (RuleEntry ruleEntry : getSortedEntries()) { + if (ruleEntry.type == RuleEntry.TYPE_TEST_RULE) { + result = ((TestRule) ruleEntry.rule).apply(result, description); + } else { + result = ((MethodRule) ruleEntry.rule).apply(result, method, target); + } + } + return result; + } + + /** + * Returns rule instances in the order how they should be applied, i.e. inner-to-outer. + * VisibleForTesting + */ + List getSortedRules() { + List result = new ArrayList(); + for (RuleEntry entry : getSortedEntries()) { + result.add(entry.rule); + } + return result; + } + + static class RuleEntry { + static final int TYPE_TEST_RULE = 1; + static final int TYPE_METHOD_RULE = 0; + + final Object rule; + final int type; + final int order; + + RuleEntry(Object rule, int type, Integer order) { + this.rule = rule; + this.type = type; + this.order = order != null ? order.intValue() : Rule.DEFAULT_ORDER; + } + } +} diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java index b37179f..c2c8e58 100644 --- a/src/main/java/org/junit/runners/Suite.java +++ b/src/main/java/org/junit/runners/Suite.java @@ -47,7 +47,7 @@ public class Suite extends ParentRunner { /** * @return the classes to be run */ - public Class[] value(); + Class[] value(); } private static Class[] getAnnotatedClasses(Class klass) throws InitializationError { @@ -88,7 +88,7 @@ public class Suite extends ParentRunner { * @param suiteClasses the classes in the suite */ protected Suite(Class klass, Class[] suiteClasses) throws InitializationError { - this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses); + this(new AllDefaultPossibilitiesBuilder(), klass, suiteClasses); } /** diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java index 945e389..ea2b16f 100644 --- a/src/main/java/org/junit/runners/model/FrameworkField.java +++ b/src/main/java/org/junit/runners/model/FrameworkField.java @@ -14,12 +14,26 @@ import org.junit.runners.BlockJUnit4ClassRunner; public class FrameworkField extends FrameworkMember { private final Field field; - FrameworkField(Field field) { + /** + * Returns a new {@code FrameworkField} for {@code field}. + * + *

      Access relaxed to {@code public} since version 4.13.1. + */ + public FrameworkField(Field field) { if (field == null) { throw new NullPointerException( "FrameworkField cannot be created without an underlying field."); } this.field = field; + + if (isPublic()) { + // This field could be a public field in a package-scope base class + try { + field.setAccessible(true); + } catch (SecurityException e) { + // We may get an IllegalAccessException when we try to access the field + } + } } @Override @@ -40,6 +54,11 @@ public class FrameworkField extends FrameworkMember { return otherMember.getName().equals(getName()); } + @Override + boolean isBridgeMethod() { + return false; + } + @Override protected int getModifiers() { return field.getModifiers(); diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java index 724f096..5634b3f 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMember.java +++ b/src/main/java/org/junit/runners/model/FrameworkMember.java @@ -12,15 +12,29 @@ public abstract class FrameworkMember> implements Annotatable { abstract boolean isShadowedBy(T otherMember); - boolean isShadowedBy(List members) { - for (T each : members) { - if (isShadowedBy(each)) { - return true; + T handlePossibleBridgeMethod(List members) { + for (int i = members.size() - 1; i >=0; i--) { + T otherMember = members.get(i); + if (isShadowedBy(otherMember)) { + if (otherMember.isBridgeMethod()) { + /* + * We need to return the previously-encountered bridge method + * because JUnit won't be able to call the parent method, + * because the parent class isn't public. + */ + members.remove(i); + return otherMember; + } + // We found a shadowed member that isn't a bridge method. Ignore it. + return null; } } - return false; + // No shadow or bridge method found. The caller should add *this* member. + return (T) this; } + abstract boolean isBridgeMethod(); + protected abstract int getModifiers(); /** diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java index 3580052..4471407 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMethod.java +++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java @@ -28,6 +28,15 @@ public class FrameworkMethod extends FrameworkMember { "FrameworkMethod cannot be created without an underlying method."); } this.method = method; + + if (isPublic()) { + // This method could be a public method in a package-scope base class + try { + method.setAccessible(true); + } catch (SecurityException e) { + // We may get an IllegalAccessException when we try to call the method + } + } } /** @@ -148,6 +157,11 @@ public class FrameworkMethod extends FrameworkMember { return true; } + @Override + boolean isBridgeMethod() { + return method.isBridge(); + } + @Override public boolean equals(Object obj) { if (!FrameworkMethod.class.isInstance(obj)) { diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java index 841b565..dd9c8b3 100644 --- a/src/main/java/org/junit/runners/model/InitializationError.java +++ b/src/main/java/org/junit/runners/model/InitializationError.java @@ -14,7 +14,7 @@ public class InitializationError extends Exception { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final List fErrors; diff --git a/src/main/java/org/junit/runners/model/InvalidTestClassError.java b/src/main/java/org/junit/runners/model/InvalidTestClassError.java new file mode 100644 index 0000000..57be610 --- /dev/null +++ b/src/main/java/org/junit/runners/model/InvalidTestClassError.java @@ -0,0 +1,39 @@ +package org.junit.runners.model; + +import java.util.List; + +/** + * Thrown by {@link org.junit.runner.Runner}s in case the class under test is not valid. + *

      + * Its message conveniently lists all of the validation errors. + * + * @since 4.13 + */ +public class InvalidTestClassError extends InitializationError { + private static final long serialVersionUID = 1L; + + private final String message; + + public InvalidTestClassError(Class offendingTestClass, List validationErrors) { + super(validationErrors); + this.message = createMessage(offendingTestClass, validationErrors); + } + + private static String createMessage(Class testClass, List validationErrors) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("Invalid test class '%s':", testClass.getName())); + int i = 1; + for (Throwable error : validationErrors) { + sb.append("\n " + (i++) + ". " + error.getMessage()); + } + return sb.toString(); + } + + /** + * @return a message with a list of all of the validation errors + */ + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/junit/runners/model/MemberValueConsumer.java b/src/main/java/org/junit/runners/model/MemberValueConsumer.java new file mode 100644 index 0000000..a6157bf --- /dev/null +++ b/src/main/java/org/junit/runners/model/MemberValueConsumer.java @@ -0,0 +1,18 @@ +package org.junit.runners.model; + +/** + * Represents a receiver for values of annotated fields/methods together with the declaring member. + * + * @see TestClass#collectAnnotatedFieldValues(Object, Class, Class, MemberValueConsumer) + * @see TestClass#collectAnnotatedMethodValues(Object, Class, Class, MemberValueConsumer) + * @since 4.13 + */ +public interface MemberValueConsumer { + /** + * Receives the next value and its declaring member. + * + * @param member declaring member ({@link FrameworkMethod} or {@link FrameworkField}) + * @param value the value of the next member + */ + void accept(FrameworkMember member, T value); +} diff --git a/src/main/java/org/junit/runners/model/MultipleFailureException.java b/src/main/java/org/junit/runners/model/MultipleFailureException.java index 325c645..8e355a7 100644 --- a/src/main/java/org/junit/runners/model/MultipleFailureException.java +++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java @@ -1,9 +1,13 @@ package org.junit.runners.model; +import java.io.PrintStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.TestCouldNotBeSkippedException; +import org.junit.internal.AssumptionViolatedException; import org.junit.internal.Throwables; /** @@ -17,12 +21,22 @@ public class MultipleFailureException extends Exception { /* * 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 + * See https://github.com/junit-team/junit4/issues/976 */ private final List fErrors; public MultipleFailureException(List errors) { - this.fErrors = new ArrayList(errors); + if (errors.isEmpty()) { + throw new IllegalArgumentException( + "List of Throwables must not be empty"); + } + this.fErrors = new ArrayList(errors.size()); + for (Throwable error : errors) { + if (error instanceof AssumptionViolatedException) { + error = new TestCouldNotBeSkippedException((AssumptionViolatedException) error); + } + fErrors.add(error); + } } public List getFailures() { @@ -34,11 +48,32 @@ public class MultipleFailureException extends Exception { 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())); + sb.append(String.format("%n %s(%s)", e.getClass().getName(), e.getMessage())); } return sb.toString(); } + @Override + public void printStackTrace() { + for (Throwable e: fErrors) { + e.printStackTrace(); + } + } + + @Override + public void printStackTrace(PrintStream s) { + for (Throwable e: fErrors) { + e.printStackTrace(s); + } + } + + @Override + public void printStackTrace(PrintWriter s) { + for (Throwable e: fErrors) { + e.printStackTrace(s); + } + } + /** * Asserts that a list of throwables is empty. If it isn't empty, * will throw {@link MultipleFailureException} (if there are diff --git a/src/main/java/org/junit/runners/model/RunnerBuilder.java b/src/main/java/org/junit/runners/model/RunnerBuilder.java index 7d3eee3..ba7c9e2 100644 --- a/src/main/java/org/junit/runners/model/RunnerBuilder.java +++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java @@ -6,7 +6,11 @@ import java.util.List; import java.util.Set; import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Description; +import org.junit.runner.OrderWith; import org.junit.runner.Runner; +import org.junit.runner.manipulation.InvalidOrderingException; +import org.junit.runner.manipulation.Ordering; /** * A RunnerBuilder is a strategy for constructing runners for classes. @@ -49,19 +53,39 @@ public abstract class RunnerBuilder { 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. + * Always returns a runner for the given test class. + * + *

      In case of an exception a runner will be returned that prints an error instead of running + * tests. + * + *

      Note that some of the internal JUnit implementations of RunnerBuilder will return + * {@code null} from this method, but no RunnerBuilder passed to a Runner constructor will + * return {@code null} from this method. * * @param testClass class to be run * @return a Runner */ public Runner safeRunnerForClass(Class testClass) { try { - return runnerForClass(testClass); + Runner runner = runnerForClass(testClass); + if (runner != null) { + configureRunner(runner); + } + return runner; } catch (Throwable e) { return new ErrorReportingRunner(testClass, e); } } + private void configureRunner(Runner runner) throws InvalidOrderingException { + Description description = runner.getDescription(); + OrderWith orderWith = description.getAnnotation(OrderWith.class); + if (orderWith != null) { + Ordering ordering = Ordering.definedBy(orderWith.value(), description); + ordering.apply(runner); + } + } + 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())); @@ -96,7 +120,7 @@ public abstract class RunnerBuilder { } private List runners(Class[] children) { - ArrayList runners = new ArrayList(); + List runners = new ArrayList(); for (Class each : children) { Runner childRunner = safeRunnerForClass(each); if (childRunner != null) { diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java old mode 100755 new mode 100644 index c8a544d..5962c2b --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -84,20 +84,21 @@ public class TestClass implements Annotatable { for (Annotation each : member.getAnnotations()) { Class type = each.annotationType(); List members = getAnnotatedMembers(map, type, true); - if (member.isShadowedBy(members)) { + T memberToAdd = member.handlePossibleBridgeMethod(members); + if (memberToAdd == null) { return; } if (runsTopToBottom(type)) { - members.add(0, member); + members.add(0, memberToAdd); } else { - members.add(member); + members.add(memberToAdd); } } } private static > Map, List> makeDeeplyUnmodifiable(Map, List> source) { - LinkedHashMap, List> copy = + Map, List> copy = new LinkedHashMap, List>(); for (Map.Entry, List> entry : source.entrySet()) { copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); @@ -168,7 +169,7 @@ public class TestClass implements Annotatable { } private static List> getSuperClasses(Class testClass) { - ArrayList> results = new ArrayList>(); + List> results = new ArrayList>(); Class current = testClass; while (current != null) { results.add(current); @@ -224,24 +225,59 @@ public class TestClass implements Annotatable { public List getAnnotatedFieldValues(Object test, Class annotationClass, Class valueClass) { - List results = new ArrayList(); + final List results = new ArrayList(); + collectAnnotatedFieldValues(test, annotationClass, valueClass, + new MemberValueConsumer() { + public void accept(FrameworkMember member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the fields annotated with the specified annotation and having the specified type, + * retrieves the values and passes those to the specified consumer. + * + * @since 4.13 + */ + public void collectAnnotatedFieldValues(Object test, + Class annotationClass, Class valueClass, + MemberValueConsumer consumer) { for (FrameworkField each : getAnnotatedFields(annotationClass)) { try { Object fieldValue = each.get(test); if (valueClass.isInstance(fieldValue)) { - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (IllegalAccessException e) { throw new RuntimeException( "How did getFields return a field we couldn't access?", e); } } - return results; } public List getAnnotatedMethodValues(Object test, Class annotationClass, Class valueClass) { - List results = new ArrayList(); + final List results = new ArrayList(); + collectAnnotatedMethodValues(test, annotationClass, valueClass, + new MemberValueConsumer() { + public void accept(FrameworkMember member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the methods annotated with the specified annotation and returning the specified type, + * invokes it and pass the return value to the specified consumer. + * + * @since 4.13 + */ + public void collectAnnotatedMethodValues(Object test, + Class annotationClass, Class valueClass, + MemberValueConsumer consumer) { for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) { try { /* @@ -254,14 +290,13 @@ public class TestClass implements Annotatable { */ if (valueClass.isAssignableFrom(each.getReturnType())) { Object fieldValue = each.invokeExplosively(test); - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (Throwable e) { throw new RuntimeException( "Exception in " + each.getName(), e); } } - return results; } public boolean isPublic() { diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java index 1c49f84..5c70a75 100644 --- a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java +++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java @@ -4,8 +4,12 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.List; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.runner.RunWith; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; @@ -18,13 +22,17 @@ import org.junit.runners.model.Statement; */ public class BlockJUnit4ClassRunnerWithParameters extends BlockJUnit4ClassRunner { + private enum InjectionType { + CONSTRUCTOR, FIELD + } + private final Object[] parameters; private final String name; public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) throws InitializationError { - super(test.getTestClass().getJavaClass()); + super(test.getTestClass()); parameters = test.getParameters().toArray( new Object[test.getParameters().size()]); name = test.getName(); @@ -32,10 +40,15 @@ public class BlockJUnit4ClassRunnerWithParameters extends @Override public Object createTest() throws Exception { - if (fieldsAreAnnotated()) { - return createTestUsingFieldInjection(); - } else { - return createTestUsingConstructorInjection(); + InjectionType injectionType = getInjectionType(); + switch (injectionType) { + case CONSTRUCTOR: + return createTestUsingConstructorInjection(); + case FIELD: + return createTestUsingFieldInjection(); + default: + throw new IllegalStateException("The injection type " + + injectionType + " is not supported."); } } @@ -60,6 +73,13 @@ public class BlockJUnit4ClassRunnerWithParameters extends int index = annotation.value(); try { field.set(testClassInstance, parameters[index]); + } catch (IllegalAccessException e) { + IllegalAccessException wrappedException = new IllegalAccessException( + "Cannot set parameter '" + field.getName() + + "'. Ensure that the field '" + field.getName() + + "' is public."); + wrappedException.initCause(e); + throw wrappedException; } catch (IllegalArgumentException iare) { throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() @@ -86,7 +106,7 @@ public class BlockJUnit4ClassRunnerWithParameters extends @Override protected void validateConstructor(List errors) { validateOnlyOneConstructor(errors); - if (fieldsAreAnnotated()) { + if (getInjectionType() != InjectionType.CONSTRUCTOR) { validateZeroArgConstructor(errors); } } @@ -94,7 +114,7 @@ public class BlockJUnit4ClassRunnerWithParameters extends @Override protected void validateFields(List errors) { super.validateFields(errors); - if (fieldsAreAnnotated()) { + if (getInjectionType() == InjectionType.FIELD) { List annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); int[] usedIndices = new int[annotatedFieldsByParameter.size()]; for (FrameworkField each : annotatedFieldsByParameter) { @@ -125,18 +145,74 @@ public class BlockJUnit4ClassRunnerWithParameters extends @Override protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); + Statement statement = childrenInvoker(notifier); + statement = withBeforeParams(statement); + statement = withAfterParams(statement); + return statement; + } + + private Statement withBeforeParams(Statement statement) { + List befores = getTestClass() + .getAnnotatedMethods(Parameterized.BeforeParam.class); + return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores); + } + + private class RunBeforeParams extends RunBefores { + RunBeforeParams(Statement next, List befores) { + super(next, befores, null); + } + + @Override + protected void invokeMethod(FrameworkMethod method) throws Throwable { + int paramCount = method.getMethod().getParameterTypes().length; + method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); + } + } + + private Statement withAfterParams(Statement statement) { + List afters = getTestClass() + .getAnnotatedMethods(Parameterized.AfterParam.class); + return afters.isEmpty() ? statement : new RunAfterParams(statement, afters); + } + + private class RunAfterParams extends RunAfters { + RunAfterParams(Statement next, List afters) { + super(next, afters, null); + } + + @Override + protected void invokeMethod(FrameworkMethod method) throws Throwable { + int paramCount = method.getMethod().getParameterTypes().length; + method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); + } } @Override protected Annotation[] getRunnerAnnotations() { - return new Annotation[0]; + Annotation[] allAnnotations = super.getRunnerAnnotations(); + Annotation[] annotationsWithoutRunWith = new Annotation[allAnnotations.length - 1]; + int i = 0; + for (Annotation annotation: allAnnotations) { + if (!annotation.annotationType().equals(RunWith.class)) { + annotationsWithoutRunWith[i] = annotation; + ++i; + } + } + return annotationsWithoutRunWith; } private List getAnnotatedFieldsByParameter() { return getTestClass().getAnnotatedFields(Parameter.class); } + private InjectionType getInjectionType() { + if (fieldsAreAnnotated()) { + return InjectionType.FIELD; + } else { + return InjectionType.CONSTRUCTOR; + } + } + private boolean fieldsAreAnnotated() { return !getAnnotatedFieldsByParameter().isEmpty(); } diff --git a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java index 16ea1f3..8123e83 100644 --- a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java +++ b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java @@ -4,7 +4,7 @@ import org.junit.runner.Runner; import org.junit.runners.model.InitializationError; /** - * A {@code ParameterizedRunnerFactory} creates a runner for a single + * A {@code ParametersRunnerFactory} creates a runner for a single * {@link TestWithParameters}. * * @since 4.12 diff --git a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java index 1b86644..1c5abd9 100644 --- a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java +++ b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java @@ -1,6 +1,7 @@ package org.junit.runners.parameterized; import static java.util.Collections.unmodifiableList; +import static org.junit.internal.Checks.notNull; import java.util.ArrayList; import java.util.List; @@ -73,10 +74,4 @@ public class TestWithParameters { 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/AnnotationValidatorFactory.java b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java index 7309fdd..fb2460d 100644 --- a/src/main/java/org/junit/validator/AnnotationValidatorFactory.java +++ b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java @@ -27,9 +27,6 @@ public class AnnotationValidatorFactory { } Class 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); diff --git a/src/main/java/org/junit/validator/AnnotationsValidator.java b/src/main/java/org/junit/validator/AnnotationsValidator.java index 30f54a6..d8b5840 100644 --- a/src/main/java/org/junit/validator/AnnotationsValidator.java +++ b/src/main/java/org/junit/validator/AnnotationsValidator.java @@ -40,7 +40,7 @@ public final class AnnotationsValidator implements TestClassValidator { return validationErrors; } - private static abstract class AnnotatableValidator { + private abstract static class AnnotatableValidator { private static final AnnotationValidatorFactory ANNOTATION_VALIDATOR_FACTORY = new AnnotationValidatorFactory(); abstract Iterable getAnnotatablesForTestClass(TestClass testClass); @@ -116,5 +116,5 @@ public final class AnnotationsValidator implements TestClassValidator { AnnotationValidator validator, FrameworkField field) { return validator.validateAnnotatedField(field); } - }; + } } diff --git a/src/main/java/org/junit/validator/TestClassValidator.java b/src/main/java/org/junit/validator/TestClassValidator.java index 43cb787..ba5e892 100644 --- a/src/main/java/org/junit/validator/TestClassValidator.java +++ b/src/main/java/org/junit/validator/TestClassValidator.java @@ -17,5 +17,5 @@ public interface TestClassValidator { * the {@link TestClass} that is validated. * @return the validation errors found by the validator. */ - public List validateTestClass(TestClass testClass); + List validateTestClass(TestClass testClass); } diff --git a/src/main/java/org/junit/validator/ValidateWith.java b/src/main/java/org/junit/validator/ValidateWith.java index 03d7906..3725db8 100644 --- a/src/main/java/org/junit/validator/ValidateWith.java +++ b/src/main/java/org/junit/validator/ValidateWith.java @@ -1,8 +1,10 @@ package org.junit.validator; +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; /** * Allows for an {@link AnnotationValidator} to be attached to an annotation. @@ -13,6 +15,7 @@ import java.lang.annotation.RetentionPolicy; * @since 4.12 */ @Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) @Inherited public @interface ValidateWith { Class value(); diff --git a/version b/version index 8d39259..650edfe 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.10 +4.13.2 -- cgit v1.2.3