diff options
author | Pete Bentley <prb@google.com> | 2021-02-21 18:26:28 +0000 |
---|---|---|
committer | Pete Bentley <prb@google.com> | 2021-02-22 18:38:53 +0000 |
commit | aa1524950fb5fbffbb61d23c5442984cb731a1eb (patch) | |
tree | 7ce2c9779133dd053d314fdb25818acbbb4fa2be /src/main/java/org/junit | |
parent | e938015d2d5a2157ed20eb622fcd9750a12bb4af (diff) | |
download | junit-aa1524950fb5fbffbb61d23c5442984cb731a1eb.tar.gz |
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.
Bug: 129054170
Test: m
Change-Id: I5ec909df6840f6c54cbab9c509c5addaee3f94e6
Diffstat (limited to 'src/main/java/org/junit')
107 files changed, 3674 insertions, 651 deletions
diff --git a/src/main/java/org/junit/Assert.java b/src/main/java/org/junit/Assert.java index d7deb06..65bbc9d 100755..100644 --- 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 <code>expected</code> */ - 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 <code>unexpected</code> */ - 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 <code>unexpected</code> */ - 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 <code>unexpected</code> */ - 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 <code>unexpected</code> */ - 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 { * <code>actual</code> 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 { * <code>actual</code> 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 { * <code>actual</code> 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 <code>expected</code> and @@ -547,7 +549,7 @@ public class Assert { * <code>actual</code> 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 { * <code>actual</code> 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 { * <code>actual</code> 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 { * <code>actual</code> 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 { * <code>actual</code> 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 <code>null</code> */ - 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 <code>null</code> */ - 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 <code>null</code> */ - 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 <code>null</code> */ - 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 <code>expected</code> */ - 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 <code>expected</code> */ - 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 <code>unexpected</code> */ - 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 <code>unexpected</code> */ - 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 <T> void assertThat(T actual, Matcher<? super T> 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 <T> void assertThat(String reason, T actual, Matcher<? super T> 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 extends Throwable> T assertThrows(Class<T> 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} (<code>null</code> + * 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 extends Throwable> T assertThrows(String message, Class<T> 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<? extends Throwable> 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<my.package.MyException> ..." + 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. * <p> - * A good example of using assumptions is in <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a> where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case. + * A good example of using assumptions is in <a href="https://github.com/junit-team/junit4/wiki/Theories">Theories</a> where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case. * </p> * Failed assumptions are usually not logged, because there may be many tests that don't apply to certain * configurations. @@ -29,11 +29,20 @@ import org.hamcrest.Matcher; * </pre> * </p> * - * @see <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a> + * @see <a href="https://github.com/junit-team/junit4/wiki/Theories">Theories</a> * * @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 <code>objects</code>, 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. + * + * <h3>Usage</h3> * <p> * For example, here is a test suite that connects to a server once before * all the test classes run, and disconnects after they are finished: @@ -79,9 +82,37 @@ import java.lang.annotation.Target; * <p> * For more information and more examples, see {@link org.junit.rules.TestRule}. * + * <h3>Ordering</h3> + * <p> + * You can use {@link #order()} if you want to have control over the order in + * which the Rules are applied. + * + * <pre> + * 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"); + * + * // ... + * } + * </pre> + * * @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. + * + * <h3>Usage</h3> * <p> * 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}. * + * <h3>Ordering</h3> + * <p> + * You can use {@link #order()} if you want to have control over the order in + * which the Rules are applied. + * + * <pre> + * 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"); + * + * // ... + * } + * </pre> + * * @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; * } * </pre> * <p> - * The <code>Test</code> annotation supports two optional parameters. - * The first, <code>expected</code>, declares that a test method should throw + * The <code>Test</code> annotation supports two optional parameters for + * exception testing and for limiting test execution time. + * + * <h3>Exception Testing</h3> + * <p> + * The parameter <code>expected</code> declares that a test method should throw * an exception. If it doesn't throw an exception or if it throws a different exception * than the one declared, the test fails. For example, the following test succeeds: * <pre> - * @Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() { + * @Test(<b>expected=IndexOutOfBoundsException.class</b>) + * public void outOfBounds() { * new ArrayList<Object>().get(1); * } * </pre> - * If the exception's message or one of its properties should be verified, the - * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further + * + * Using the parameter <code>expected</code> 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 <code>assertThrows</code> the code that throws the exception can be + * precisely specified. If the exception's message or one of its properties + * should be verified, the <code>ExpectedException</code> rule can be used. Further * information about exception testing can be found at the - * <a href="https://github.com/junit-team/junit/wiki/Exception-testing">JUnit Wiki</a>. + * <a href="https://github.com/junit-team/junit4/wiki/Exception-testing">JUnit Wiki</a>. + * + * <h3>Timeout</h3> * <p> - * The second optional parameter, <code>timeout</code>, causes a test to fail if it takes + * The parameter <code>timeout</code> causes a test to fail if it takes * longer than a specified amount of clock time (measured in milliseconds). The following test fails: * <pre> - * @Test(<b>timeout=100</b>) public void infinity() { + * @Test(<b>timeout=100</b>) + * public void infinity() { * while(true); * } * </pre> @@ -49,7 +67,8 @@ import java.lang.annotation.Target; * following test may or may not fail depending on how the operating system * schedules threads: * <pre> - * @Test(<b>timeout=100</b>) public void sleep100() { + * @Test(<b>timeout=100</b>) + * public void sleep100() { * Thread.sleep(100); * } * </pre> @@ -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; * </pre> * * @version 4.12 - * @see <a href="https://github.com/junit-team/junit/wiki/Categories">Categories at JUnit wiki</a> + * @see <a href="https://github.com/junit-team/junit4/wiki/Categories">Categories at JUnit wiki</a> */ 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 <tt>true</tt>, runs tests annotated with <em>any</em> of the categories in * {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with <em>all</em> of the categories. */ - public boolean matchAny() default true; + 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 <tt>true</tt>, the tests annotated with <em>any</em> of the categories in {@link ExcludeCategory#value()} * do not run. Otherwise, the tests do not run if and only if annotated with <em>all</em> categories. */ - public boolean matchAny() default true; + 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<Class<?>> includes, - boolean matchAnyExcludes, Set<Class<?>> excludes) { + boolean matchAnyExcludes, Set<Class<?>> 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<Class<?>> copyAndRefine(Set<Class<?>> classes) { - HashSet<Class<?>> c= new HashSet<Class<?>>(); + Set<Class<?>> c= new LinkedHashSet<Class<?>>(); if (classes != null) { c.addAll(classes); } c.remove(null); return c; } - - private static boolean hasNull(Class<?>... classes) { - if (classes == null) return false; - for (Class<?> clazz : classes) { - if (clazz == null) { - return true; - } - } - return false; - } } public Categories(Class<?> klass, RunnerBuilder builder) throws InitializationError { @@ -315,7 +317,6 @@ public class Categories extends Suite { } catch (NoTestsRemainException e) { throw new InitializationError(e); } - assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); } private static Set<Class<?>> 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<Class<?>> 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<Class<?>> createSet(Class<?>... t) { - final Set<Class<?>> set= new HashSet<Class<?>>(); - if (t != null) { - Collections.addAll(set, t); + private static Set<Class<?>> 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.<Class<?>>singleton(classes[0]) + : new LinkedHashSet<Class<?>>(Arrays.asList(classes)); + } + + private static Set<Class<?>> 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.<Class<?>>emptySet() + : Collections.<Class<?>>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<Class<?>> categoryClasses = new ArrayList<Class<?>>();
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<String, Long> fDurations = new HashMap<String, Long>(); private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>(); @@ -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<Failure> 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; * </pre> */ public class ResultMatchers { + + /** + * Do not instantiate. + * @deprecated will be private soon. + */ + @Deprecated + public ResultMatchers() { + } + /** * Matches if the tests are all successful */ @@ -53,13 +62,33 @@ public class ResultMatchers { } /** + * Matches if the result has exactly one failure matching the given matcher. + * + * @since 4.13 + */ + public static Matcher<PrintableResult> hasSingleFailureMatching(final Matcher<Throwable> matcher) { + return new TypeSafeMatcher<PrintableResult>() { + @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<PrintableResult> hasFailureContaining(final String string) { - return new BaseMatcher<PrintableResult>() { - public boolean matches(Object item) { - return item.toString().contains(string); + return new TypeSafeMatcher<PrintableResult>() { + @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: * * <pre> 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; * } * } * </pre> - * 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. <code>UserTest</code> will attempt to run <code>filenameIncludesUsername</code> on every compatible data * point defined in the class. If any of the assumptions fail, the data point is silently ignored. If all of the - * assumptions pass, but an assertion fails, the test fails. + * assumptions pass, but an assertion fails, the test fails. If no parameters can be found that satisfy all assumptions, the test fails. * <p> * Defining general statements as theories allows data point reuse across a bunch of functionality tests and also * allows automated tools to search for new, unexpected data points that expose bugs. @@ -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<Throwable> 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<Throwable> 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<PotentialAssignment> assigned = new ArrayList<PotentialAssignment>( - this.assigned); - assigned.add(source); + List<PotentialAssignment> potentialAssignments = new ArrayList<PotentialAssignment>(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<PotentialAssignment> 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<Integer> fIndices = new ArrayList<Integer>(); private final String fMessage; + private final AssertionError fCause; /** * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's @@ -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); } @@ -41,6 +43,11 @@ public class ArrayComparisonFailure extends AssertionError { } @Override + public synchronized Throwable getCause() { + return super.getCause() == null ? fCause : super.getCause(); + } + + @Override public String getMessage() { StringBuilder sb = new StringBuilder(); if (fMessage != null) { 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> 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> 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<T> extends BaseMatcher<T> implements Serializable { + + private final String matcherDescription; + + private SerializableMatcherDescription(Matcher<T> 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 <T> Matcher<T> asSerializableMatcher(Matcher<T> matcher) { + if (matcher == null || matcher instanceof Serializable) { + return matcher; + } else { + return new SerializableMatcherDescription<T>(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<Failure> 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 <T extends Throwable> 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<String> 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<String> getTrimmedStackTraceLines(Throwable exception) { + List<StackTraceElement> 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<String> trimmedLines = new ArrayList<String>(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<String> 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<String> causedByLines = new ArrayList<String>(); + + 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<String> stackTraceLines, StringBuilder destBuilder) { + for (String stackTraceLine : stackTraceLines) { + destBuilder.append(String.format("%s%n", stackTraceLine)); + } + } + + private static <T> List<T> asReversedList(final List<T> list) { + return new AbstractList<T>() { + + @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.<init>(", + "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} + * + * <p>Always returns an empty list. + */ + public List<String> 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} + * + * <p>Always throws an {@link UnsupportedOperationException} + */ + public long getThreadCpuTime(long id) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * <p>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<String> getInputArguments() { + if (Holder.getInputArgumentsMethod != null) { + try { + return (List<String>) 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<String> 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<T extends Throwable> 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<T extends Throwable> extends TypeSafeMatcher<T> { - private final Matcher<? extends Throwable> causeMatcher; + private final Matcher<?> causeMatcher; - public ThrowableCauseMatcher(Matcher<? extends Throwable> causeMatcher) { + public ThrowableCauseMatcher(Matcher<?> causeMatcher) { this.causeMatcher = causeMatcher; } @@ -44,7 +44,7 @@ public class ThrowableCauseMatcher<T extends Throwable> extends * @param <T> type of the outer exception */ @Factory - public static <T extends Throwable> Matcher<T> hasCause(final Matcher<? extends Throwable> matcher) { + public static <T extends Throwable> Matcher<T> hasCause(final Matcher<?> matcher) { return new ThrowableCauseMatcher<T>(matcher); } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java index 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<T> extends BaseMatcher<T> { } 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<Throwable> 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<Throwable> 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<Throwable> 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<Class<?>> getSuperClasses(Class<?> testClass) { - ArrayList<Class<?>> results = new ArrayList<Class<?>>(); + List<Class<?>> results = new ArrayList<Class<?>>(); 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<? extends Annotation> annotation, String suffix) { super(String.format("The @%s '%s' %s", annotation.getSimpleName(), member.getName(), suffix)); } diff --git a/src/main/java/org/junit/internal/runners/statements/ExpectException.java b/src/main/java/org/junit/internal/runners/statements/ExpectException.java index 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; @@ -80,6 +89,20 @@ public class FailOnTimeout extends Statement { } /** + * 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<Throwable> task = new FutureTask<Throwable>(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.<Throwable>asList(currThreadException, stuckThreadException)); + } else { + return currThreadException; + } + } + + /** + * Retrieves the stack trace for a given thread. + * @param thread The thread whose stack is to be retrieved. + * @return The stack trace; returns a zero-length array if the thread has + * terminated or the stack cannot be retrieved for some other reason. + */ + private StackTraceElement[] getStackTrace(Thread thread) { + try { + return thread.getStackTrace(); + } catch (SecurityException e) { + return new StackTraceElement[0]; + } + } + + /** + * Determines whether the test appears to be stuck in some thread other than + * the "main thread" (the one created to run the test). This feature is experimental. + * Behavior may change after the 4.12 release in response to feedback. + * @param mainThread The main thread created by {@code evaluate()} + * @return The thread which appears to be causing the problem, if different from + * {@code mainThread}, or {@code null} if the main thread appears to be the + * problem or if the thread cannot be determined. The return value is never equal + * to {@code mainThread}. + */ + private Thread getStuckThread(Thread mainThread) { + List<Thread> 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<Thread> 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<Throwable> { 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 <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... elementMatchers) { - return CoreMatchers.<T>hasItems(elementMatchers); + return CoreMatchers.hasItems(elementMatchers); } /** @@ -57,7 +57,7 @@ public class JUnitMatchers { */ @Deprecated public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> 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. + * <p> + * The most illustrative use case is for tests that make use of the + * {@link Timeout} rule, when ran in debug mode the test may terminate on + * timeout abruptly during debugging. Developers may disable the timeout, or + * increase the timeout by making a code change on tests that need debugging and + * remember revert the change afterwards or rules such as {@link Timeout} that + * may be disabled during debugging may be wrapped in a {@code DisableOnDebug}. + * <p> + * The important benefit of this feature is that you can disable such rules + * without any making any modifications to your test class to remove them during + * debugging. + * <p> + * This does nothing to tackle timeouts or time sensitive code under test when + * debugging and may make this less useful in such circumstances. + * <p> + * Example usage: + * + * <pre> + * public static class DisableTimeoutOnDebugSampleTest { + * + * @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 + * } + * } + * </pre> + * + * @since 4.12 + */ +public class DisableOnDebug implements TestRule { + private final TestRule rule; + private final boolean debugging; + + /** + * Create a {@code DisableOnDebug} instance with the timeout specified in + * milliseconds. + * + * @param rule to disable during debugging + */ + public DisableOnDebug(TestRule rule) { + this(rule, ManagementFactory.getRuntimeMXBean() + .getInputArguments()); + } + + /** + * Visible for testing purposes only. + * + * @param rule the rule to disable during debugging + * @param inputArguments + * arguments provided to the Java runtime + */ + DisableOnDebug(TestRule rule, List<String> inputArguments) { + this.rule = rule; + debugging = isDebugging(inputArguments); + } + + /** + * @see TestRule#apply(Statement, Description) + */ + public Statement apply(Statement base, Description description) { + if (debugging) { + return base; + } else { + return rule.apply(base, description); + } + } + + /** + * Parses arguments passed to the runtime environment for debug flags + * <p> + * Options specified in: + * <ul> + * <li> + * <a href="http://docs.oracle.com/javase/6/docs/technotes/guides/jpda/conninv.html#Invocation" + * >javase-6</a></li> + * <li><a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/conninv.html#Invocation" + * >javase-7</a></li> + * <li><a href="http://docs.oracle.com/javase/8/docs/technotes/guides/jpda/conninv.html#Invocation" + * >javase-8</a></li> + * + * + * @param arguments + * the arguments passed to the runtime environment, usually this + * will be {@link RuntimeMXBean#getInputArguments()} + * @return true if the current JVM was started in debug mode, false + * otherwise. + */ + private static boolean isDebugging(List<String> arguments) { + for (final String argument : arguments) { + if ("-Xdebug".equals(argument) || 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> T checkSucceeds(Callable<T> 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<? extends Throwable> 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; * * <pre> 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; * } * }</pre> * - * <p> - * You have to add the {@code ExpectedException} rule to your test. + * <p>You have to add the {@code ExpectedException} rule to your test. * This doesn't affect your existing tests (see {@code throwsNothing()}). - * After specifiying the type of the expected exception your test is + * 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. * - * <p> - * Instead of specifying the exception's type you can characterize the - * expected exception based on other criterias, too: + * <p>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. + * + * <p>Instead of specifying the exception's type you can characterize the + * expected exception based on other criteria, too: * * <ul> * <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li> @@ -53,8 +55,7 @@ import org.junit.runners.model.Statement; * <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li> * </ul> * - * <p> - * You can combine any of the presented expect-methods. The test is + * <p>You can combine any of the presented expect-methods. The test is * successful if all specifications are met. * <pre> @Test * public void throwsException() { @@ -63,9 +64,15 @@ import org.junit.runners.model.Statement; * throw new NullPointerException("What happened?"); * }</pre> * + * <p>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. + * <pre> @Rule(order = Integer.MAX_VALUE) + * public ExpectedException thrown = ExpectedException.none();</pre> + * * <h3>AssumptionViolatedExceptions</h3> - * <p> - * JUnit uses {@link AssumptionViolatedException}s for indicating that a test + * <p>JUnit uses {@link AssumptionViolatedException}s for indicating that a test * provides no useful information. (See {@link org.junit.Assume} for more * information.) You have to call {@code assume} methods before you set * expectations of the {@code ExpectedException} rule. In this case the rule @@ -80,8 +87,7 @@ import org.junit.runners.model.Statement; * * <h3>AssertionErrors</h3> * - * <p> - * JUnit uses {@link AssertionError}s for indicating that a test is failing. You + * <p>JUnit uses {@link AssertionError}s for indicating that a test is failing. You * have to call {@code assert} methods before you set expectations of the * {@code ExpectedException} rule, if they should be handled by the framework. * E.g. the following test fails because of the {@code assertTrue} statement. @@ -93,8 +99,7 @@ import org.junit.runners.model.Statement; * }</pre> * * <h3>Missing Exceptions</h3> - * <p> - * By default missing exceptions are reported with an error message + * <p>By default missing exceptions are reported with an error message * like "Expected test to throw an instance of foo". You can configure a different * message by means of {@link #reportMissingExceptionWithMessage(String)}. You * can use a {@code %s} placeholder for the description of the expected @@ -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); * }</pre> */ - public void expectCause(Matcher<? extends Throwable> 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<Throwable> errors = new ArrayList<Throwable>(); 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}. * - * <ul> - * <li>{@link ErrorCollector}: collect multiple errors in one test method</li> - * <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li> - * <li>{@link ExternalResource}: start and stop a server, for example</li> - * <li>{@link TemporaryFolder}: create fresh files, and delete after test</li> - * <li>{@link TestName}: remember the test name for use during the method</li> - * <li>{@link TestWatchman}: add logic at events during method execution</li> - * <li>{@link Timeout}: cause test to fail after a set time</li> - * <li>{@link Verifier}: fail test if object state ends up incorrect</li> - * </ul> - * - * Note that {@link MethodRule} has been replaced by {@link TestRule}, + * <p>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)}: * * <pre> - * 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")); + * } + * } + * </pre> + * + * <pre> + * public class UseRuleChain { + * @Rule + * public final TestRule extendedLogging = CompositeRules.extendedLogging(); * - * @Test - * public void example() { - * assertTrue(true); - * } + * @Test + * public void example() { + * assertTrue(true); + * } * } * </pre> * @@ -38,6 +46,13 @@ import org.junit.runners.model.Statement; * finished outer rule * </pre> * + * 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<TestRule> rulesOfNewChain = new ArrayList<TestRule>(); 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. * * <p>Example of usage: * <pre> @@ -26,18 +31,104 @@ import org.junit.Rule; * } * </pre> * + * <p>TemporaryFolder rule supports assured deletion mode, which + * will fail the test in case deletion fails with {@link AssertionError}. + * + * <p>Creating TemporaryFolder with assured deletion: + * <pre> + * @Rule + * public TemporaryFolder folder= TemporaryFolder.builder().assureDeletion().build(); + * </pre> + * * @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; * } * } * </pre> + * <p>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<Throwable> 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(); } /** @@ -122,6 +125,16 @@ public class Timeout implements TestRule { } /** + * Gets whether this {@code Timeout} will look for a stuck thread + * when the test times out. + * + * @since 4.12 + */ + protected final boolean getLookingForStuckThread() { + return lookForStuckThread; + } + + /** * Creates a {@link Statement} that will run the given * {@code statement}, and timeout the operation based * on the values configured in this rule. Subclasses @@ -133,6 +146,7 @@ public class Timeout implements TestRule { Statement statement) throws Exception { return FailOnTimeout.builder() .withTimeout(timeout, timeUnit) + .withLookingForStuckThread(lookForStuckThread) .build(statement); } @@ -191,6 +205,25 @@ public class Timeout implements TestRule { } /** + * Specifies whether to look for a stuck thread. If a timeout occurs and this + * feature is enabled, the rule will look for a thread that appears to be stuck + * and dump its backtrace. This feature is experimental. Behavior may change + * after the 4.12 release in response to feedback. + * + * @param enable {@code true} to enable the feature + * @return {@code this} for method chaining. + */ + public Builder withLookingForStuckThread(boolean enable) { + this.lookForStuckThread = enable; + return this; + } + + protected boolean getLookingForStuckThread() { + return lookForStuckThread; + } + + + /** * Builds a {@link Timeout} instance using the values in this builder., */ public Timeout build() { 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 @@ -125,6 +125,17 @@ public class Description implements Serializable { } /** + * Create a <code>Description</code> named after <code>testClass</code> + * + * @param testClass A not null {@link Class} containing tests + * @param annotations meta-data about the test, for downstream interpreters + * @return a <code>Description</code> of <code>testClass</code> + */ + public static Description createSuiteDescription(Class<?> testClass, Annotation... annotations) { + return new Description(testClass, testClass.getName(), annotations); + } + + /** * Describes a Runner which runs no tests */ public static final Description EMPTY = new Description(null, "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<Description> fChildren = new ConcurrentLinkedQueue<Description>(); 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<String> result = new ArrayList<String>();
-
+ 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 <code>@OrderWith</code> or extends a class annotated + * with <code>@OrderWith</code>, 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<? extends Ordering.Factory> 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<Exception> 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 <code>desiredDescription</code> + * 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 + * <p>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.</p> + * + * @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: * <pre> * 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())); * } * </pre> * @@ -167,4 +171,32 @@ public abstract class Request { public Request sortWith(Comparator<Description> comparator) { return new SortingRequest(this, comparator); } + + /** + * Returns a Request whose Tests can be run in a certain order, defined by + * <code>ordering</code> + * <p> + * For example, here is code to run a test suite in reverse order: + * <pre> + * 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())); + * } + * </pre> + * + * @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<Failure> 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<Failure>(); 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<Failure>(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<Failure> getFailures() { return failures; @@ -87,6 +90,20 @@ public class Result implements Serializable { } /** + * 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 <code>true</code> if all tests succeeded */ public boolean wasSuccessful() { @@ -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<Failure> 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<Failure>(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<Failure>) 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<Description> COMPARATOR = new Comparator<Description>() { + 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. + * + * <p>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 <code>orderer</code> + * + * @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<Description> order(Collection<Description> descriptions) + throws InvalidOrderingException { + List<Description> inOrder = ordering.orderItems( + Collections.unmodifiableCollection(descriptions)); + if (!ordering.validateOrderingIsCorrect()) { + return inOrder; + } + + Set<Description> uniqueDescriptions = new HashSet<Description>(descriptions); + if (!uniqueDescriptions.containsAll(inOrder)) { + throw new InvalidOrderingException("Ordering added items"); + } + Set<Description> resultAsSet = new HashSet<Description>(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 <code>target</code>. + * + * @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. + * + * <p>In general you will not need to use a <code>Ordering</code> 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<Description> orderItems(Collection<Description> descriptions) { + List<Description> shuffled = new ArrayList<Description>(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<? extends Ordering.Factory> 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<? extends Ordering.Factory> 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 <code>target</code> 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<Description> orderItems(Collection<Description> 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. + * + * <p>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 <code>Sorter</code> orders tests. In general you will not need - * to use a <code>Sorter</code> directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * to use a <code>Sorter</code> directly. Instead, use + * {@link org.junit.runner.Request#sortWith(Comparator)}. * * @since 4.0 */ -public class Sorter implements Comparator<Description> { +public class Sorter extends Ordering implements Comparator<Description> { /** * NULL is a <code>Sorter</code> that leaves elements in an undefined order */ @@ -27,17 +32,26 @@ public class Sorter implements Comparator<Description> { * to sort tests * * @param comparator the {@link Comparator} to use when sorting tests + * @since 4.0 */ public Sorter(Comparator<Description> comparator) { this.comparator = comparator; } /** - * Sorts the test in <code>runner</code> using <code>comparator</code> + * Sorts the tests in <code>target</code> using <code>comparator</code>. + * + * @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<Description> { public int compare(Description o1, Description o2) { return comparator.compare(o1, o2); } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + @Override + protected final List<Description> orderItems(Collection<Description> 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<Description> sorted = new ArrayList<Description>(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 @@ -70,6 +70,34 @@ public class RunListener { } /** + * 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}. + * + * <p>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. * * @param description the description of the test that is about to be run 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<RunListener> safeListeners = new ArrayList<RunListener>(capacity); - ArrayList<Failure> failures = new ArrayList<Failure>(capacity); + List<RunListener> safeListeners = new ArrayList<RunListener>(capacity); + List<Failure> failures = new ArrayList<Failure>(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; } /** @@ -106,6 +106,41 @@ public class RunNotifier { } /** + * 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. * * @param description the description of the atomic test (generally a class and method name) 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; * <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because * prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier, * so no two listeners were ever called concurrently. If we instead made the methods here - * sychronized, clients that added multiple listeners that called common code might see + * 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} + * <p/> + * 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} + * <p/> + * 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<FrameworkMethod> { - private final ConcurrentHashMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>(); + private static TestClassValidator PUBLIC_CLASS_VALIDATOR = new PublicClassValidator(); + + private final ConcurrentMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>(); + + /** + * 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<FrameworkMethod> { 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<FrameworkMethod> { protected void collectInitializationErrors(List<Throwable> errors) { super.collectInitializationErrors(errors); + validatePublicConstructor(errors); validateNoNonStaticInnerClass(errors); validateConstructor(errors); validateInstanceMethods(errors); @@ -130,6 +156,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { validateMethods(errors); } + private void validatePublicConstructor(List<Throwable> errors) { + if (getTestClass().getJavaClass() != null) { + errors.addAll(PUBLIC_CLASS_VALIDATOR.validateTestClass(getTestClass())); + } + } + protected void validateNoNonStaticInnerClass(List<Throwable> errors) { if (getTestClass().isANonStaticInnerClass()) { String gripe = "The inner class " + getTestClass().getName() @@ -180,6 +212,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { * 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<Throwable> errors) { @@ -187,7 +220,7 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { validatePublicVoidNoArgMethods(Before.class, false, errors); validateTestMethods(errors); - if (computeTestMethods().size() == 0) { + if (computeTestMethods().isEmpty()) { errors.add(new Exception("No runnable methods")); } } @@ -218,6 +251,16 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { } /** + * 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<FrameworkMethod> { * Here is an outline of the default implementation: * * <ul> - * <li>Invoke {@code method} on the result of {@code createTest()}, and + * <li>Invoke {@code method} on the result of {@link #createTest(org.junit.runners.model.FrameworkMethod)}, and * throw any exceptions thrown by either operation. - * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code - * expecting} attribute, return normally only if the previous step threw an + * <li>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. * <li>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<FrameworkMethod> { * 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<FrameworkMethod> { 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<FrameworkMethod> { /** * 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<? extends Throwable> 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<FrameworkMethod> { target); } - private Statement withRules(FrameworkMethod method, Object target, - Statement statement) { - List<TestRule> testRules = getTestRules(target); - Statement result = statement; - result = withMethodRules(method, testRules, target, result); - result = withTestRules(method, testRules, result); - - return result; - } - - private Statement withMethodRules(FrameworkMethod method, List<TestRule> testRules, - Object target, Statement result) { - for (org.junit.rules.MethodRule each : getMethodRules(target)) { - if (!testRules.contains(each)) { - result = each.apply(result, method, target); + private Statement withRules(FrameworkMethod method, Object target, Statement statement) { + RuleContainer ruleContainer = new RuleContainer(); + CURRENT_RULE_CONTAINER.set(ruleContainer); + try { + List<TestRule> 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<org.junit.rules.MethodRule> getMethodRules(Object target) { - return rules(target); + return ruleContainer.apply(method, describeChild(method), target, statement); } /** @@ -378,27 +418,12 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { * test */ protected List<MethodRule> rules(Object target) { - List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(target, - Rule.class, MethodRule.class); - - rules.addAll(getTestClass().getAnnotatedFieldValues(target, - Rule.class, MethodRule.class)); - - return rules; - } - - /** - * Returns a {@link Statement}: apply all non-static fields - * annotated with {@link Rule}. - * - * @param statement The base statement - * @return a RunRules statement if any class-level {@link Rule}s are - * found, or the base statement - */ - private Statement withTestRules(FrameworkMethod method, List<TestRule> testRules, - Statement statement) { - return testRules.isEmpty() ? statement : - new RunRules(statement, testRules, describeChild(method)); + RuleCollector<MethodRule> collector = new RuleCollector<MethodRule>(); + 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<FrameworkMethod> { * test */ protected List<TestRule> getTestRules(Object target) { - List<TestRule> result = getTestClass().getAnnotatedMethodValues(target, - Rule.class, TestRule.class); - - result.addAll(getTestClass().getAnnotatedFieldValues(target, - Rule.class, TestRule.class)); - - return result; + RuleCollector<TestRule> collector = new RuleCollector<TestRule>(); + getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector); + getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector); + return collector.result; } private Class<? extends Throwable> getExpectedException(Test annotation) { @@ -424,14 +446,28 @@ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { } } - 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<RuleContainer> CURRENT_RULE_CONTAINER = + new ThreadLocal<RuleContainer>(); + + private static class RuleCollector<T> implements MemberValueConsumer<T> { + final List<T> result = new ArrayList<T>(); + + 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. * <p> - * For example, to test a Fibonacci function, write: + * For example, to test the <code>+</code> operator, write: * <pre> * @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); * } * } * </pre> * <p> - * Each instance of <code>FibonacciTest</code> will be constructed using the - * two-argument constructor and the data values in the + * Each instance of <code>AdditionTest</code> will be constructed using the + * three-argument constructor and the data values in the * <code>@Parameters</code> method. * <p> * In order that you can easily identify the individual tests, you may provide a @@ -69,33 +79,36 @@ import org.junit.runners.parameterized.TestWithParameters; * </dl> * <p> * In the example given above, the <code>Parameterized</code> runner creates - * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter, + * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter, * then the current parameter index is used as name. * <p> * You can also write: * <pre> * @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); + * } * } * </pre> * <p> - * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor + * Each instance of <code>AdditionTest</code> will be constructed with the default constructor * and fields annotated by <code>@Parameter</code> will be initialized * with the data values in the <code>@Parameters</code> method. * @@ -105,8 +118,7 @@ import org.junit.runners.parameterized.TestWithParameters; * <pre> * @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 } } }; * } * </pre> * @@ -130,6 +142,19 @@ import org.junit.runners.parameterized.TestWithParameters; * } * </pre> * + * <h3>Executing code before/after executing tests for specific parameters</h3> + * <p> + * 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. + * <pre> + * @BeforeParam + * public static void beforeTestsForParameter(String onlyParameter) { + * System.out.println("Testing " + onlyParameter); + * } + * </pre> + * * <h3>Create different runners</h3> * <p> * 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. * * <pre> - * 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; * } * </pre> * + * <h3>Avoid creating parameters</h3> + * <p>With {@link org.junit.Assume assumptions} you can dynamically skip tests. + * Assumptions are also supported by the <code>@Parameters</code> 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. + * <pre> + * @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"); + * } + * </pre> * @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 <code>Parameters</code>. @@ -230,122 +270,235 @@ public class Parameterized extends Suite { Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class; } - private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); - - private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList(); + /** + * 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<Runner> 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<? extends ParametersRunnerFactory> factoryClass = annotation - .value(); - return factoryClass.newInstance(); - } + private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception { + super(klass, runnersFactory.createRunners()); + validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount); } - @Override - protected List<Runner> getChildren() { - return runners; + private void validateBeforeParamAndAfterParamMethods(Integer parameterCount) + throws InvalidTestClassError { + List<Throwable> errors = new ArrayList<Throwable>(); + 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<? extends Annotation> annotation, Integer parameterCount, + List<Throwable> errors) { + List<FrameworkMethod> 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<Object> allParameters() throws Throwable { - Object parameters = getParametersMethod().invokeExplosively(null); - if (parameters instanceof Iterable) { - return (Iterable<Object>) parameters; - } else if (parameters instanceof Object[]) { - return Arrays.asList((Object[]) parameters); - } else { - throw parametersMethodReturnedWrongType(); + private 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<FrameworkMethod> 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<Object> allParameters; + private final int parameterCount; + private final Runner runnerOverride; + + private RunnersFactory(Class<?> klass) throws Throwable { + testClass = new TestClass(klass); + parametersMethod = getParametersMethod(testClass); + List<Object> 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<Runner> 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<Runner> createRunnersForParameters( - Iterable<Object> allParameters, String namePattern, - ParametersRunnerFactory runnerFactory) - throws InitializationError, - Exception { - try { - List<TestWithParameters> tests = createTestsForParameters( - allParameters, namePattern); - List<Runner> runners = new ArrayList<Runner>(); - for (TestWithParameters test : tests) { - runners.add(runnerFactory - .createRunnerForTestWithParameters(test)); + private ParametersRunnerFactory getParametersRunnerFactory() + throws InstantiationException, IllegalAccessException { + UseParametersRunnerFactory annotation = testClass + .getAnnotation(UseParametersRunnerFactory.class); + if (annotation == null) { + return DEFAULT_FACTORY; + } else { + Class<? extends ParametersRunnerFactory> factoryClass = annotation + .value(); + return factoryClass.newInstance(); } - return runners; - } catch (ClassCastException e) { - throw parametersMethodReturnedWrongType(); } - } - private List<TestWithParameters> createTestsForParameters( - Iterable<Object> allParameters, String namePattern) - throws Exception { - int i = 0; - List<TestWithParameters> children = new ArrayList<TestWithParameters>(); - for (Object parametersOfSingleTest : allParameters) { - children.add(createTestWithNotNormalizedParameters(namePattern, - i++, parametersOfSingleTest)); + 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<Object> allParameters( + TestClass testClass, FrameworkMethod parametersMethod) throws Throwable { + Object parameters = parametersMethod.invokeExplosively(null); + if (parameters instanceof List) { + return (List<Object>) parameters; + } else if (parameters instanceof Collection) { + return new ArrayList<Object>((Collection<Object>) parameters); + } else if (parameters instanceof Iterable) { + List<Object> result = new ArrayList<Object>(); + for (Object entry : ((Iterable<Object>) 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<FrameworkMethod> 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<Runner> createRunnersForParameters( + Iterable<Object> allParameters, String namePattern, + ParametersRunnerFactory runnerFactory) throws Exception { + try { + List<TestWithParameters> tests = createTestsForParameters( + allParameters, namePattern); + List<Runner> runners = new ArrayList<Runner>(); + for (TestWithParameters test : tests) { + runners.add(runnerFactory + .createRunnerForTestWithParameters(test)); + } + return runners; + } catch (ClassCastException e) { + throw parametersMethodReturnedWrongType(testClass, parametersMethod); + } + } + + private List<TestWithParameters> createTestsForParameters( + Iterable<Object> allParameters, String namePattern) + throws Exception { + int i = 0; + List<TestWithParameters> children = new ArrayList<TestWithParameters>(); + for (Object parametersOfSingleTest : allParameters) { + children.add(createTestWithNotNormalizedParameters(namePattern, + i++, parametersOfSingleTest)); + } + return children; + } + + private 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 index 92641bf..0a0e7cb 100755..100644 --- 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<T> extends Runner implements Filterable, - Sortable { - private static final List<TestClassValidator> VALIDATORS = Arrays.asList( - new AnnotationsValidator(), new PublicClassValidator()); + Orderable { + private static final List<TestClassValidator> VALIDATORS = Collections.<TestClassValidator>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<T> filteredChildren = null; + private volatile List<T> filteredChildren = null; private volatile RunnerScheduler scheduler = new RunnerScheduler() { public void schedule(Runnable childStatement) { @@ -84,6 +92,21 @@ public abstract class ParentRunner<T> 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<T> 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<T> 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<T> extends Runner implements Filterable, * each method in the tested class. */ protected List<TestRule> classRules() { - List<TestRule> result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class); - result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class)); - return result; + ClassRuleCollector collector = new ClassRuleCollector(); + testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector); + testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector); + return collector.getOrderedRules(); } /** @@ -271,6 +296,22 @@ public abstract class ParentRunner<T> 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 <code>false</code>. * @@ -346,8 +387,16 @@ public abstract class ParentRunner<T> 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<T> 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<T> extends Runner implements Filterable, throw e; } catch (Throwable e) { testNotifier.addFailure(e); + } finally { + testNotifier.fireTestSuiteFinished(); } } @@ -375,7 +427,8 @@ public abstract class ParentRunner<T> extends Runner implements Filterable, // public void filter(Filter filter) throws NoTestsRemainException { - synchronized (childrenLock) { + childrenLock.lock(); + try { List<T> children = new ArrayList<T>(getFilteredChildren()); for (Iterator<T> iter = children.iterator(); iter.hasNext(); ) { T each = iter.next(); @@ -389,21 +442,70 @@ public abstract class ParentRunner<T> 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<T> sortedChildren = new ArrayList<T>(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<T> children = getFilteredChildren(); + // In theory, we could have duplicate Descriptions. De-dup them before ordering, + // and add them back at the end. + Map<Description, List<T>> childMap = new LinkedHashMap<Description, List<T>>( + children.size()); + for (T child : children) { + Description description = describeChild(child); + List<T> childrenWithDescription = childMap.get(description); + if (childrenWithDescription == null) { + childrenWithDescription = new ArrayList<T>(1); + childMap.put(description, childrenWithDescription); + } + childrenWithDescription.add(child); + orderer.apply(child); + } + + List<Description> inOrder = orderer.order(childMap.keySet()); + + children = new ArrayList<T>(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<T> 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<Throwable> errors = new ArrayList<Throwable>(); collectInitializationErrors(errors); if (!errors.isEmpty()) { - throw new InitializationError(errors); + throw new InvalidTestClassError(testClass.getJavaClass(), errors); } } - private Collection<T> getFilteredChildren() { + private List<T> getFilteredChildren() { if (filteredChildren == null) { - synchronized (childrenLock) { + childrenLock.lock(); + try { if (filteredChildren == null) { - filteredChildren = Collections.unmodifiableCollection(getChildren()); + filteredChildren = Collections.unmodifiableList( + new ArrayList<T>(getChildren())); } + } finally { + childrenLock.unlock(); } } return filteredChildren; @@ -449,4 +560,23 @@ public abstract class ParentRunner<T> extends Runner implements Filterable, public void setScheduler(RunnerScheduler scheduler) { this.scheduler = scheduler; } + + private static class ClassRuleCollector implements MemberValueConsumer<TestRule> { + final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>(); + + 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<TestRule> getOrderedRules() { + Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR); + List<TestRule> result = new ArrayList<TestRule>(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<Object, Integer> orderValues = new IdentityHashMap<Object, Integer>(); + private final List<TestRule> testRules = new ArrayList<TestRule>(); + private final List<MethodRule> methodRules = new ArrayList<MethodRule>(); + + /** + * 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<RuleEntry> ENTRY_COMPARATOR = new Comparator<RuleEntry>() { + 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<RuleEntry> getSortedEntries() { + List<RuleEntry> ruleEntries = new ArrayList<RuleEntry>( + 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<Object> getSortedRules() { + List<Object> result = new ArrayList<Object>(); + 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<Runner> { /** * @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<Runner> { * @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<FrameworkField> { private final Field field; - FrameworkField(Field field) { + /** + * Returns a new {@code FrameworkField} for {@code field}. + * + * <p>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 @@ -41,6 +55,11 @@ public class FrameworkField extends FrameworkMember<FrameworkField> { } @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<T extends FrameworkMember<T>> implements Annotatable { abstract boolean isShadowedBy(T otherMember); - boolean isShadowedBy(List<T> members) { - for (T each : members) { - if (isShadowedBy(each)) { - return true; + T handlePossibleBridgeMethod(List<T> 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> { "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 + } + } } /** @@ -149,6 +158,11 @@ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { } @Override + boolean isBridgeMethod() { + return method.isBridge(); + } + + @Override public boolean equals(Object obj) { if (!FrameworkMethod.class.isInstance(obj)) { return false; 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<Throwable> 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. + * <p> + * 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<Throwable> validationErrors) { + super(validationErrors); + this.message = createMessage(offendingTestClass, validationErrors); + } + + private static String createMessage(Class<?> testClass, List<Throwable> 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<T> { + /** + * 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<Throwable> fErrors; public MultipleFailureException(List<Throwable> errors) { - this.fErrors = new ArrayList<Throwable>(errors); + if (errors.isEmpty()) { + throw new IllegalArgumentException( + "List of Throwables must not be empty"); + } + this.fErrors = new ArrayList<Throwable>(errors.size()); + for (Throwable error : errors) { + if (error instanceof AssumptionViolatedException) { + error = new TestCouldNotBeSkippedException((AssumptionViolatedException) error); + } + fErrors.add(error); + } } public List<Throwable> 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. + * + * <p>In case of an exception a runner will be returned that prints an error instead of running + * tests. + * + * <p>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<Runner> runners(Class<?>[] children) { - ArrayList<Runner> runners = new ArrayList<Runner>(); + List<Runner> runners = new ArrayList<Runner>(); 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 index c8a544d..5962c2b 100755..100644 --- 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<? extends Annotation> type = each.annotationType(); List<T> 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 <T extends FrameworkMember<T>> Map<Class<? extends Annotation>, List<T>> makeDeeplyUnmodifiable(Map<Class<? extends Annotation>, List<T>> source) { - LinkedHashMap<Class<? extends Annotation>, List<T>> copy = + Map<Class<? extends Annotation>, List<T>> copy = new LinkedHashMap<Class<? extends Annotation>, List<T>>(); for (Map.Entry<Class<? extends Annotation>, List<T>> entry : source.entrySet()) { copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); @@ -168,7 +169,7 @@ public class TestClass implements Annotatable { } private static List<Class<?>> getSuperClasses(Class<?> testClass) { - ArrayList<Class<?>> results = new ArrayList<Class<?>>(); + List<Class<?>> results = new ArrayList<Class<?>>(); Class<?> current = testClass; while (current != null) { results.add(current); @@ -224,24 +225,59 @@ public class TestClass implements Annotatable { public <T> List<T> getAnnotatedFieldValues(Object test, Class<? extends Annotation> annotationClass, Class<T> valueClass) { - List<T> results = new ArrayList<T>(); + final List<T> results = new ArrayList<T>(); + collectAnnotatedFieldValues(test, annotationClass, valueClass, + new MemberValueConsumer<T>() { + 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 <T> void collectAnnotatedFieldValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass, + MemberValueConsumer<T> 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 <T> List<T> getAnnotatedMethodValues(Object test, Class<? extends Annotation> annotationClass, Class<T> valueClass) { - List<T> results = new ArrayList<T>(); + final List<T> results = new ArrayList<T>(); + collectAnnotatedMethodValues(test, annotationClass, valueClass, + new MemberValueConsumer<T>() { + 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 <T> void collectAnnotatedMethodValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass, + MemberValueConsumer<T> 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<Throwable> errors) { validateOnlyOneConstructor(errors); - if (fieldsAreAnnotated()) { + if (getInjectionType() != InjectionType.CONSTRUCTOR) { validateZeroArgConstructor(errors); } } @@ -94,7 +114,7 @@ public class BlockJUnit4ClassRunnerWithParameters extends @Override protected void validateFields(List<Throwable> errors) { super.validateFields(errors); - if (fieldsAreAnnotated()) { + if (getInjectionType() == InjectionType.FIELD) { List<FrameworkField> 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<FrameworkMethod> befores = getTestClass() + .getAnnotatedMethods(Parameterized.BeforeParam.class); + return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores); + } + + private class RunBeforeParams extends RunBefores { + RunBeforeParams(Statement next, List<FrameworkMethod> 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<FrameworkMethod> afters = getTestClass() + .getAnnotatedMethods(Parameterized.AfterParam.class); + return afters.isEmpty() ? statement : new RunAfterParams(statement, afters); + } + + private class RunAfterParams extends RunAfters { + RunAfterParams(Statement next, List<FrameworkMethod> 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<FrameworkField> 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<? extends AnnotationValidator> clazz = validateWithAnnotation.value(); - if (clazz == null) { - throw new IllegalArgumentException("Can't create validator, value is null in annotation " + validateWithAnnotation.getClass().getName()); - } try { AnnotationValidator annotationValidator = clazz.newInstance(); VALIDATORS_FOR_ANNOTATION_TYPES.putIfAbsent(validateWithAnnotation, annotationValidator); 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<T extends Annotatable> { + private abstract static class AnnotatableValidator<T extends Annotatable> { private static final AnnotationValidatorFactory ANNOTATION_VALIDATOR_FACTORY = new AnnotationValidatorFactory(); abstract Iterable<T> 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<Exception> validateTestClass(TestClass testClass); + List<Exception> validateTestClass(TestClass testClass); } diff --git a/src/main/java/org/junit/validator/ValidateWith.java b/src/main/java/org/junit/validator/ValidateWith.java 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<? extends AnnotationValidator> value(); |