diff options
Diffstat (limited to 'src/main/java/org')
165 files changed, 11022 insertions, 6348 deletions
diff --git a/src/main/java/org/junit/After.java b/src/main/java/org/junit/After.java index 39aa6e5..eae7e3a 100644 --- a/src/main/java/org/junit/After.java +++ b/src/main/java/org/junit/After.java @@ -6,15 +6,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>If you allocate external resources in a {@link org.junit.Before} method you need to release them + * If you allocate external resources in a {@link org.junit.Before} method you need to release them * after the test runs. Annotating a <code>public void</code> method * with <code>@After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>@After</code> - * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an + * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an * exception. The <code>@After</code> methods declared in superclasses will be run after those of the current - * class.</p> - * + * class, unless they are overridden in the current class. + * <p> * Here is a simple example: -* <pre> + * <pre> * public class Example { * File output; * @Before public void createOutputFile() { @@ -28,9 +28,10 @@ import java.lang.annotation.Target; * } * } * </pre> - * + * * @see org.junit.Before * @see org.junit.Test + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/org/junit/AfterClass.java b/src/main/java/org/junit/AfterClass.java index 2d6bc80..dba4109 100644 --- a/src/main/java/org/junit/AfterClass.java +++ b/src/main/java/org/junit/AfterClass.java @@ -6,15 +6,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them + * If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them * after all the tests in the class have run. Annotating a <code>public static void</code> method * with <code>@AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>@AfterClass</code> - * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an + * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an * exception. The <code>@AfterClass</code> methods declared in superclasses will be run after those of the current - * class.</p> - * + * class, unless they are shadowed in the current class. + * <p> * Here is a simple example: -* <pre> + * <pre> * public class Example { * private static DatabaseConnection database; * @BeforeClass public static void login() { @@ -31,9 +31,10 @@ import java.lang.annotation.Target; * } * } * </pre> - * + * * @see org.junit.BeforeClass * @see org.junit.Test + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/src/main/java/org/junit/Assume.java b/src/main/java/org/junit/Assume.java index 7b6c21a..b7687f7 100644 --- a/src/main/java/org/junit/Assume.java +++ b/src/main/java/org/junit/Assume.java @@ -1,27 +1,25 @@ package org.junit; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.everyItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; + import org.hamcrest.Matcher; -import org.junit.internal.AssumptionViolatedException; -import org.junit.internal.matchers.Each; /** * A set of methods useful for stating assumptions about the conditions in which a test is meaningful. - * A failed assumption does not mean the code is broken, but that the test provides no useful information. - * The default JUnit runner treats tests with failing assumptions as ignored. Custom runners may behave differently. - * - * For example: - * <pre> - * // only provides information if database is reachable. - * \@Test public void calculateTotalSalary() { - * DBConnection dbc = Database.connect(); - * assumeNotNull(dbc); - * // ... - * } - * </pre> + * A failed assumption does not mean the code is broken, but that the test provides no useful information. Assume + * basically means "don't run this test if these conditions don't apply". The default JUnit runner skips tests with + * failing assumptions. Custom runners may behave differently. + * <p> + * A good example of using assumptions is in <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a> where they are needed to exclude certain datapoints that aren't suitable or allowed for a certain test case. + * </p> + * Failed assumptions are usually not logged, because there may be many tests that don't apply to certain + * configurations. + * + * <p> * These methods can be used directly: <code>Assume.assumeTrue(...)</code>, however, they * read better if they are referenced through static import:<br/> * <pre> @@ -29,66 +27,132 @@ import org.junit.internal.matchers.Each; * ... * assumeTrue(...); * </pre> + * </p> + * + * @see <a href="https://github.com/junit-team/junit/wiki/Theories">Theories</a> + * + * @since 4.4 */ public class Assume { - /** - * If called with an expression evaluating to {@code false}, the test will halt and be ignored. - * @param b - */ - public static void assumeTrue(boolean b) { - assumeThat(b, is(true)); - } + /** + * If called with an expression evaluating to {@code false}, the test will halt and be ignored. + */ + public static void assumeTrue(boolean b) { + assumeThat(b, is(true)); + } + + /** + * The inverse of {@link #assumeTrue(boolean)}. + */ + public static void assumeFalse(boolean b) { + assumeTrue(!b); + } + + /** + * If called with an expression evaluating to {@code false}, the test will halt and be ignored. + * + * @param b If <code>false</code>, the method will attempt to stop the test and ignore it by + * throwing {@link AssumptionViolatedException}. + * @param message A message to pass to {@link AssumptionViolatedException}. + */ + public static void assumeTrue(String message, boolean b) { + if (!b) throw new AssumptionViolatedException(message); + } - /** - * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored. - * @param objects - */ - public static void assumeNotNull(Object... objects) { - assumeThat(asList(objects), Each.each(notNullValue())); - } + /** + * The inverse of {@link #assumeTrue(String, boolean)}. + */ + public static void assumeFalse(String message, boolean b) { + assumeTrue(message, !b); + } + + /** + * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored. + */ + public static void assumeNotNull(Object... objects) { + assumeThat(asList(objects), everyItem(notNullValue())); + } + + /** + * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>. + * If not, the test halts and is ignored. + * Example: + * <pre>: + * assumeThat(1, is(1)); // passes + * foo(); // will execute + * assumeThat(0, is(1)); // assumption failure! test halts + * int x = 1 / 0; // will never execute + * </pre> + * + * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))} + * @param actual the computed value being compared + * @param matcher an expression, built of {@link Matcher}s, specifying allowed values + * @see org.hamcrest.CoreMatchers + * @see org.junit.matchers.JUnitMatchers + */ + public static <T> void assumeThat(T actual, Matcher<T> matcher) { + if (!matcher.matches(actual)) { + throw new AssumptionViolatedException(actual, matcher); + } + } + + /** + * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>. + * If not, the test halts and is ignored. + * Example: + * <pre>: + * assumeThat("alwaysPasses", 1, is(1)); // passes + * foo(); // will execute + * assumeThat("alwaysFails", 0, is(1)); // assumption failure! test halts + * int x = 1 / 0; // will never execute + * </pre> + * + * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))} + * @param actual the computed value being compared + * @param matcher an expression, built of {@link Matcher}s, specifying allowed values + * @see org.hamcrest.CoreMatchers + * @see org.junit.matchers.JUnitMatchers + */ + public static <T> void assumeThat(String message, T actual, Matcher<T> matcher) { + if (!matcher.matches(actual)) { + throw new AssumptionViolatedException(message, actual, matcher); + } + } - /** - * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>. - * If not, the test halts and is ignored. - * Example: - * <pre>: - * assumeThat(1, is(1)); // passes - * foo(); // will execute - * assumeThat(0, is(1)); // assumption failure! test halts - * int x = 1 / 0; // will never execute - * </pre> - * - * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))} - * @param actual the computed value being compared - * @param matcher an expression, built of {@link Matcher}s, specifying allowed values - * - * @see org.hamcrest.CoreMatchers - * @see org.junit.matchers.JUnitMatchers - */ - public static <T> void assumeThat(T actual, Matcher<T> matcher) { - if (!matcher.matches(actual)) - throw new AssumptionViolatedException(actual, matcher); - } + /** + * Use to assume that an operation completes normally. If {@code e} is non-null, the test will halt and be ignored. + * + * For example: + * <pre> + * \@Test public void parseDataFile() { + * DataFile file; + * try { + * file = DataFile.open("sampledata.txt"); + * } catch (IOException e) { + * // stop test and ignore if data can't be opened + * assumeNoException(e); + * } + * // ... + * } + * </pre> + * + * @param e if non-null, the offending exception + */ + public static void assumeNoException(Throwable e) { + assumeThat(e, nullValue()); + } /** - * Use to assume that an operation completes normally. If {@code t} is non-null, the test will halt and be ignored. - * - * For example: - * <pre> - * \@Test public void parseDataFile() { - * DataFile file; - * try { - * file = DataFile.open("sampledata.txt"); - * } catch (IOException e) { - * // stop test and ignore if data can't be opened - * assumeNoException(e); - * } - * // ... - * } - * </pre> - * @param t if non-null, the offending exception - */ - public static void assumeNoException(Throwable t) { - assumeThat(t, nullValue()); - } + * Attempts to halt the test and ignore it if Throwable <code>e</code> is + * not <code>null</code>. Similar to {@link #assumeNoException(Throwable)}, + * but provides an additional message that can explain the details + * concerning the assumption. + * + * @param e if non-null, the offending exception + * @param message Additional message to pass to {@link AssumptionViolatedException}. + * @see #assumeNoException(Throwable) + */ + public static void assumeNoException(String message, Throwable e) { + assumeThat(message, e, nullValue()); + } } diff --git a/src/main/java/org/junit/AssumptionViolatedException.java b/src/main/java/org/junit/AssumptionViolatedException.java new file mode 100644 index 0000000..e48ddf0 --- /dev/null +++ b/src/main/java/org/junit/AssumptionViolatedException.java @@ -0,0 +1,46 @@ +package org.junit; + +import org.hamcrest.Matcher; + +/** + * An exception class used to implement <i>assumptions</i> (state in which a given test + * is meaningful and should or should not be executed). A test for which an assumption + * fails should not generate a test case failure. + * + * @see org.junit.Assume + * @since 4.12 + */ +@SuppressWarnings("deprecation") +public class AssumptionViolatedException extends org.junit.internal.AssumptionViolatedException { + private static final long serialVersionUID = 1L; + + /** + * An assumption exception with the given <i>actual</i> value and a <i>matcher</i> describing + * the expectation that failed. + */ + public <T> AssumptionViolatedException(T actual, Matcher<T> matcher) { + super(actual, matcher); + } + + /** + * An assumption exception with a message with the given <i>actual</i> value and a + * <i>matcher</i> describing the expectation that failed. + */ + public <T> AssumptionViolatedException(String message, T expected, Matcher<T> matcher) { + super(message, expected, matcher); + } + + /** + * An assumption exception with the given message only. + */ + public AssumptionViolatedException(String message) { + super(message); + } + + /** + * An assumption exception with the given message and a cause. + */ + public AssumptionViolatedException(String assumption, Throwable t) { + super(assumption, t); + } +} diff --git a/src/main/java/org/junit/Before.java b/src/main/java/org/junit/Before.java index 66b34ee..def8adb 100644 --- a/src/main/java/org/junit/Before.java +++ b/src/main/java/org/junit/Before.java @@ -6,13 +6,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>When writing tests, it is common to find that several tests need similar + * When writing tests, it is common to find that several tests need similar * objects created before they can run. Annotating a <code>public void</code> method * with <code>@Before</code> causes that method to be run before the {@link org.junit.Test} method. - * The <code>@Before</code> methods of superclasses will be run before those of the current class. - * No other ordering is defined. - * </p> - * + * The <code>@Before</code> methods of superclasses will be run before those of the current class, + * unless they are overridden in the current class. No other ordering is defined. + * <p> * Here is a simple example: * <pre> * public class Example { @@ -28,9 +27,10 @@ import java.lang.annotation.Target; * } * } * </pre> - * + * * @see org.junit.BeforeClass * @see org.junit.After + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/src/main/java/org/junit/BeforeClass.java b/src/main/java/org/junit/BeforeClass.java index 35b7854..8183fa0 100644 --- a/src/main/java/org/junit/BeforeClass.java +++ b/src/main/java/org/junit/BeforeClass.java @@ -6,13 +6,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>Sometimes several tests need to share computationally expensive setup - * (like logging into a database). While this can compromise the independence of + * Sometimes several tests need to share computationally expensive setup + * (like logging into a database). While this can compromise the independence of * tests, sometimes it is a necessary optimization. Annotating a <code>public static void</code> no-arg method - * with <code>@BeforeClass</code> causes it to be run once before any of + * with <code>@BeforeClass</code> causes it to be run once before any of * the test methods in the class. The <code>@BeforeClass</code> methods of superclasses - * will be run before those the current class.</p> - * + * will be run before those of the current class, unless they are shadowed in the current class. + * <p> * For example: * <pre> * public class Example { @@ -27,7 +27,9 @@ import java.lang.annotation.Target; * } * } * </pre> + * * @see org.junit.AfterClass + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/src/main/java/org/junit/ClassRule.java b/src/main/java/org/junit/ClassRule.java index 97a111f..02c40a7 100644 --- a/src/main/java/org/junit/ClassRule.java +++ b/src/main/java/org/junit/ClassRule.java @@ -6,55 +6,82 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Annotates static fields that contain rules. Such a field must be public, - * static, and a subtype of {@link org.junit.rules.TestRule}. - * The {@link org.junit.runners.model.Statement} passed - * to the {@link org.junit.rules.TestRule} will run any {@link BeforeClass} methods, + * Annotates static fields that reference rules or methods that return them. A field must be public, + * static, and a subtype of {@link org.junit.rules.TestRule}. A method must be public static, and return + * a subtype of {@link org.junit.rules.TestRule}. + * <p> + * The {@link org.junit.runners.model.Statement} passed + * to the {@link org.junit.rules.TestRule} will run any {@link BeforeClass} methods, * then the entire body of the test class (all contained methods, if it is - * a standard JUnit test class, or all contained classes, if it is a + * a standard JUnit test class, or all contained classes, if it is a * {@link org.junit.runners.Suite}), and finally any {@link AfterClass} methods. - * + * <p> * The statement passed to the {@link org.junit.rules.TestRule} will never throw an exception, * and throwing an exception from the {@link org.junit.rules.TestRule} will result in undefined - * behavior. This means that some {@link org.junit.rules.TestRule}s, such as - * {@link org.junit.rules.ErrorCollector}, - * {@link org.junit.rules.ExpectedException}, + * behavior. This means that some {@link org.junit.rules.TestRule}s, such as + * {@link org.junit.rules.ErrorCollector}, + * {@link org.junit.rules.ExpectedException}, * and {@link org.junit.rules.Timeout}, * have undefined behavior when used as {@link ClassRule}s. - * + * <p> * If there are multiple * annotated {@link ClassRule}s on a class, they will be applied in an order * that depends on your JVM's implementation of the reflection API, which is - * undefined, in general. - * + * undefined, in general. However, Rules defined by fields will always be applied + * before Rules defined by methods. + * <p> * For example, here is a test suite that connects to a server once before * all the test classes run, and disconnects after they are finished: - * * <pre> - * * @RunWith(Suite.class) * @SuiteClasses({A.class, B.class, C.class}) * public class UsesExternalResource { - * public static Server myServer= new Server(); - * - * @ClassRule - * public static ExternalResource resource= new ExternalResource() { - * @Override - * protected void before() throws Throwable { - * myServer.connect(); - * }; - * - * @Override - * protected void after() { - * myServer.disconnect(); - * }; - * }; + * public static Server myServer= new Server(); + * + * @ClassRule + * public static ExternalResource resource= new ExternalResource() { + * @Override + * protected void before() throws Throwable { + * myServer.connect(); + * } + * + * @Override + * protected void after() { + * myServer.disconnect(); + * } + * }; + * } + * </pre> + * <p> + * and the same using a method + * <pre> + * @RunWith(Suite.class) + * @SuiteClasses({A.class, B.class, C.class}) + * public class UsesExternalResource { + * public static Server myServer= new Server(); + * + * @ClassRule + * public static ExternalResource getResource() { + * return new ExternalResource() { + * @Override + * protected void before() throws Throwable { + * myServer.connect(); + * } + * + * @Override + * protected void after() { + * myServer.disconnect(); + * } + * }; + * } * } * </pre> - * - * For more information and more examples, see {@link org.junit.rules.TestRule}. + * <p> + * For more information and more examples, see {@link org.junit.rules.TestRule}. + * + * @since 4.9 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface ClassRule { } diff --git a/src/main/java/org/junit/ComparisonFailure.java b/src/main/java/org/junit/ComparisonFailure.java index d37db4f..9563e61 100644 --- a/src/main/java/org/junit/ComparisonFailure.java +++ b/src/main/java/org/junit/ComparisonFailure.java @@ -1,138 +1,171 @@ package org.junit; /** - * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw - * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex - * strings. - * + * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. + * Create and throw a <code>ComparisonFailure</code> manually if you want to show users the + * difference between two complex strings. + * <p/> * Inspired by a patch from Alex Chaffee (alex@purpletech.com) + * + * @since 4.0 */ -public class ComparisonFailure extends AssertionError { - /** - * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened. - * @see ComparisonCompactor - */ - private static final int MAX_CONTEXT_LENGTH= 20; - private static final long serialVersionUID= 1L; - - private String fExpected; - private String fActual; - - /** - * Constructs a comparison failure. - * @param message the identifying message or null - * @param expected the expected string value - * @param actual the actual string value - */ - public ComparisonFailure (String message, String expected, String actual) { - super (message); - fExpected= expected; - fActual= actual; - } - - /** - * Returns "..." in place of common prefix and "..." in - * place of common suffix between expected and actual. - * - * @see Throwable#getMessage() - */ - @Override - public String getMessage() { - return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); - } - - /** - * Returns the actual string value - * @return the actual string value - */ - public String getActual() { - return fActual; - } - /** - * Returns the expected string value - * @return the expected string value - */ - public String getExpected() { - return fExpected; - } - - private static class ComparisonCompactor { - private static final String ELLIPSIS= "..."; - private static final String DELTA_END= "]"; - private static final String DELTA_START= "["; - - /** - * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code> - * is exceeded, the Strings are shortened - */ - private int fContextLength; - private String fExpected; - private String fActual; - private int fPrefix; - private int fSuffix; - - /** - * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength - * is exceeded, the Strings are shortened - * @param expected the expected string value - * @param actual the actual string value - */ - public ComparisonCompactor(int contextLength, String expected, String actual) { - fContextLength= contextLength; - fExpected= expected; - fActual= actual; - } - - private String compact(String message) { - if (fExpected == null || fActual == null || areStringsEqual()) - return Assert.format(message, fExpected, fActual); - - findCommonPrefix(); - findCommonSuffix(); - String expected= compactString(fExpected); - String actual= compactString(fActual); - return Assert.format(message, expected, actual); - } - - private String compactString(String source) { - String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; - if (fPrefix > 0) - result= computeCommonPrefix() + result; - if (fSuffix > 0) - result= result + computeCommonSuffix(); - return result; - } - - private void findCommonPrefix() { - fPrefix= 0; - int end= Math.min(fExpected.length(), fActual.length()); - for (; fPrefix < end; fPrefix++) { - if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) - break; - } - } - - private void findCommonSuffix() { - int expectedSuffix= fExpected.length() - 1; - int actualSuffix= fActual.length() - 1; - for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { - if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) - break; - } - fSuffix= fExpected.length() - expectedSuffix; - } - - private String computeCommonPrefix() { - return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); - } - - private String computeCommonSuffix() { - int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); - return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); - } - - private boolean areStringsEqual() { - return fExpected.equals(fActual); - } - } -}
\ No newline at end of file +public class ComparisonFailure extends AssertionError { + /** + * The maximum length for expected and actual strings. If it is exceeded, the strings should be shortened. + * + * @see ComparisonCompactor + */ + private static final int MAX_CONTEXT_LENGTH = 20; + private static final long serialVersionUID = 1L; + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private String fExpected; + private String fActual; + + /** + * Constructs a comparison failure. + * + * @param message the identifying message or null + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonFailure(String message, String expected, String actual) { + super(message); + this.fExpected = expected; + this.fActual = actual; + } + + /** + * Returns "..." in place of common prefix and "..." in place of common suffix between expected and actual. + * + * @see Throwable#getMessage() + */ + @Override + public String getMessage() { + return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); + } + + /** + * Returns the actual string value + * + * @return the actual string value + */ + public String getActual() { + return fActual; + } + + /** + * Returns the expected string value + * + * @return the expected string value + */ + public String getExpected() { + return fExpected; + } + + private static class ComparisonCompactor { + private static final String ELLIPSIS = "..."; + private static final String DIFF_END = "]"; + private static final String DIFF_START = "["; + + /** + * The maximum length for <code>expected</code> and <code>actual</code> strings to show. When + * <code>contextLength</code> is exceeded, the Strings are shortened. + */ + private final int contextLength; + private final String expected; + private final String actual; + + /** + * @param contextLength the maximum length of context surrounding the difference between the compared strings. + * When context length is exceeded, the prefixes and suffixes are compacted. + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonCompactor(int contextLength, String expected, String actual) { + this.contextLength = contextLength; + this.expected = expected; + this.actual = actual; + } + + public String compact(String message) { + if (expected == null || actual == null || expected.equals(actual)) { + return Assert.format(message, expected, actual); + } else { + DiffExtractor extractor = new DiffExtractor(); + String compactedPrefix = extractor.compactPrefix(); + String compactedSuffix = extractor.compactSuffix(); + return Assert.format(message, + compactedPrefix + extractor.expectedDiff() + compactedSuffix, + compactedPrefix + extractor.actualDiff() + compactedSuffix); + } + } + + private String sharedPrefix() { + int end = Math.min(expected.length(), actual.length()); + for (int i = 0; i < end; i++) { + if (expected.charAt(i) != actual.charAt(i)) { + return expected.substring(0, i); + } + } + return expected.substring(0, end); + } + + private String sharedSuffix(String prefix) { + int suffixLength = 0; + int maxSuffixLength = Math.min(expected.length() - prefix.length(), + actual.length() - prefix.length()) - 1; + for (; suffixLength <= maxSuffixLength; suffixLength++) { + if (expected.charAt(expected.length() - 1 - suffixLength) + != actual.charAt(actual.length() - 1 - suffixLength)) { + break; + } + } + return expected.substring(expected.length() - suffixLength); + } + + private class DiffExtractor { + private final String sharedPrefix; + private final String sharedSuffix; + + /** + * Can not be instantiated outside {@link org.junit.ComparisonFailure.ComparisonCompactor}. + */ + private DiffExtractor() { + sharedPrefix = sharedPrefix(); + sharedSuffix = sharedSuffix(sharedPrefix); + } + + public String expectedDiff() { + return extractDiff(expected); + } + + public String actualDiff() { + return extractDiff(actual); + } + + public String compactPrefix() { + if (sharedPrefix.length() <= contextLength) { + return sharedPrefix; + } + return ELLIPSIS + sharedPrefix.substring(sharedPrefix.length() - contextLength); + } + + public String compactSuffix() { + if (sharedSuffix.length() <= contextLength) { + return sharedSuffix; + } + return sharedSuffix.substring(0, contextLength) + ELLIPSIS; + } + + private String extractDiff(String source) { + return DIFF_START + source.substring(sharedPrefix.length(), source.length() - sharedSuffix.length()) + + DIFF_END; + } + } + } +} diff --git a/src/main/java/org/junit/FixMethodOrder.java b/src/main/java/org/junit/FixMethodOrder.java new file mode 100644 index 0000000..aaa0313 --- /dev/null +++ b/src/main/java/org/junit/FixMethodOrder.java @@ -0,0 +1,41 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.runners.MethodSorters; + +/** + * This class allows the user to choose the order of execution of the methods within a test class. + * + * <p>The default order of execution of JUnit tests within a class is deterministic but not predictable. + * The order of execution is not guaranteed for Java 7 (and some previous versions), and can even change + * from run to run, so the order of execution was changed to be deterministic (in JUnit 4.11) + * + * <p>It is recommended that test methods be written so that they are independent of the order that they are executed. + * However, there may be a number of dependent tests either through error or by design. + * This class allows the user to specify the order of execution of test methods. + * + * <p>For possibilities, see {@link MethodSorters} + * + * Here is an example: + * + * <pre> + * @FixMethodOrder(MethodSorters.NAME_ASCENDING) + * public class MyTest { + * } + * </pre> + * + * @see org.junit.runners.MethodSorters + * @since 4.11 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface FixMethodOrder { + /** + * Optionally specify <code>value</code> to have the methods executed in a particular order + */ + MethodSorters value() default MethodSorters.DEFAULT; +} diff --git a/src/main/java/org/junit/Ignore.java b/src/main/java/org/junit/Ignore.java index de530a9..db23581 100644 --- a/src/main/java/org/junit/Ignore.java +++ b/src/main/java/org/junit/Ignore.java @@ -6,34 +6,35 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with + * Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with * {@link org.junit.Test} that are also annotated with <code>@Ignore</code> will not be executed as tests. - * Also, you can annotate a class containing test methods with <code>@Ignore</code> and none of the containing - * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the - * number of tests that ran and the number of tests that failed.</p> - * - * For example: + * Also, you can annotate a class containing test methods with <code>@Ignore</code> and none of the containing + * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the + * number of tests that ran and the number of tests that failed. + * + * <p>For example: * <pre> * @Ignore @Test public void something() { ... * </pre> - * @Ignore takes an optional default parameter if you want to record why a test is being ignored:<br/> + * @Ignore takes an optional default parameter if you want to record why a test is being ignored: * <pre> * @Ignore("not ready yet") @Test public void something() { ... * </pre> - * @Ignore can also be applied to the test class:<br/> + * @Ignore can also be applied to the test class: * <pre> - * @Ignore public class IgnoreMe { - * @Test public void test1() { ... } - * @Test public void test2() { ... } - * } + * @Ignore public class IgnoreMe { + * @Test public void test1() { ... } + * @Test public void test2() { ... } + * } * </pre> * + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Ignore { - /** - * The optional reason why the test is ignored. - */ - String value() default ""; + /** + * The optional reason why the test is ignored. + */ + String value() default ""; } diff --git a/src/main/java/org/junit/Rule.java b/src/main/java/org/junit/Rule.java index 9e67c07..711235c 100644 --- a/src/main/java/org/junit/Rule.java +++ b/src/main/java/org/junit/Rule.java @@ -6,42 +6,65 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Annotates fields that contain rules. Such a field must be public, not - * static, and a subtype of {@link org.junit.rules.TestRule}. - * The {@link org.junit.runners.model.Statement} passed - * to the {@link org.junit.rules.TestRule} will run any {@link Before} methods, + * Annotates fields that reference rules or methods that return a rule. A field must be public, not + * static, and a subtype of {@link org.junit.rules.TestRule} (preferred) or + * {@link org.junit.rules.MethodRule}. A method must be public, not static, + * and must return a subtype of {@link org.junit.rules.TestRule} (preferred) or + * {@link org.junit.rules.MethodRule}. + * <p> + * The {@link org.junit.runners.model.Statement} passed + * to the {@link org.junit.rules.TestRule} will run any {@link Before} methods, * then the {@link Test} method, and finally any {@link After} methods, * throwing an exception if any of these fail. If there are multiple - * annotated {@link Rule}s on a class, they will be applied in an order + * annotated {@link Rule}s on a class, they will be applied in order of fields first, then methods. + * However, if there are multiple fields (or methods) they will be applied in an order * that depends on your JVM's implementation of the reflection API, which is - * undefined, in general. - * + * undefined, in general. Rules defined by fields will always be applied + * before Rules defined by methods. You can use a {@link org.junit.rules.RuleChain} if you want + * to have control over the order in which the Rules are applied. + * <p> * For example, here is a test class that creates a temporary folder before * each test method, and deletes it after each: + * <pre> + * public static class HasTempFolder { + * @Rule + * public TemporaryFolder folder= new TemporaryFolder(); * + * @Test + * public void testUsingTempFolder() throws IOException { + * File createdFile= folder.newFile("myfile.txt"); + * File createdFolder= folder.newFolder("subfolder"); + * // ... + * } + * } + * </pre> + * <p> + * And the same using a method. * <pre> * public static class HasTempFolder { - * @Rule - * public TemporaryFolder folder= new TemporaryFolder(); - * - * @Test - * public void testUsingTempFolder() throws IOException { - * File createdFile= folder.newFile("myfile.txt"); - * File createdFolder= folder.newFolder("subfolder"); - * // ... - * } + * private TemporaryFolder folder= new TemporaryFolder(); + * + * @Rule + * public TemporaryFolder getFolder() { + * return folder; + * } + * + * @Test + * public void testUsingTempFolder() throws IOException { + * File createdFile= folder.newFile("myfile.txt"); + * File createdFolder= folder.newFolder("subfolder"); + * // ... + * } * } * </pre> - * - * For more information and more examples, see - * {@link org.junit.rules.TestRule}. + * <p> + * For more information and more examples, see + * {@link org.junit.rules.TestRule}. * - * Note: for backwards compatibility, this annotation may also mark - * fields of type {@link org.junit.rules.MethodRule}, which will be honored. However, - * this is a deprecated interface and feature. + * @since 4.7 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface Rule { }
\ No newline at end of file diff --git a/src/main/java/org/junit/Test.java b/src/main/java/org/junit/Test.java index 23dc78a..71ac428 100644 --- a/src/main/java/org/junit/Test.java +++ b/src/main/java/org/junit/Test.java @@ -6,25 +6,24 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>The <code>Test</code> annotation tells JUnit that the <code>public void</code> method + * The <code>Test</code> annotation tells JUnit that the <code>public void</code> method * to which it is attached can be run as a test case. To run the method, * JUnit first constructs a fresh instance of the class then invokes the * annotated method. Any exceptions thrown by the test will be reported * by JUnit as a failure. If no exceptions are thrown, the test is assumed - * to have succeeded.</p> - * - * <p>A simple test looks like this: + * to have succeeded. + * <p> + * A simple test looks like this: * <pre> * public class Example { - * <b>@Test</b> + * <b>@Test</b> * public void method() { * org.junit.Assert.assertTrue( new ArrayList().isEmpty() ); * } * } * </pre> - * </p> - * - * <p>The <code>Test</code> annotation supports two optional parameters. + * <p> + * The <code>Test</code> annotation supports two optional parameters. * The first, <code>expected</code>, declares that a test method should throw * an exception. If it doesn't throw an exception or if it throws a different exception * than the one declared, the test fails. For example, the following test succeeds: @@ -32,37 +31,68 @@ import java.lang.annotation.Target; * @Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() { * new ArrayList<Object>().get(1); * } - * </pre></p> - * - * <p>The second optional parameter, <code>timeout</code>, causes a test to fail if it takes + * </pre> + * If the exception's message or one of its properties should be verified, the + * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further + * information about exception testing can be found at the + * <a href="https://github.com/junit-team/junit/wiki/Exception-testing">JUnit Wiki</a>. + * <p> + * The second optional parameter, <code>timeout</code>, causes a test to fail if it takes * longer than a specified amount of clock time (measured in milliseconds). The following test fails: * <pre> * @Test(<b>timeout=100</b>) public void infinity() { * while(true); * } - * </pre></p> + * </pre> + * <b>Warning</b>: while <code>timeout</code> is useful to catch and terminate + * infinite loops, it should <em>not</em> be considered deterministic. The + * following test may or may not fail depending on how the operating system + * schedules threads: + * <pre> + * @Test(<b>timeout=100</b>) public void sleep100() { + * Thread.sleep(100); + * } + * </pre> + * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the + * thread which runs the fixture's @Before and @After methods. This may yield different behavior for + * code that is not thread safe when compared to the same test method without a timeout parameter. + * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the + * same thread as the fixture's @Before and @After methods. + * + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Test { - - /** - * Default empty exception - */ - static class None extends Throwable { - private static final long serialVersionUID= 1L; - private None() { - } - } - - /** - * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed iff - * an exception of the specified class is thrown by the method. - */ - Class<? extends Throwable> expected() default None.class; - - /** - * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it - * takes longer than that number of milliseconds.*/ - long timeout() default 0L; + + /** + * Default empty exception + */ + static class None extends Throwable { + private static final long serialVersionUID = 1L; + + private None() { + } + } + + /** + * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed if + * and only if an exception of the specified class is thrown by the method. If the Throwable's + * message or one of its properties should be verified, the + * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used instead. + */ + Class<? extends Throwable> expected() default None.class; + + /** + * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it + * takes longer than that number of milliseconds. + * <p> + * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the + * thread which runs the fixture's @Before and @After methods. This may yield different behavior for + * code that is not thread safe when compared to the same test method without a timeout parameter. + * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the + * same thread as the fixture's @Before and @After methods. + * </p> + */ + long timeout() default 0L; } diff --git a/src/main/java/org/junit/experimental/ParallelComputer.java b/src/main/java/org/junit/experimental/ParallelComputer.java index fccb97c..97da0f7 100644 --- a/src/main/java/org/junit/experimental/ParallelComputer.java +++ b/src/main/java/org/junit/experimental/ParallelComputer.java @@ -1,11 +1,8 @@ package org.junit.experimental; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.junit.runner.Computer; import org.junit.runner.Runner; @@ -15,64 +12,56 @@ import org.junit.runners.model.RunnerBuilder; import org.junit.runners.model.RunnerScheduler; public class ParallelComputer extends Computer { - private final boolean fClasses; + private final boolean classes; - private final boolean fMethods; + private final boolean methods; - public ParallelComputer(boolean classes, boolean methods) { - fClasses= classes; - fMethods= methods; - } + public ParallelComputer(boolean classes, boolean methods) { + this.classes = classes; + this.methods = methods; + } - public static Computer classes() { - return new ParallelComputer(true, false); - } + public static Computer classes() { + return new ParallelComputer(true, false); + } - public static Computer methods() { - return new ParallelComputer(false, true); - } + public static Computer methods() { + return new ParallelComputer(false, true); + } - private static <T> Runner parallelize(Runner runner) { - if (runner instanceof ParentRunner<?>) { - ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() { - private final List<Future<Object>> fResults= new ArrayList<Future<Object>>(); + private static Runner parallelize(Runner runner) { + if (runner instanceof ParentRunner) { + ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() { + private final ExecutorService fService = Executors.newCachedThreadPool(); - private final ExecutorService fService= Executors - .newCachedThreadPool(); + public void schedule(Runnable childStatement) { + fService.submit(childStatement); + } - public void schedule(final Runnable childStatement) { - fResults.add(fService.submit(new Callable<Object>() { - public Object call() throws Exception { - childStatement.run(); - return null; - } - })); - } + public void finished() { + try { + fService.shutdown(); + fService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + } + }); + } + return runner; + } - public void finished() { - for (Future<Object> each : fResults) - try { - each.get(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - return runner; - } + @Override + public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes) + throws InitializationError { + Runner suite = super.getSuite(builder, classes); + return this.classes ? parallelize(suite) : suite; + } - @Override - public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes) - throws InitializationError { - Runner suite= super.getSuite(builder, classes); - return fClasses ? parallelize(suite) : suite; - } - - @Override - protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) - throws Throwable { - Runner runner= super.getRunner(builder, testClass); - return fMethods ? parallelize(runner) : runner; - } + @Override + protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) + throws Throwable { + Runner runner = super.getRunner(builder, testClass); + return methods ? parallelize(runner) : runner; + } } diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java index d57b4d3..290c180 100644 --- a/src/main/java/org/junit/experimental/categories/Categories.java +++ b/src/main/java/org/junit/experimental/categories/Categories.java @@ -1,13 +1,10 @@ -/** - * - */ package org.junit.experimental.categories; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; @@ -20,173 +17,369 @@ import org.junit.runners.model.RunnerBuilder; * From a given set of test classes, runs only the classes and methods that are * annotated with either the category given with the @IncludeCategory * annotation, or a subtype of that category. - * + * <p> * Note that, for now, annotating suites with {@code @Category} has no effect. * Categories must be annotated on the direct method or class. - * + * <p> * Example: - * * <pre> * public interface FastTests { * } - * + * * public interface SlowTests { * } - * + * + * public interface SmokeTests + * } + * * public static class A { - * @Test - * public void a() { - * fail(); - * } - * - * @Category(SlowTests.class) - * @Test - * public void b() { - * } + * @Test + * public void a() { + * fail(); + * } + * + * @Category(SlowTests.class) + * @Test + * public void b() { + * } + * + * @Category({FastTests.class, SmokeTests.class}) + * @Test + * public void c() { + * } * } - * - * @Category( { SlowTests.class, FastTests.class }) + * + * @Category({SlowTests.class, FastTests.class}) * public static class B { - * @Test - * public void c() { - * - * } + * @Test + * public void d() { + * } * } - * + * * @RunWith(Categories.class) * @IncludeCategory(SlowTests.class) - * @SuiteClasses( { A.class, B.class }) + * @SuiteClasses({A.class, B.class}) * // Note that Categories is a kind of Suite * public static class SlowTestSuite { + * // Will run A.b and B.d, but not A.a and A.c + * } + * </pre> + * <p> + * Example to run multiple categories: + * <pre> + * @RunWith(Categories.class) + * @IncludeCategory({FastTests.class, SmokeTests.class}) + * @SuiteClasses({A.class, B.class}) + * public static class FastOrSmokeTestSuite { + * // Will run A.c and B.d, but not A.b because it is not any of FastTests or SmokeTests * } * </pre> + * + * @version 4.12 + * @see <a href="https://github.com/junit-team/junit/wiki/Categories">Categories at JUnit wiki</a> */ public class Categories extends Suite { - // the way filters are implemented makes this unnecessarily complicated, - // buggy, and difficult to specify. A new way of handling filters could - // someday enable a better new implementation. - // https://github.com/KentBeck/junit/issues/issue/172 - - @Retention(RetentionPolicy.RUNTIME) - public @interface IncludeCategory { - public Class<?> value(); - } - - @Retention(RetentionPolicy.RUNTIME) - public @interface ExcludeCategory { - public Class<?> value(); - } - - public static class CategoryFilter extends Filter { - public static CategoryFilter include(Class<?> categoryType) { - return new CategoryFilter(categoryType, null); - } - - private final Class<?> fIncluded; - - private final Class<?> fExcluded; - - public CategoryFilter(Class<?> includedCategory, - Class<?> excludedCategory) { - fIncluded= includedCategory; - fExcluded= excludedCategory; - } - - @Override - public String describe() { - return "category " + fIncluded; - } - - @Override - public boolean shouldRun(Description description) { - if (hasCorrectCategoryAnnotation(description)) - return true; - for (Description each : description.getChildren()) - if (shouldRun(each)) - return true; - return false; - } - - private boolean hasCorrectCategoryAnnotation(Description description) { - List<Class<?>> categories= categories(description); - if (categories.isEmpty()) - return fIncluded == null; - for (Class<?> each : categories) - if (fExcluded != null && fExcluded.isAssignableFrom(each)) - return false; - for (Class<?> each : categories) - if (fIncluded == null || fIncluded.isAssignableFrom(each)) - return true; - return false; - } - - private List<Class<?>> categories(Description description) { - ArrayList<Class<?>> categories= new ArrayList<Class<?>>(); - categories.addAll(Arrays.asList(directCategories(description))); - categories.addAll(Arrays.asList(directCategories(parentDescription(description)))); - return categories; - } - - private Description parentDescription(Description description) { - Class<?> testClass= description.getTestClass(); - if (testClass == null) - return null; - return Description.createSuiteDescription(testClass); - } - - private Class<?>[] directCategories(Description description) { - if (description == null) - return new Class<?>[0]; - Category annotation= description.getAnnotation(Category.class); - if (annotation == null) - return new Class<?>[0]; - return annotation.value(); - } - } - - public Categories(Class<?> klass, RunnerBuilder builder) - throws InitializationError { - super(klass, builder); - try { - filter(new CategoryFilter(getIncludedCategory(klass), - getExcludedCategory(klass))); - } catch (NoTestsRemainException e) { - throw new InitializationError(e); - } - assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); - } - - private Class<?> getIncludedCategory(Class<?> klass) { - IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class); - return annotation == null ? null : annotation.value(); - } - - private Class<?> getExcludedCategory(Class<?> klass) { - ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class); - return annotation == null ? null : annotation.value(); - } - - private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError { - if (!canHaveCategorizedChildren(description)) - assertNoDescendantsHaveCategoryAnnotations(description); - for (Description each : description.getChildren()) - assertNoCategorizedDescendentsOfUncategorizeableParents(each); - } - - private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError { - for (Description each : description.getChildren()) { - if (each.getAnnotation(Category.class) != null) - throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods."); - assertNoDescendantsHaveCategoryAnnotations(each); - } - } - - // If children have names like [0], our current magical category code can't determine their - // parentage. - private static boolean canHaveCategorizedChildren(Description description) { - for (Description each : description.getChildren()) - if (each.getTestClass() == null) - return false; - return true; - } -}
\ No newline at end of file + + @Retention(RetentionPolicy.RUNTIME) + public @interface IncludeCategory { + /** + * Determines the tests to run that are annotated with categories specified in + * the value of this annotation or their subtypes unless excluded with {@link ExcludeCategory}. + */ + public Class<?>[] value() default {}; + + /** + * If <tt>true</tt>, runs tests annotated with <em>any</em> of the categories in + * {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with <em>all</em> of the categories. + */ + public boolean matchAny() default true; + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface ExcludeCategory { + /** + * Determines the tests which do not run if they are annotated with categories specified in the + * value of this annotation or their subtypes regardless of being included in {@link IncludeCategory#value()}. + */ + public Class<?>[] value() default {}; + + /** + * If <tt>true</tt>, the tests annotated with <em>any</em> of the categories in {@link ExcludeCategory#value()} + * do not run. Otherwise, the tests do not run if and only if annotated with <em>all</em> categories. + */ + public boolean matchAny() default true; + } + + public static class CategoryFilter extends Filter { + private final Set<Class<?>> included; + private final Set<Class<?>> excluded; + private final boolean includedAny; + private final boolean excludedAny; + + public static CategoryFilter include(boolean matchAny, Class<?>... categories) { + if (hasNull(categories)) { + throw new NullPointerException("has null category"); + } + return categoryFilter(matchAny, createSet(categories), true, null); + } + + public static CategoryFilter include(Class<?> category) { + return include(true, category); + } + + public static CategoryFilter include(Class<?>... categories) { + return include(true, categories); + } + + public static CategoryFilter exclude(boolean matchAny, Class<?>... categories) { + if (hasNull(categories)) { + throw new NullPointerException("has null category"); + } + return categoryFilter(true, null, matchAny, createSet(categories)); + } + + public static CategoryFilter exclude(Class<?> category) { + return exclude(true, category); + } + + public static CategoryFilter exclude(Class<?>... categories) { + return exclude(true, categories); + } + + public static CategoryFilter categoryFilter(boolean matchAnyInclusions, Set<Class<?>> inclusions, + boolean matchAnyExclusions, Set<Class<?>> exclusions) { + return new CategoryFilter(matchAnyInclusions, inclusions, matchAnyExclusions, exclusions); + } + + protected CategoryFilter(boolean matchAnyIncludes, Set<Class<?>> includes, + boolean matchAnyExcludes, Set<Class<?>> excludes) { + includedAny = matchAnyIncludes; + excludedAny = matchAnyExcludes; + included = copyAndRefine(includes); + excluded = copyAndRefine(excludes); + } + + /** + * @see #toString() + */ + @Override + public String describe() { + return toString(); + } + + /** + * Returns string in the form <tt>"[included categories] - [excluded categories]"</tt>, where both + * sets have comma separated names of categories. + * + * @return string representation for the relative complement of excluded categories set + * in the set of included categories. Examples: + * <ul> + * <li> <tt>"categories [all]"</tt> for all included categories and no excluded ones; + * <li> <tt>"categories [all] - [A, B]"</tt> for all included categories and given excluded ones; + * <li> <tt>"categories [A, B] - [C, D]"</tt> for given included categories and given excluded ones. + * </ul> + * @see Class#toString() name of category + */ + @Override public String toString() { + StringBuilder description= new StringBuilder("categories ") + .append(included.isEmpty() ? "[all]" : included); + if (!excluded.isEmpty()) { + description.append(" - ").append(excluded); + } + return description.toString(); + } + + @Override + public boolean shouldRun(Description description) { + if (hasCorrectCategoryAnnotation(description)) { + return true; + } + + for (Description each : description.getChildren()) { + if (shouldRun(each)) { + return true; + } + } + + return false; + } + + private boolean hasCorrectCategoryAnnotation(Description description) { + final Set<Class<?>> childCategories= categories(description); + + // If a child has no categories, immediately return. + if (childCategories.isEmpty()) { + return included.isEmpty(); + } + + if (!excluded.isEmpty()) { + if (excludedAny) { + if (matchesAnyParentCategories(childCategories, excluded)) { + return false; + } + } else { + if (matchesAllParentCategories(childCategories, excluded)) { + return false; + } + } + } + + if (included.isEmpty()) { + // Couldn't be excluded, and with no suite's included categories treated as should run. + return true; + } else { + if (includedAny) { + return matchesAnyParentCategories(childCategories, included); + } else { + return matchesAllParentCategories(childCategories, included); + } + } + } + + /** + * @return <tt>true</tt> if at least one (any) parent category match a child, otherwise <tt>false</tt>. + * If empty <tt>parentCategories</tt>, returns <tt>false</tt>. + */ + private boolean matchesAnyParentCategories(Set<Class<?>> childCategories, Set<Class<?>> parentCategories) { + for (Class<?> parentCategory : parentCategories) { + if (hasAssignableTo(childCategories, parentCategory)) { + return true; + } + } + return false; + } + + /** + * @return <tt>false</tt> if at least one parent category does not match children, otherwise <tt>true</tt>. + * If empty <tt>parentCategories</tt>, returns <tt>true</tt>. + */ + private boolean matchesAllParentCategories(Set<Class<?>> childCategories, Set<Class<?>> parentCategories) { + for (Class<?> parentCategory : parentCategories) { + if (!hasAssignableTo(childCategories, parentCategory)) { + return false; + } + } + return true; + } + + private static Set<Class<?>> categories(Description description) { + Set<Class<?>> categories= new HashSet<Class<?>>(); + Collections.addAll(categories, directCategories(description)); + Collections.addAll(categories, directCategories(parentDescription(description))); + return categories; + } + + private static Description parentDescription(Description description) { + Class<?> testClass= description.getTestClass(); + return testClass == null ? null : Description.createSuiteDescription(testClass); + } + + private static Class<?>[] directCategories(Description description) { + if (description == null) { + return new Class<?>[0]; + } + + Category annotation= description.getAnnotation(Category.class); + return annotation == null ? new Class<?>[0] : annotation.value(); + } + + private static Set<Class<?>> copyAndRefine(Set<Class<?>> classes) { + HashSet<Class<?>> c= new HashSet<Class<?>>(); + if (classes != null) { + c.addAll(classes); + } + c.remove(null); + return c; + } + + private static boolean hasNull(Class<?>... classes) { + if (classes == null) return false; + for (Class<?> clazz : classes) { + if (clazz == null) { + return true; + } + } + return false; + } + } + + public Categories(Class<?> klass, RunnerBuilder builder) throws InitializationError { + super(klass, builder); + try { + Set<Class<?>> included= getIncludedCategory(klass); + Set<Class<?>> excluded= getExcludedCategory(klass); + boolean isAnyIncluded= isAnyIncluded(klass); + boolean isAnyExcluded= isAnyExcluded(klass); + + filter(CategoryFilter.categoryFilter(isAnyIncluded, included, isAnyExcluded, excluded)); + } catch (NoTestsRemainException e) { + throw new InitializationError(e); + } + assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); + } + + private static Set<Class<?>> getIncludedCategory(Class<?> klass) { + IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class); + return createSet(annotation == null ? null : annotation.value()); + } + + private static boolean isAnyIncluded(Class<?> klass) { + IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class); + return annotation == null || annotation.matchAny(); + } + + private static Set<Class<?>> getExcludedCategory(Class<?> klass) { + ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class); + return createSet(annotation == null ? null : annotation.value()); + } + + private static boolean isAnyExcluded(Class<?> klass) { + ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class); + return annotation == null || annotation.matchAny(); + } + + private static void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError { + if (!canHaveCategorizedChildren(description)) { + assertNoDescendantsHaveCategoryAnnotations(description); + } + for (Description each : description.getChildren()) { + assertNoCategorizedDescendentsOfUncategorizeableParents(each); + } + } + + private static void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError { + for (Description each : description.getChildren()) { + if (each.getAnnotation(Category.class) != null) { + throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods."); + } + assertNoDescendantsHaveCategoryAnnotations(each); + } + } + + // If children have names like [0], our current magical category code can't determine their parentage. + private static boolean canHaveCategorizedChildren(Description description) { + for (Description each : description.getChildren()) { + if (each.getTestClass() == null) { + return false; + } + } + return true; + } + + private static boolean hasAssignableTo(Set<Class<?>> assigns, Class<?> to) { + for (final Class<?> from : assigns) { + if (to.isAssignableFrom(from)) { + return true; + } + } + return false; + } + + private static Set<Class<?>> createSet(Class<?>... t) { + final Set<Class<?>> set= new HashSet<Class<?>>(); + if (t != null) { + Collections.addAll(set, t); + } + return set; + } +} diff --git a/src/main/java/org/junit/experimental/categories/Category.java b/src/main/java/org/junit/experimental/categories/Category.java index 3a4c0b9..8eae836 100644 --- a/src/main/java/org/junit/experimental/categories/Category.java +++ b/src/main/java/org/junit/experimental/categories/Category.java @@ -1,43 +1,48 @@ package org.junit.experimental.categories; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import org.junit.validator.ValidateWith; + /** * Marks a test class or test method as belonging to one or more categories of tests. * The value is an array of arbitrary classes. - * + * * This annotation is only interpreted by the Categories runner (at present). - * + * * For example: -<pre> - public interface FastTests {} - public interface SlowTests {} - - public static class A { - @Test - public void a() { - fail(); - } - - @Category(SlowTests.class) - @Test - public void b() { - } - } - - @Category({SlowTests.class, FastTests.class}) - public static class B { - @Test - public void c() { - - } - } -</pre> - * + * <pre> + * public interface FastTests {} + * public interface SlowTests {} + * + * public static class A { + * @Test + * public void a() { + * fail(); + * } + * + * @Category(SlowTests.class) + * @Test + * public void b() { + * } + * } + * + * @Category({SlowTests.class, FastTests.class}) + * public static class B { + * @Test + * public void c() { + * + * } + * } + * </pre> + * * For more usage, see code example on {@link Categories}. */ @Retention(RetentionPolicy.RUNTIME) +@Inherited +@ValidateWith(CategoryValidator.class) public @interface Category { - Class<?>[] value(); + Class<?>[] value(); }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java new file mode 100644 index 0000000..cee1ae7 --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java @@ -0,0 +1,47 @@ +package org.junit.experimental.categories;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory;
+import org.junit.runner.FilterFactoryParams;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Implementation of FilterFactory for Category filtering.
+ */
+abstract class CategoryFilterFactory implements FilterFactory {
+ /**
+ * Creates a {@link org.junit.experimental.categories.Categories.CategoryFilter} given a
+ * {@link FilterFactoryParams} argument.
+ *
+ * @param params Parameters needed to create the {@link Filter}
+ */
+ public Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException {
+ try {
+ return createFilter(parseCategories(params.getArgs()));
+ } catch (ClassNotFoundException e) {
+ throw new FilterNotCreatedException(e);
+ }
+ }
+
+ /**
+ * Creates a {@link org.junit.experimental.categories.Categories.CategoryFilter} given an array of classes.
+ *
+ * @param categories Category classes.
+ */
+ protected abstract Filter createFilter(List<Class<?>> categories);
+
+ private List<Class<?>> parseCategories(String categories) throws ClassNotFoundException {
+ List<Class<?>> categoryClasses = new ArrayList<Class<?>>();
+
+ for (String category : categories.split(",")) {
+ Class<?> categoryClass = Classes.getClass(category);
+
+ categoryClasses.add(categoryClass);
+ }
+
+ return categoryClasses;
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/CategoryValidator.java b/src/main/java/org/junit/experimental/categories/CategoryValidator.java new file mode 100644 index 0000000..491d8ac --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/CategoryValidator.java @@ -0,0 +1,62 @@ +package org.junit.experimental.categories; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runners.model.FrameworkMethod; +import org.junit.validator.AnnotationValidator; + +/** + * Validates that there are no errors in the use of the {@code Category} + * annotation. If there is, a {@code Throwable} object will be added to the list + * of errors. + * + * @since 4.12 + */ +public final class CategoryValidator extends AnnotationValidator { + + @SuppressWarnings("unchecked") + private static final Set<Class<? extends Annotation>> INCOMPATIBLE_ANNOTATIONS = unmodifiableSet(new HashSet<Class<? extends Annotation>>( + asList(BeforeClass.class, AfterClass.class, Before.class, After.class))); + + /** + * Adds to {@code errors} a throwable for each problem detected. Looks for + * {@code BeforeClass}, {@code AfterClass}, {@code Before} and {@code After} + * annotations. + * + * @param method the method that is being validated + * @return A list of exceptions detected + * + * @since 4.12 + */ + @Override + public List<Exception> validateAnnotatedMethod(FrameworkMethod method) { + List<Exception> errors = new ArrayList<Exception>(); + Annotation[] annotations = method.getAnnotations(); + for (Annotation annotation : annotations) { + for (Class<?> clazz : INCOMPATIBLE_ANNOTATIONS) { + if (annotation.annotationType().isAssignableFrom(clazz)) { + addErrorMessage(errors, clazz); + } + } + } + return unmodifiableList(errors); + } + + private void addErrorMessage(List<Exception> errors, Class<?> clazz) { + String message = String.format("@%s can not be combined with @Category", + clazz.getSimpleName()); + errors.add(new Exception(message)); + } +} diff --git a/src/main/java/org/junit/experimental/categories/ExcludeCategories.java b/src/main/java/org/junit/experimental/categories/ExcludeCategories.java new file mode 100644 index 0000000..8ccb6b5 --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/ExcludeCategories.java @@ -0,0 +1,52 @@ +package org.junit.experimental.categories;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.experimental.categories.Categories.CategoryFilter;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * {@link org.junit.runner.FilterFactory} to exclude categories.
+ *
+ * The {@link Filter} that is created will filter out tests that are categorized with any of the
+ * given categories.
+ *
+ * Usage from command line:
+ * <code>
+ * --filter=org.junit.experimental.categories.ExcludeCategories=pkg.of.Cat1,pkg.of.Cat2
+ * </code>
+ *
+ * Usage from API:
+ * <code>
+ * new ExcludeCategories().createFilter(Cat1.class, Cat2.class);
+ * </code>
+ */
+public final class ExcludeCategories extends CategoryFilterFactory {
+ /**
+ * Creates a {@link Filter} which is only passed by tests that are
+ * not categorized with any of the specified categories.
+ *
+ * @param categories Category classes.
+ */
+ @Override
+ protected Filter createFilter(List<Class<?>> categories) {
+ return new ExcludesAny(categories);
+ }
+
+ private static class ExcludesAny extends CategoryFilter {
+ public ExcludesAny(List<Class<?>> categories) {
+ this(new HashSet<Class<?>>(categories));
+ }
+
+ public ExcludesAny(Set<Class<?>> categories) {
+ super(true, null, true, categories);
+ }
+
+ @Override
+ public String describe() {
+ return "excludes " + super.describe();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/IncludeCategories.java b/src/main/java/org/junit/experimental/categories/IncludeCategories.java new file mode 100644 index 0000000..38eb693 --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/IncludeCategories.java @@ -0,0 +1,52 @@ +package org.junit.experimental.categories;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.experimental.categories.Categories.CategoryFilter;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * {@link org.junit.runner.FilterFactory} to include categories.
+ *
+ * The {@link Filter} that is created will filter out tests that are categorized with any of the
+ * given categories.
+ *
+ * Usage from command line:
+ * <code>
+ * --filter=org.junit.experimental.categories.IncludeCategories=pkg.of.Cat1,pkg.of.Cat2
+ * </code>
+ *
+ * Usage from API:
+ * <code>
+ * new IncludeCategories().createFilter(Cat1.class, Cat2.class);
+ * </code>
+ */
+public final class IncludeCategories extends CategoryFilterFactory {
+ /**
+ * Creates a {@link Filter} which is only passed by tests that are
+ * categorized with any of the specified categories.
+ *
+ * @param categories Category classes.
+ */
+ @Override
+ protected Filter createFilter(List<Class<?>> categories) {
+ return new IncludesAny(categories);
+ }
+
+ private static class IncludesAny extends CategoryFilter {
+ public IncludesAny(List<Class<?>> categories) {
+ this(new HashSet<Class<?>>(categories));
+ }
+
+ public IncludesAny(Set<Class<?>> categories) {
+ super(true, categories, true, null);
+ }
+
+ @Override
+ public String describe() {
+ return "includes " + super.describe();
+ }
+ }
+}
diff --git a/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java index 03c3c8c..116d755 100644 --- a/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java +++ b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java @@ -4,12 +4,12 @@ package org.junit.experimental.max; * Thrown when Max cannot read the MaxCore serialization */ public class CouldNotReadCoreException extends Exception { - private static final long serialVersionUID= 1L; + private static final long serialVersionUID = 1L; - /** - * Constructs - */ - public CouldNotReadCoreException(Throwable e) { - super(e); - } + /** + * Constructs + */ + public CouldNotReadCoreException(Throwable e) { + super(e); + } } diff --git a/src/main/java/org/junit/experimental/max/MaxCore.java b/src/main/java/org/junit/experimental/max/MaxCore.java index a2a34a9..625cade 100644 --- a/src/main/java/org/junit/experimental/max/MaxCore.java +++ b/src/main/java/org/junit/experimental/max/MaxCore.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.List; import junit.framework.TestSuite; - import org.junit.internal.requests.SortingRequest; import org.junit.internal.runners.ErrorReportingRunner; import org.junit.internal.runners.JUnit38ClassRunner; @@ -21,150 +20,162 @@ import org.junit.runners.model.InitializationError; /** * A replacement for JUnitCore, which keeps track of runtime and failure history, and reorders tests * to maximize the chances that a failing test occurs early in the test run. - * + * * The rules for sorting are: * <ol> * <li> Never-run tests first, in arbitrary order * <li> Group remaining tests by the date at which they most recently failed. * <li> Sort groups such that the most recent failure date is first, and never-failing tests are at the end. - * <li> Within a group, run the fastest tests first. + * <li> Within a group, run the fastest tests first. * </ol> */ public class MaxCore { - private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX= "malformed JUnit 3 test class: "; - - /** - * Create a new MaxCore from a serialized file stored at storedResults - * @deprecated use storedLocally() - */ - @Deprecated - public static MaxCore forFolder(String folderName) { - return storedLocally(new File(folderName)); - } - - /** - * Create a new MaxCore from a serialized file stored at storedResults - */ - public static MaxCore storedLocally(File storedResults) { - return new MaxCore(storedResults); - } - - private final MaxHistory fHistory; - - private MaxCore(File storedResults) { - fHistory = MaxHistory.forFolder(storedResults); - } - - /** - * Run all the tests in <code>class</code>. - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Class<?> testClass) { - return run(Request.aClass(testClass)); - } - - /** - * Run all the tests contained in <code>request</code>. - * @param request the request describing tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Request request) { - return run(request, new JUnitCore()); - } - - /** - * Run all the tests contained in <code>request</code>. - * - * This variant should be used if {@code core} has attached listeners that this - * run should notify. - * - * @param request the request describing tests - * @param core a JUnitCore to delegate to. - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Request request, JUnitCore core) { - core.addListener(fHistory.listener()); - return core.run(sortRequest(request).getRunner()); - } - - /** - * @param request - * @return a new Request, which contains all of the same tests, but in a new order. - */ - public Request sortRequest(Request request) { - if (request instanceof SortingRequest) // We'll pay big karma points for this - return request; - List<Description> leaves= findLeaves(request); - Collections.sort(leaves, fHistory.testComparator()); - return constructLeafRequest(leaves); - } - - private Request constructLeafRequest(List<Description> leaves) { - final List<Runner> runners = new ArrayList<Runner>(); - for (Description each : leaves) - runners.add(buildRunner(each)); - return new Request() { - @Override - public Runner getRunner() { - try { - return new Suite((Class<?>)null, runners) {}; - } catch (InitializationError e) { - return new ErrorReportingRunner(null, e); - } - } - }; - } - - private Runner buildRunner(Description each) { - if (each.toString().equals("TestSuite with 0 tests")) - return Suite.emptySuite(); - if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX)) - // This is cheating, because it runs the whole class - // to get the warning for this method, but we can't do better, - // because JUnit 3.8's - // thrown away which method the warning is for. - return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each))); - Class<?> type= each.getTestClass(); - if (type == null) - throw new RuntimeException("Can't build a runner from description [" + each + "]"); - String methodName= each.getMethodName(); - if (methodName == null) - return Request.aClass(type).getRunner(); - return Request.method(type, methodName).getRunner(); - } - - private Class<?> getMalformedTestClass(Description each) { - try { - return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, "")); - } catch (ClassNotFoundException e) { - return null; - } - } - - /** - * @param request a request to run - * @return a list of method-level tests to run, sorted in the order - * specified in the class comment. - */ - public List<Description> sortedLeavesForTest(Request request) { - return findLeaves(sortRequest(request)); - } - - private List<Description> findLeaves(Request request) { - List<Description> results= new ArrayList<Description>(); - findLeaves(null, request.getRunner().getDescription(), results); - return results; - } - - private void findLeaves(Description parent, Description description, List<Description> results) { - if (description.getChildren().isEmpty()) - if (description.toString().equals("warning(junit.framework.TestSuite$1)")) - results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent)); - else - results.add(description); - else - for (Description each : description.getChildren()) - findLeaves(description, each, results); - } -} + private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX = "malformed JUnit 3 test class: "; + + /** + * Create a new MaxCore from a serialized file stored at storedResults + * + * @deprecated use storedLocally() + */ + @Deprecated + public static MaxCore forFolder(String folderName) { + return storedLocally(new File(folderName)); + } + + /** + * Create a new MaxCore from a serialized file stored at storedResults + */ + public static MaxCore storedLocally(File storedResults) { + return new MaxCore(storedResults); + } + + private final MaxHistory history; + + private MaxCore(File storedResults) { + history = MaxHistory.forFolder(storedResults); + } + + /** + * Run all the tests in <code>class</code>. + * + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Class<?> testClass) { + return run(Request.aClass(testClass)); + } + + /** + * Run all the tests contained in <code>request</code>. + * + * @param request the request describing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Request request) { + return run(request, new JUnitCore()); + } + + /** + * Run all the tests contained in <code>request</code>. + * + * This variant should be used if {@code core} has attached listeners that this + * run should notify. + * + * @param request the request describing tests + * @param core a JUnitCore to delegate to. + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Request request, JUnitCore core) { + core.addListener(history.listener()); + return core.run(sortRequest(request).getRunner()); + } + + /** + * @return a new Request, which contains all of the same tests, but in a new order. + */ + public Request sortRequest(Request request) { + if (request instanceof SortingRequest) { + // We'll pay big karma points for this + return request; + } + List<Description> leaves = findLeaves(request); + Collections.sort(leaves, history.testComparator()); + return constructLeafRequest(leaves); + } + + private Request constructLeafRequest(List<Description> leaves) { + final List<Runner> runners = new ArrayList<Runner>(); + for (Description each : leaves) { + runners.add(buildRunner(each)); + } + return new Request() { + @Override + public Runner getRunner() { + try { + return new Suite((Class<?>) null, runners) { + }; + } catch (InitializationError e) { + return new ErrorReportingRunner(null, e); + } + } + }; + } + + private Runner buildRunner(Description each) { + if (each.toString().equals("TestSuite with 0 tests")) { + return Suite.emptySuite(); + } + if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX)) { + // This is cheating, because it runs the whole class + // to get the warning for this method, but we can't do better, + // because JUnit 3.8's + // thrown away which method the warning is for. + return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each))); + } + Class<?> type = each.getTestClass(); + if (type == null) { + throw new RuntimeException("Can't build a runner from description [" + each + "]"); + } + String methodName = each.getMethodName(); + if (methodName == null) { + return Request.aClass(type).getRunner(); + } + return Request.method(type, methodName).getRunner(); + } + + private Class<?> getMalformedTestClass(Description each) { + try { + return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, "")); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * @param request a request to run + * @return a list of method-level tests to run, sorted in the order + * specified in the class comment. + */ + public List<Description> sortedLeavesForTest(Request request) { + return findLeaves(sortRequest(request)); + } + + private List<Description> findLeaves(Request request) { + List<Description> results = new ArrayList<Description>(); + findLeaves(null, request.getRunner().getDescription(), results); + return results; + } + private void findLeaves(Description parent, Description description, List<Description> results) { + if (description.getChildren().isEmpty()) { + if (description.toString().equals("warning(junit.framework.TestSuite$1)")) { + results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent)); + } else { + results.add(description); + } + } else { + for (Description each : description.getChildren()) { + findLeaves(description, each, results); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/max/MaxHistory.java b/src/main/java/org/junit/experimental/max/MaxHistory.java index e091793..45a4033 100644 --- a/src/main/java/org/junit/experimental/max/MaxHistory.java +++ b/src/main/java/org/junit/experimental/max/MaxHistory.java @@ -24,143 +24,150 @@ import org.junit.runner.notification.RunListener; * </ul> */ public class MaxHistory implements Serializable { - private static final long serialVersionUID= 1L; - - /** - * Loads a {@link MaxHistory} from {@code file}, or generates a new one that - * will be saved to {@code file}. - */ - public static MaxHistory forFolder(File file) { - if (file.exists()) - try { - return readHistory(file); - } catch (CouldNotReadCoreException e) { - e.printStackTrace(); - file.delete(); - } - return new MaxHistory(file); - } - - private static MaxHistory readHistory(File storedResults) - throws CouldNotReadCoreException { - try { - FileInputStream file= new FileInputStream(storedResults); - try { - ObjectInputStream stream= new ObjectInputStream(file); - try { - return (MaxHistory) stream.readObject(); - } finally { - stream.close(); - } - } finally { - file.close(); - } - } catch (Exception e) { - throw new CouldNotReadCoreException(e); - } - } - - private final Map<String, Long> fDurations= new HashMap<String, Long>(); - - private final Map<String, Long> fFailureTimestamps= new HashMap<String, Long>(); - - private final File fHistoryStore; - - private MaxHistory(File storedResults) { - fHistoryStore= storedResults; - } - - private void save() throws IOException { - ObjectOutputStream stream= new ObjectOutputStream(new FileOutputStream( - fHistoryStore)); - stream.writeObject(this); - stream.close(); - } - - Long getFailureTimestamp(Description key) { - return fFailureTimestamps.get(key.toString()); - } - - void putTestFailureTimestamp(Description key, long end) { - fFailureTimestamps.put(key.toString(), end); - } - - boolean isNewTest(Description key) { - return !fDurations.containsKey(key.toString()); - } - - Long getTestDuration(Description key) { - return fDurations.get(key.toString()); - } - - void putTestDuration(Description description, long duration) { - fDurations.put(description.toString(), duration); - } - - private final class RememberingListener extends RunListener { - private long overallStart= System.currentTimeMillis(); - - private Map<Description, Long> starts= new HashMap<Description, Long>(); - - @Override - public void testStarted(Description description) throws Exception { - starts.put(description, System.nanoTime()); // Get most accurate - // possible time - } - - @Override - public void testFinished(Description description) throws Exception { - long end= System.nanoTime(); - long start= starts.get(description); - putTestDuration(description, end - start); - } - - @Override - public void testFailure(Failure failure) throws Exception { - putTestFailureTimestamp(failure.getDescription(), overallStart); - } - - @Override - public void testRunFinished(Result result) throws Exception { - save(); - } - } - - private class TestComparator implements Comparator<Description> { - public int compare(Description o1, Description o2) { - // Always prefer new tests - if (isNewTest(o1)) - return -1; - if (isNewTest(o2)) - return 1; - // Then most recently failed first - int result= getFailure(o2).compareTo(getFailure(o1)); - return result != 0 ? result - // Then shorter tests first - : getTestDuration(o1).compareTo(getTestDuration(o2)); - } - - private Long getFailure(Description key) { - Long result= getFailureTimestamp(key); - if (result == null) - return 0L; // 0 = "never failed (that I know about)" - return result; - } - } - - /** - * @return a listener that will update this history based on the test - * results reported. - */ - public RunListener listener() { - return new RememberingListener(); - } - - /** - * @return a comparator that ranks tests based on the JUnit Max sorting - * rules, as described in the {@link MaxCore} class comment. - */ - public Comparator<Description> testComparator() { - return new TestComparator(); - } + private static final long serialVersionUID = 1L; + + /** + * Loads a {@link MaxHistory} from {@code file}, or generates a new one that + * will be saved to {@code file}. + */ + public static MaxHistory forFolder(File file) { + if (file.exists()) { + try { + return readHistory(file); + } catch (CouldNotReadCoreException e) { + e.printStackTrace(); + file.delete(); + } + } + return new MaxHistory(file); + } + + private static MaxHistory readHistory(File storedResults) + throws CouldNotReadCoreException { + try { + FileInputStream file = new FileInputStream(storedResults); + try { + ObjectInputStream stream = new ObjectInputStream(file); + try { + return (MaxHistory) stream.readObject(); + } finally { + stream.close(); + } + } finally { + file.close(); + } + } catch (Exception e) { + throw new CouldNotReadCoreException(e); + } + } + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final Map<String, Long> fDurations = new HashMap<String, Long>(); + private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>(); + private final File fHistoryStore; + + private MaxHistory(File storedResults) { + fHistoryStore = storedResults; + } + + private void save() throws IOException { + ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream( + fHistoryStore)); + stream.writeObject(this); + stream.close(); + } + + Long getFailureTimestamp(Description key) { + return fFailureTimestamps.get(key.toString()); + } + + void putTestFailureTimestamp(Description key, long end) { + fFailureTimestamps.put(key.toString(), end); + } + + boolean isNewTest(Description key) { + return !fDurations.containsKey(key.toString()); + } + + Long getTestDuration(Description key) { + return fDurations.get(key.toString()); + } + + void putTestDuration(Description description, long duration) { + fDurations.put(description.toString(), duration); + } + + private final class RememberingListener extends RunListener { + private long overallStart = System.currentTimeMillis(); + + private Map<Description, Long> starts = new HashMap<Description, Long>(); + + @Override + public void testStarted(Description description) throws Exception { + starts.put(description, System.nanoTime()); // Get most accurate + // possible time + } + + @Override + public void testFinished(Description description) throws Exception { + long end = System.nanoTime(); + long start = starts.get(description); + putTestDuration(description, end - start); + } + + @Override + public void testFailure(Failure failure) throws Exception { + putTestFailureTimestamp(failure.getDescription(), overallStart); + } + + @Override + public void testRunFinished(Result result) throws Exception { + save(); + } + } + + private class TestComparator implements Comparator<Description> { + public int compare(Description o1, Description o2) { + // Always prefer new tests + if (isNewTest(o1)) { + return -1; + } + if (isNewTest(o2)) { + return 1; + } + // Then most recently failed first + int result = getFailure(o2).compareTo(getFailure(o1)); + return result != 0 ? result + // Then shorter tests first + : getTestDuration(o1).compareTo(getTestDuration(o2)); + } + + private Long getFailure(Description key) { + Long result = getFailureTimestamp(key); + if (result == null) { + return 0L; // 0 = "never failed (that I know about)" + } + return result; + } + } + + /** + * @return a listener that will update this history based on the test + * results reported. + */ + public RunListener listener() { + return new RememberingListener(); + } + + /** + * @return a comparator that ranks tests based on the JUnit Max sorting + * rules, as described in the {@link MaxCore} class comment. + */ + public Comparator<Description> testComparator() { + return new TestComparator(); + } } diff --git a/src/main/java/org/junit/experimental/results/FailureList.java b/src/main/java/org/junit/experimental/results/FailureList.java index f4bc9b7..e02eeae 100644 --- a/src/main/java/org/junit/experimental/results/FailureList.java +++ b/src/main/java/org/junit/experimental/results/FailureList.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.experimental.results; import java.util.List; @@ -10,22 +7,22 @@ import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; class FailureList { - private final List<Failure> failures; + private final List<Failure> failures; - public FailureList(List<Failure> failures) { - this.failures= failures; - } + public FailureList(List<Failure> failures) { + this.failures = failures; + } - public Result result() { - Result result= new Result(); - RunListener listener= result.createListener(); - for (Failure failure : failures) { - try { - listener.testFailure(failure); - } catch (Exception e) { - throw new RuntimeException("I can't believe this happened"); - } - } - return result; - } + public Result result() { + Result result = new Result(); + RunListener listener = result.createListener(); + for (Failure failure : failures) { + try { + listener.testFailure(failure); + } catch (Exception e) { + throw new RuntimeException("I can't believe this happened"); + } + } + return result; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/results/PrintableResult.java b/src/main/java/org/junit/experimental/results/PrintableResult.java index 8bc6f54..ffe22f0 100644 --- a/src/main/java/org/junit/experimental/results/PrintableResult.java +++ b/src/main/java/org/junit/experimental/results/PrintableResult.java @@ -14,50 +14,50 @@ import org.junit.runner.notification.Failure; * A test result that prints nicely in error messages. * This is only intended to be used in JUnit self-tests. * For example: - * + * * <pre> * assertThat(testResult(HasExpectedException.class), isSuccessful()); * </pre> */ public class PrintableResult { - /** - * The result of running JUnit on {@code type} - */ - public static PrintableResult testResult(Class<?> type) { - return testResult(Request.aClass(type)); - } - - /** - * The result of running JUnit on Request {@code request} - */ - public static PrintableResult testResult(Request request) { - return new PrintableResult(new JUnitCore().run(request)); - } - - private Result result; - - /** - * A result that includes the given {@code failures} - */ - public PrintableResult(List<Failure> failures) { - this(new FailureList(failures).result()); - } - - private PrintableResult(Result result) { - this.result = result; - } - - @Override - public String toString() { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - new TextListener(new PrintStream(stream)).testRunFinished(result); - return stream.toString(); - } - - /** - * Returns the number of failures in this result. - */ - public int failureCount() { - return result.getFailures().size(); - } + private Result result; + + /** + * The result of running JUnit on {@code type} + */ + public static PrintableResult testResult(Class<?> type) { + return testResult(Request.aClass(type)); + } + + /** + * The result of running JUnit on Request {@code request} + */ + public static PrintableResult testResult(Request request) { + return new PrintableResult(new JUnitCore().run(request)); + } + + /** + * A result that includes the given {@code failures} + */ + public PrintableResult(List<Failure> failures) { + this(new FailureList(failures).result()); + } + + private PrintableResult(Result result) { + this.result = result; + } + + /** + * Returns the number of failures in this result. + */ + public int failureCount() { + return result.getFailures().size(); + } + + @Override + public String toString() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + new TextListener(new PrintStream(stream)).testRunFinished(result); + return stream.toString(); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/results/ResultMatchers.java b/src/main/java/org/junit/experimental/results/ResultMatchers.java index 220d0dc..cf58f1b 100644 --- a/src/main/java/org/junit/experimental/results/ResultMatchers.java +++ b/src/main/java/org/junit/experimental/results/ResultMatchers.java @@ -3,68 +3,68 @@ package org.junit.experimental.results; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; -import org.junit.internal.matchers.TypeSafeMatcher; +import org.hamcrest.TypeSafeMatcher; /** * Matchers on a PrintableResult, to enable JUnit self-tests. * For example: - * + * * <pre> - * assertThat(testResult(HasExpectedException.class), isSuccessful()); + * assertThat(testResult(HasExpectedException.class), isSuccessful()); * </pre> */ public class ResultMatchers { - /** - * Matches if the tests are all successful - */ - public static Matcher<PrintableResult> isSuccessful() { - return failureCountIs(0); - } + /** + * Matches if the tests are all successful + */ + public static Matcher<PrintableResult> isSuccessful() { + return failureCountIs(0); + } - /** - * Matches if there are {@code count} failures - */ - public static Matcher<PrintableResult> failureCountIs(final int count) { - return new TypeSafeMatcher<PrintableResult>() { - public void describeTo(Description description) { - description.appendText("has " + count + " failures"); - } + /** + * Matches if there are {@code count} failures + */ + public static Matcher<PrintableResult> failureCountIs(final int count) { + return new TypeSafeMatcher<PrintableResult>() { + public void describeTo(Description description) { + description.appendText("has " + count + " failures"); + } - @Override - public boolean matchesSafely(PrintableResult item) { - return item.failureCount() == count; - } - }; - } - - /** - * Matches if the result has exactly one failure, and it contains {@code string} - */ - public static Matcher<Object> hasSingleFailureContaining(final String string) { - return new BaseMatcher<Object>() { - public boolean matches(Object item) { - return item.toString().contains(string) && failureCountIs(1).matches(item); - } + @Override + public boolean matchesSafely(PrintableResult item) { + return item.failureCount() == count; + } + }; + } - public void describeTo(Description description) { - description.appendText("has single failure containing " + string); - } - }; - } + /** + * Matches if the result has exactly one failure, and it contains {@code string} + */ + public static Matcher<Object> hasSingleFailureContaining(final String string) { + return new BaseMatcher<Object>() { + public boolean matches(Object item) { + return item.toString().contains(string) && failureCountIs(1).matches(item); + } - /** - * Matches if the result has one or more failures, and at least one of them - * contains {@code string} - */ - public static Matcher<PrintableResult> hasFailureContaining(final String string) { - return new BaseMatcher<PrintableResult>() { - public boolean matches(Object item) { - return item.toString().contains(string); - } + public void describeTo(Description description) { + description.appendText("has single failure containing " + string); + } + }; + } - public void describeTo(Description description) { - description.appendText("has failure containing " + string); - } - }; - } + /** + * Matches if the result has one or more failures, and at least one of them + * contains {@code string} + */ + public static Matcher<PrintableResult> hasFailureContaining(final String string) { + return new BaseMatcher<PrintableResult>() { + public boolean matches(Object item) { + return item.toString().contains(string); + } + + public void describeTo(Description description) { + description.appendText("has failure containing " + string); + } + }; + } } diff --git a/src/main/java/org/junit/experimental/runners/Enclosed.java b/src/main/java/org/junit/experimental/runners/Enclosed.java index b0560ed..610b970 100644 --- a/src/main/java/org/junit/experimental/runners/Enclosed.java +++ b/src/main/java/org/junit/experimental/runners/Enclosed.java @@ -1,31 +1,45 @@ package org.junit.experimental.runners; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + import org.junit.runners.Suite; import org.junit.runners.model.RunnerBuilder; - /** * If you put tests in inner classes, Ant, for example, won't find them. By running the outer class * with Enclosed, the tests in the inner classes will be run. You might put tests in inner classes - * to group them for convenience or to share constants. - * - * So, for example: - * <pre> - * \@RunWith(Enclosed.class) - * public class ListTests { - * ...useful shared stuff... - * public static class OneKindOfListTest {...} - * public static class AnotherKind {...} - * } - * </pre> - * - * For a real example, @see org.junit.tests.manipulation.SortableTest. + * to group them for convenience or to share constants. Abstract inner classes are ignored. + * <p> + * So, for example: + * <pre> + * @RunWith(Enclosed.class) + * public class ListTests { + * ...useful shared stuff... + * public static class OneKindOfListTest {...} + * public static class AnotherKind {...} + * abstract public static class Ignored {...} + * } + * </pre> */ public class Enclosed extends Suite { - /** - * Only called reflectively. Do not use programmatically. - */ - public Enclosed(Class<?> klass, RunnerBuilder builder) throws Throwable { - super(builder, klass, klass.getClasses()); - } + /** + * Only called reflectively. Do not use programmatically. + */ + public Enclosed(Class<?> klass, RunnerBuilder builder) throws Throwable { + super(builder, klass, filterAbstractClasses(klass.getClasses())); + } + + private static Class<?>[] filterAbstractClasses(final Class<?>[] classes) { + final List<Class<?>> filteredList= new ArrayList<Class<?>>(classes.length); + + for (final Class<?> clazz : classes) { + if (!Modifier.isAbstract(clazz.getModifiers())) { + filteredList.add(clazz); + } + } + + return filteredList.toArray(new Class<?>[filteredList.size()]); + } } diff --git a/src/main/java/org/junit/experimental/theories/DataPoint.java b/src/main/java/org/junit/experimental/theories/DataPoint.java index 2aaba6a..0a017bb 100644 --- a/src/main/java/org/junit/experimental/theories/DataPoint.java +++ b/src/main/java/org/junit/experimental/theories/DataPoint.java @@ -1,9 +1,56 @@ package org.junit.experimental.theories; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Annotating an field or method with @DataPoint will cause the field value + * or the value returned by the method to be used as a potential parameter for + * theories in that class, when run with the + * {@link org.junit.experimental.theories.Theories Theories} runner. + * <p> + * A DataPoint is only considered as a potential value for parameters for + * which its type is assignable. When multiple {@code DataPoint}s exist + * with overlapping types more control can be obtained by naming each DataPoint + * using the value of this annotation, e.g. with + * <code>@DataPoint({"dataset1", "dataset2"})</code>, and then specifying + * which named set to consider as potential values for each parameter using the + * {@link org.junit.experimental.theories.FromDataPoints @FromDataPoints} + * annotation. + * <p> + * Parameters with no specified source (i.e. without @FromDataPoints or + * other {@link org.junit.experimental.theories.ParametersSuppliedBy + * @ParameterSuppliedBy} annotations) will use all {@code DataPoint}s that are + * assignable to the parameter type as potential values, including named sets of + * {@code DataPoint}s. + * + * <pre> + * @DataPoint + * public static String dataPoint = "value"; + * + * @DataPoint("generated") + * public static String generatedDataPoint() { + * return "generated value"; + * } + * + * @Theory + * public void theoryMethod(String param) { + * ... + * } + * </pre> + * + * @see org.junit.experimental.theories.Theories + * @see org.junit.experimental.theories.Theory + * @see org.junit.experimental.theories.DataPoint + * @see org.junit.experimental.theories.FromDataPoints + */ @Retention(RetentionPolicy.RUNTIME) +@Target({FIELD, METHOD}) public @interface DataPoint { - -} + String[] value() default {}; + Class<? extends Throwable>[] ignoredExceptions() default {}; +}
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/DataPoints.java b/src/main/java/org/junit/experimental/theories/DataPoints.java index 42145e3..b47461b 100644 --- a/src/main/java/org/junit/experimental/theories/DataPoints.java +++ b/src/main/java/org/junit/experimental/theories/DataPoints.java @@ -1,9 +1,64 @@ package org.junit.experimental.theories; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Annotating an array or iterable-typed field or method with @DataPoints + * will cause the values in the array or iterable given to be used as potential + * parameters for theories in that class when run with the + * {@link org.junit.experimental.theories.Theories Theories} runner. + * <p> + * DataPoints will only be considered as potential values for parameters for + * which their types are assignable. When multiple sets of DataPoints exist with + * overlapping types more control can be obtained by naming the DataPoints using + * the value of this annotation, e.g. with + * <code>@DataPoints({"dataset1", "dataset2"})</code>, and then specifying + * which named set to consider as potential values for each parameter using the + * {@link org.junit.experimental.theories.FromDataPoints @FromDataPoints} + * annotation. + * <p> + * Parameters with no specified source (i.e. without @FromDataPoints or + * other {@link org.junit.experimental.theories.ParametersSuppliedBy + * @ParameterSuppliedBy} annotations) will use all DataPoints that are + * assignable to the parameter type as potential values, including named sets of + * DataPoints. + * <p> + * DataPoints methods whose array types aren't assignable from the target + * parameter type (and so can't possibly return relevant values) will not be + * called when generating values for that parameter. Iterable-typed datapoints + * methods must always be called though, as this information is not available + * here after generic type erasure, so expensive methods returning iterable + * datapoints are a bad idea. + * + * <pre> + * @DataPoints + * public static String[] dataPoints = new String[] { ... }; + * + * @DataPoints + * public static String[] generatedDataPoints() { + * return new String[] { ... }; + * } + * + * @Theory + * public void theoryMethod(String param) { + * ... + * } + * </pre> + * + * @see org.junit.experimental.theories.Theories + * @see org.junit.experimental.theories.Theory + * @see org.junit.experimental.theories.DataPoint + * @see org.junit.experimental.theories.FromDataPoints + */ @Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) public @interface DataPoints { + String[] value() default {}; + Class<? extends Throwable>[] ignoredExceptions() default {}; } diff --git a/src/main/java/org/junit/experimental/theories/FromDataPoints.java b/src/main/java/org/junit/experimental/theories/FromDataPoints.java new file mode 100644 index 0000000..2b149ca --- /dev/null +++ b/src/main/java/org/junit/experimental/theories/FromDataPoints.java @@ -0,0 +1,54 @@ +package org.junit.experimental.theories; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.experimental.theories.internal.SpecificDataPointsSupplier; + +/** + * Annotating a parameter of a {@link org.junit.experimental.theories.Theory + * @Theory} method with <code>@FromDataPoints</code> will limit the + * datapoints considered as potential values for that parameter to just the + * {@link org.junit.experimental.theories.DataPoints DataPoints} with the given + * name. DataPoint names can be given as the value parameter of the + * @DataPoints annotation. + * <p> + * DataPoints without names will not be considered as values for any parameters + * annotated with @FromDataPoints. + * <pre> + * @DataPoints + * public static String[] unnamed = new String[] { ... }; + * + * @DataPoints("regexes") + * public static String[] regexStrings = new String[] { ... }; + * + * @DataPoints({"forMatching", "alphanumeric"}) + * public static String[] testStrings = new String[] { ... }; + * + * @Theory + * public void stringTheory(String param) { + * // This will be called with every value in 'regexStrings', + * // 'testStrings' and 'unnamed'. + * } + * + * @Theory + * public void regexTheory(@FromDataPoints("regexes") String regex, + * @FromDataPoints("forMatching") String value) { + * // This will be called with only the values in 'regexStrings' as + * // regex, only the values in 'testStrings' as value, and none + * // of the values in 'unnamed'. + * } + * </pre> + * + * @see org.junit.experimental.theories.Theory + * @see org.junit.experimental.theories.DataPoint + * @see org.junit.experimental.theories.DataPoints + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@ParametersSuppliedBy(SpecificDataPointsSupplier.class) +public @interface FromDataPoints { + String value(); +} diff --git a/src/main/java/org/junit/experimental/theories/ParameterSignature.java b/src/main/java/org/junit/experimental/theories/ParameterSignature.java index e7150fc..cf22583 100644 --- a/src/main/java/org/junit/experimental/theories/ParameterSignature.java +++ b/src/main/java/org/junit/experimental/theories/ParameterSignature.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.experimental.theories; import java.lang.annotation.Annotation; @@ -8,83 +5,130 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class ParameterSignature { - public static ArrayList<ParameterSignature> signatures(Method method) { - return signatures(method.getParameterTypes(), method - .getParameterAnnotations()); - } - - public static List<ParameterSignature> signatures(Constructor<?> constructor) { - return signatures(constructor.getParameterTypes(), constructor - .getParameterAnnotations()); - } - - private static ArrayList<ParameterSignature> signatures( - Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) { - ArrayList<ParameterSignature> sigs= new ArrayList<ParameterSignature>(); - for (int i= 0; i < parameterTypes.length; i++) { - sigs.add(new ParameterSignature(parameterTypes[i], - parameterAnnotations[i])); - } - return sigs; - } - - private final Class<?> type; - - private final Annotation[] annotations; - - private ParameterSignature(Class<?> type, Annotation[] annotations) { - this.type= type; - this.annotations= annotations; - } - - public boolean canAcceptType(Class<?> candidate) { - return type.isAssignableFrom(candidate); - } - - public Class<?> getType() { - return type; - } - - public List<Annotation> getAnnotations() { - return Arrays.asList(annotations); - } - - public boolean canAcceptArrayType(Class<?> type) { - return type.isArray() && canAcceptType(type.getComponentType()); - } - - public boolean hasAnnotation(Class<? extends Annotation> type) { - return getAnnotation(type) != null; - } - - public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) { - Annotation[] annotations2= annotations; - return findDeepAnnotation(annotations2, annotationType, 3); - } - - private <T extends Annotation> T findDeepAnnotation( - Annotation[] annotations, Class<T> annotationType, int depth) { - if (depth == 0) - return null; - for (Annotation each : annotations) { - if (annotationType.isInstance(each)) - return annotationType.cast(each); - Annotation candidate= findDeepAnnotation(each.annotationType() - .getAnnotations(), annotationType, depth - 1); - if (candidate != null) - return annotationType.cast(candidate); - } - - return null; - } - - public <T extends Annotation> T getAnnotation(Class<T> annotationType) { - for (Annotation each : getAnnotations()) - if (annotationType.isInstance(each)) - return annotationType.cast(each); - return null; - } + + private static final Map<Class<?>, Class<?>> CONVERTABLE_TYPES_MAP = buildConvertableTypesMap(); + + private static Map<Class<?>, Class<?>> buildConvertableTypesMap() { + Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>(); + + putSymmetrically(map, boolean.class, Boolean.class); + putSymmetrically(map, byte.class, Byte.class); + putSymmetrically(map, short.class, Short.class); + putSymmetrically(map, char.class, Character.class); + putSymmetrically(map, int.class, Integer.class); + putSymmetrically(map, long.class, Long.class); + putSymmetrically(map, float.class, Float.class); + putSymmetrically(map, double.class, Double.class); + + return Collections.unmodifiableMap(map); + } + + private static <T> void putSymmetrically(Map<T, T> map, T a, T b) { + map.put(a, b); + map.put(b, a); + } + + public static ArrayList<ParameterSignature> signatures(Method method) { + return signatures(method.getParameterTypes(), method + .getParameterAnnotations()); + } + + public static List<ParameterSignature> signatures(Constructor<?> constructor) { + return signatures(constructor.getParameterTypes(), constructor + .getParameterAnnotations()); + } + + private static ArrayList<ParameterSignature> signatures( + Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) { + ArrayList<ParameterSignature> sigs = new ArrayList<ParameterSignature>(); + for (int i = 0; i < parameterTypes.length; i++) { + sigs.add(new ParameterSignature(parameterTypes[i], + parameterAnnotations[i])); + } + return sigs; + } + + private final Class<?> type; + + private final Annotation[] annotations; + + private ParameterSignature(Class<?> type, Annotation[] annotations) { + this.type = type; + this.annotations = annotations; + } + + public boolean canAcceptValue(Object candidate) { + return (candidate == null) ? !type.isPrimitive() : canAcceptType(candidate.getClass()); + } + + public boolean canAcceptType(Class<?> candidate) { + return type.isAssignableFrom(candidate) || + isAssignableViaTypeConversion(type, candidate); + } + + public boolean canPotentiallyAcceptType(Class<?> candidate) { + return candidate.isAssignableFrom(type) || + isAssignableViaTypeConversion(candidate, type) || + canAcceptType(candidate); + } + + private boolean isAssignableViaTypeConversion(Class<?> targetType, Class<?> candidate) { + if (CONVERTABLE_TYPES_MAP.containsKey(candidate)) { + Class<?> wrapperClass = CONVERTABLE_TYPES_MAP.get(candidate); + return targetType.isAssignableFrom(wrapperClass); + } else { + return false; + } + } + + public Class<?> getType() { + return type; + } + + public List<Annotation> getAnnotations() { + return Arrays.asList(annotations); + } + + public boolean hasAnnotation(Class<? extends Annotation> type) { + return getAnnotation(type) != null; + } + + public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) { + Annotation[] annotations2 = annotations; + return findDeepAnnotation(annotations2, annotationType, 3); + } + + private <T extends Annotation> T findDeepAnnotation( + Annotation[] annotations, Class<T> annotationType, int depth) { + if (depth == 0) { + return null; + } + for (Annotation each : annotations) { + if (annotationType.isInstance(each)) { + return annotationType.cast(each); + } + Annotation candidate = findDeepAnnotation(each.annotationType() + .getAnnotations(), annotationType, depth - 1); + if (candidate != null) { + return annotationType.cast(candidate); + } + } + + return null; + } + + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + for (Annotation each : getAnnotations()) { + if (annotationType.isInstance(each)) { + return annotationType.cast(each); + } + } + return null; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java index 9016c91..bac8b34 100644 --- a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java +++ b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java @@ -2,7 +2,42 @@ package org.junit.experimental.theories; import java.util.List; - +/** + * Abstract parent class for suppliers of input data points for theories. Extend this class to customize how {@link + * org.junit.experimental.theories.Theories Theories} runner + * finds accepted data points. Then use your class together with <b>@ParametersSuppliedBy</b> on input + * parameters for theories. + * + * <p> + * For example, here is a supplier for values between two integers, and an annotation that references it: + * + * <pre> + * @Retention(RetentionPolicy.RUNTIME) + * <b>@ParametersSuppliedBy</b>(BetweenSupplier.class) + * public @interface Between { + * int first(); + * + * int last(); + * } + * + * public static class BetweenSupplier extends <b>ParameterSupplier</b> { + * @Override + * public List<<b>PotentialAssignment</b>> getValueSources(<b>ParameterSignature</b> sig) { + * List<<b>PotentialAssignment</b>> list = new ArrayList<PotentialAssignment>(); + * Between annotation = (Between) sig.getSupplierAnnotation(); + * + * for (int i = annotation.first(); i <= annotation.last(); i++) + * list.add(<b>PotentialAssignment</b>.forValue("ints", i)); + * return list; + * } + * } + * </pre> + * </p> + * + * @see org.junit.experimental.theories.ParametersSuppliedBy + * @see org.junit.experimental.theories.Theories + * @see org.junit.experimental.theories.FromDataPoints + */ public abstract class ParameterSupplier { - public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig); + public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable; } diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java index 8f090ef..15b5d95 100644 --- a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java +++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java @@ -1,12 +1,48 @@ package org.junit.experimental.theories; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.PARAMETER; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; - +/** + * Annotating a {@link org.junit.experimental.theories.Theory Theory} method + * parameter with @ParametersSuppliedBy causes it to be supplied with + * values from the named + * {@link org.junit.experimental.theories.ParameterSupplier ParameterSupplier} + * when run as a theory by the {@link org.junit.experimental.theories.Theories + * Theories} runner. + * + * In addition, annotations themselves can be annotated with + * @ParametersSuppliedBy, and then used similarly. ParameterSuppliedBy + * annotations on parameters are detected by searching up this heirarchy such + * that these act as syntactic sugar, making: + * + * <pre> + * @ParametersSuppliedBy(Supplier.class) + * public @interface SpecialParameter { } + * + * @Theory + * public void theoryMethod(@SpecialParameter String param) { + * ... + * } + * </pre> + * + * equivalent to: + * + * <pre> + * @Theory + * public void theoryMethod(@ParametersSuppliedBy(Supplier.class) String param) { + * ... + * } + * </pre> + */ @Retention(RetentionPolicy.RUNTIME) +@Target({ ANNOTATION_TYPE, PARAMETER }) public @interface ParametersSuppliedBy { - Class<? extends ParameterSupplier> value(); + Class<? extends ParameterSupplier> value(); } diff --git a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java index 0c008d0..18ca07a 100644 --- a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java +++ b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java @@ -1,31 +1,52 @@ package org.junit.experimental.theories; +import static java.lang.String.format; + public abstract class PotentialAssignment { - public static class CouldNotGenerateValueException extends Exception { - private static final long serialVersionUID= 1L; - } - - public static PotentialAssignment forValue(final String name, final Object value) { - return new PotentialAssignment() { - @Override - public Object getValue() throws CouldNotGenerateValueException { - return value; - } - - @Override - public String toString() { - return String.format("[%s]", value); - } - - @Override - public String getDescription() - throws CouldNotGenerateValueException { - return name; - } - }; - } - - public abstract Object getValue() throws CouldNotGenerateValueException; - - public abstract String getDescription() throws CouldNotGenerateValueException; -} + public static class CouldNotGenerateValueException extends Exception { + private static final long serialVersionUID = 1L; + + public CouldNotGenerateValueException() { + } + + public CouldNotGenerateValueException(Throwable e) { + super(e); + } + } + + public static PotentialAssignment forValue(final String name, final Object value) { + return new PotentialAssignment() { + @Override + public Object getValue() { + return value; + } + + @Override + public String toString() { + return format("[%s]", value); + } + + @Override + public String getDescription() { + String valueString; + + if (value == null) { + valueString = "null"; + } else { + try { + valueString = format("\"%s\"", value); + } catch (Throwable e) { + valueString = format("[toString() threw %s: %s]", + e.getClass().getSimpleName(), e.getMessage()); + } + } + + return format("%s <from %s>", valueString, name); + } + }; + } + + public abstract Object getValue() throws CouldNotGenerateValueException; + + public abstract String getDescription() throws CouldNotGenerateValueException; +}
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/Theories.java b/src/main/java/org/junit/experimental/theories/Theories.java index 82ff98b..817f553 100644 --- a/src/main/java/org/junit/experimental/theories/Theories.java +++ b/src/main/java/org/junit/experimental/theories/Theories.java @@ -1,16 +1,14 @@ -/** - * - */ package org.junit.experimental.theories; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.junit.Assert; -import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException; +import org.junit.Assume; import org.junit.experimental.theories.internal.Assignments; import org.junit.experimental.theories.internal.ParameterizedAssertionError; import org.junit.internal.AssumptionViolatedException; @@ -20,180 +18,288 @@ import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; +/** + * The Theories runner allows to test a certain functionality against a subset of an infinite set of data points. + * <p> + * A Theory is a piece of functionality (a method) that is executed against several data inputs called data points. + * To make a test method a theory you mark it with <b>@Theory</b>. To create a data point you create a public + * field in your test class and mark it with <b>@DataPoint</b>. The Theories runner then executes your test + * method as many times as the number of data points declared, providing a different data point as + * the input argument on each invocation. + * </p> + * <p> + * A Theory differs from standard test method in that it captures some aspect of the intended behavior in possibly + * infinite numbers of scenarios which corresponds to the number of data points declared. Using assumptions and + * assertions properly together with covering multiple scenarios with different data points can make your tests more + * flexible and bring them closer to scientific theories (hence the name). + * </p> + * <p> + * For example: + * <pre> + * + * @RunWith(<b>Theories.class</b>) + * public class UserTest { + * <b>@DataPoint</b> + * public static String GOOD_USERNAME = "optimus"; + * <b>@DataPoint</b> + * public static String USERNAME_WITH_SLASH = "optimus/prime"; + * + * <b>@Theory</b> + * public void filenameIncludesUsername(String username) { + * assumeThat(username, not(containsString("/"))); + * assertThat(new User(username).configFileName(), containsString(username)); + * } + * } + * </pre> + * This makes it clear that the user's filename should be included in the config file name, + * only if it doesn't contain a slash. Another test or theory might define what happens when a username does contain + * a slash. <code>UserTest</code> will attempt to run <code>filenameIncludesUsername</code> on every compatible data + * point defined in the class. If any of the assumptions fail, the data point is silently ignored. If all of the + * assumptions pass, but an assertion fails, the test fails. + * <p> + * Defining general statements as theories allows data point reuse across a bunch of functionality tests and also + * allows automated tools to search for new, unexpected data points that expose bugs. + * </p> + * <p> + * The support for Theories has been absorbed from the Popper project, and more complete documentation can be found + * from that projects archived documentation. + * </p> + * + * @see <a href="http://web.archive.org/web/20071012143326/popper.tigris.org/tutorial.html">Archived Popper project documentation</a> + * @see <a href="http://web.archive.org/web/20110608210825/http://shareandenjoy.saff.net/tdd-specifications.pdf">Paper on Theories</a> + */ public class Theories extends BlockJUnit4ClassRunner { - public Theories(Class<?> klass) throws InitializationError { - super(klass); - } - - @Override - protected void collectInitializationErrors(List<Throwable> errors) { - super.collectInitializationErrors(errors); - validateDataPointFields(errors); - } - - private void validateDataPointFields(List<Throwable> errors) { - Field[] fields= getTestClass().getJavaClass().getDeclaredFields(); - - for (Field each : fields) - if (each.getAnnotation(DataPoint.class) != null && !Modifier.isStatic(each.getModifiers())) - errors.add(new Error("DataPoint field " + each.getName() + " must be static")); - } - - @Override - protected void validateConstructor(List<Throwable> errors) { - validateOnlyOneConstructor(errors); - } - - @Override - protected void validateTestMethods(List<Throwable> errors) { - for (FrameworkMethod each : computeTestMethods()) - if(each.getAnnotation(Theory.class) != null) - each.validatePublicVoid(false, errors); - else - each.validatePublicVoidNoArg(false, errors); - } - - @Override - protected List<FrameworkMethod> computeTestMethods() { - List<FrameworkMethod> testMethods= super.computeTestMethods(); - List<FrameworkMethod> theoryMethods= getTestClass().getAnnotatedMethods(Theory.class); - testMethods.removeAll(theoryMethods); - testMethods.addAll(theoryMethods); - return testMethods; - } - - @Override - public Statement methodBlock(final FrameworkMethod method) { - return new TheoryAnchor(method, getTestClass()); - } - - public static class TheoryAnchor extends Statement { - private int successes= 0; - - private FrameworkMethod fTestMethod; - private TestClass fTestClass; - - private List<AssumptionViolatedException> fInvalidParameters= new ArrayList<AssumptionViolatedException>(); - - public TheoryAnchor(FrameworkMethod method, TestClass testClass) { - fTestMethod= method; - fTestClass= testClass; - } + public Theories(Class<?> klass) throws InitializationError { + super(klass); + } + + @Override + protected void collectInitializationErrors(List<Throwable> errors) { + super.collectInitializationErrors(errors); + validateDataPointFields(errors); + validateDataPointMethods(errors); + } + + private void validateDataPointFields(List<Throwable> errors) { + Field[] fields = getTestClass().getJavaClass().getDeclaredFields(); + + for (Field field : fields) { + if (field.getAnnotation(DataPoint.class) == null && field.getAnnotation(DataPoints.class) == null) { + continue; + } + if (!Modifier.isStatic(field.getModifiers())) { + errors.add(new Error("DataPoint field " + field.getName() + " must be static")); + } + if (!Modifier.isPublic(field.getModifiers())) { + errors.add(new Error("DataPoint field " + field.getName() + " must be public")); + } + } + } + + private void validateDataPointMethods(List<Throwable> errors) { + Method[] methods = getTestClass().getJavaClass().getDeclaredMethods(); + + for (Method method : methods) { + if (method.getAnnotation(DataPoint.class) == null && method.getAnnotation(DataPoints.class) == null) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + errors.add(new Error("DataPoint method " + method.getName() + " must be static")); + } + if (!Modifier.isPublic(method.getModifiers())) { + errors.add(new Error("DataPoint method " + method.getName() + " must be public")); + } + } + } + + @Override + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + } + + @Override + protected void validateTestMethods(List<Throwable> errors) { + for (FrameworkMethod each : computeTestMethods()) { + if (each.getAnnotation(Theory.class) != null) { + each.validatePublicVoid(false, errors); + each.validateNoTypeParametersOnArgs(errors); + } else { + each.validatePublicVoidNoArg(false, errors); + } + + for (ParameterSignature signature : ParameterSignature.signatures(each.getMethod())) { + ParametersSuppliedBy annotation = signature.findDeepAnnotation(ParametersSuppliedBy.class); + if (annotation != null) { + validateParameterSupplier(annotation.value(), errors); + } + } + } + } + + private void validateParameterSupplier(Class<? extends ParameterSupplier> supplierClass, List<Throwable> errors) { + Constructor<?>[] constructors = supplierClass.getConstructors(); + + if (constructors.length != 1) { + errors.add(new Error("ParameterSupplier " + supplierClass.getName() + + " must have only one constructor (either empty or taking only a TestClass)")); + } else { + Class<?>[] paramTypes = constructors[0].getParameterTypes(); + if (!(paramTypes.length == 0) && !paramTypes[0].equals(TestClass.class)) { + errors.add(new Error("ParameterSupplier " + supplierClass.getName() + + " constructor must take either nothing or a single TestClass instance")); + } + } + } + + @Override + protected List<FrameworkMethod> computeTestMethods() { + List<FrameworkMethod> testMethods = new ArrayList<FrameworkMethod>(super.computeTestMethods()); + List<FrameworkMethod> theoryMethods = getTestClass().getAnnotatedMethods(Theory.class); + testMethods.removeAll(theoryMethods); + testMethods.addAll(theoryMethods); + return testMethods; + } + + @Override + public Statement methodBlock(final FrameworkMethod method) { + return new TheoryAnchor(method, getTestClass()); + } + + public static class TheoryAnchor extends Statement { + private int successes = 0; + + private final FrameworkMethod testMethod; + private final TestClass testClass; + + private List<AssumptionViolatedException> fInvalidParameters = new ArrayList<AssumptionViolatedException>(); + + public TheoryAnchor(FrameworkMethod testMethod, TestClass testClass) { + this.testMethod = testMethod; + this.testClass = testClass; + } private TestClass getTestClass() { - return fTestClass; + return testClass; } - @Override - public void evaluate() throws Throwable { - runWithAssignment(Assignments.allUnassigned( - fTestMethod.getMethod(), getTestClass())); - - if (successes == 0) - Assert - .fail("Never found parameters that satisfied method assumptions. Violated assumptions: " - + fInvalidParameters); - } - - protected void runWithAssignment(Assignments parameterAssignment) - throws Throwable { - if (!parameterAssignment.isComplete()) { - runWithIncompleteAssignment(parameterAssignment); - } else { - runWithCompleteAssignment(parameterAssignment); - } - } - - protected void runWithIncompleteAssignment(Assignments incomplete) - throws InstantiationException, IllegalAccessException, - Throwable { - for (PotentialAssignment source : incomplete - .potentialsForNextUnassigned()) { - runWithAssignment(incomplete.assignNext(source)); - } - } - - protected void runWithCompleteAssignment(final Assignments complete) - throws InstantiationException, IllegalAccessException, - InvocationTargetException, NoSuchMethodException, Throwable { - new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) { - @Override - protected void collectInitializationErrors( - List<Throwable> errors) { - // do nothing - } - - @Override - public Statement methodBlock(FrameworkMethod method) { - final Statement statement= super.methodBlock(method); - return new Statement() { - @Override - public void evaluate() throws Throwable { - try { - statement.evaluate(); - handleDataPointSuccess(); - } catch (AssumptionViolatedException e) { - handleAssumptionViolation(e); - } catch (Throwable e) { - reportParameterizedError(e, complete - .getArgumentStrings(nullsOk())); - } - } - - }; - } - - @Override - protected Statement methodInvoker(FrameworkMethod method, Object test) { - return methodCompletesWithParameters(method, complete, test); - } - - @Override - public Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance( - complete.getConstructorArguments(nullsOk())); - } - }.methodBlock(fTestMethod).evaluate(); - } - - private Statement methodCompletesWithParameters( - final FrameworkMethod method, final Assignments complete, final Object freshInstance) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - try { - final Object[] values= complete.getMethodArguments( - nullsOk()); - method.invokeExplosively(freshInstance, values); - } catch (CouldNotGenerateValueException e) { - // ignore - } - } - }; - } - - protected void handleAssumptionViolation(AssumptionViolatedException e) { - fInvalidParameters.add(e); - } - - protected void reportParameterizedError(Throwable e, Object... params) - throws Throwable { - if (params.length == 0) - throw e; - throw new ParameterizedAssertionError(e, fTestMethod.getName(), - params); - } - - private boolean nullsOk() { - Theory annotation= fTestMethod.getMethod().getAnnotation( - Theory.class); - if (annotation == null) - return false; - return annotation.nullsAccepted(); - } - - protected void handleDataPointSuccess() { - successes++; - } - } + @Override + public void evaluate() throws Throwable { + runWithAssignment(Assignments.allUnassigned( + testMethod.getMethod(), getTestClass())); + + //if this test method is not annotated with Theory, then no successes is a valid case + boolean hasTheoryAnnotation = testMethod.getAnnotation(Theory.class) != null; + if (successes == 0 && hasTheoryAnnotation) { + Assert + .fail("Never found parameters that satisfied method assumptions. Violated assumptions: " + + fInvalidParameters); + } + } + + protected void runWithAssignment(Assignments parameterAssignment) + throws Throwable { + if (!parameterAssignment.isComplete()) { + runWithIncompleteAssignment(parameterAssignment); + } else { + runWithCompleteAssignment(parameterAssignment); + } + } + + protected void runWithIncompleteAssignment(Assignments incomplete) + throws Throwable { + for (PotentialAssignment source : incomplete + .potentialsForNextUnassigned()) { + runWithAssignment(incomplete.assignNext(source)); + } + } + + protected void runWithCompleteAssignment(final Assignments complete) + throws Throwable { + new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) { + @Override + protected void collectInitializationErrors( + List<Throwable> errors) { + // do nothing + } + + @Override + public Statement methodBlock(FrameworkMethod method) { + final Statement statement = super.methodBlock(method); + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + statement.evaluate(); + handleDataPointSuccess(); + } catch (AssumptionViolatedException e) { + handleAssumptionViolation(e); + } catch (Throwable e) { + reportParameterizedError(e, complete + .getArgumentStrings(nullsOk())); + } + } + + }; + } + + @Override + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return methodCompletesWithParameters(method, complete, test); + } + + @Override + public Object createTest() throws Exception { + Object[] params = complete.getConstructorArguments(); + + if (!nullsOk()) { + Assume.assumeNotNull(params); + } + + return getTestClass().getOnlyConstructor().newInstance(params); + } + }.methodBlock(testMethod).evaluate(); + } + + private Statement methodCompletesWithParameters( + final FrameworkMethod method, final Assignments complete, final Object freshInstance) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + final Object[] values = complete.getMethodArguments(); + + if (!nullsOk()) { + Assume.assumeNotNull(values); + } + + method.invokeExplosively(freshInstance, values); + } + }; + } + + protected void handleAssumptionViolation(AssumptionViolatedException e) { + fInvalidParameters.add(e); + } + + protected void reportParameterizedError(Throwable e, Object... params) + throws Throwable { + if (params.length == 0) { + throw e; + } + throw new ParameterizedAssertionError(e, testMethod.getName(), + params); + } + + private boolean nullsOk() { + Theory annotation = testMethod.getMethod().getAnnotation( + Theory.class); + if (annotation == null) { + return false; + } + return annotation.nullsAccepted(); + } + + protected void handleDataPointSuccess() { + successes++; + } + } } diff --git a/src/main/java/org/junit/experimental/theories/Theory.java b/src/main/java/org/junit/experimental/theories/Theory.java index 134fe9d..0b9f2c4 100644 --- a/src/main/java/org/junit/experimental/theories/Theory.java +++ b/src/main/java/org/junit/experimental/theories/Theory.java @@ -1,12 +1,18 @@ -/** - * - */ package org.junit.experimental.theories; +import static java.lang.annotation.ElementType.METHOD; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Marks test methods that should be read as theories by the {@link org.junit.experimental.theories.Theories Theories} runner. + * + * @see org.junit.experimental.theories.Theories + */ @Retention(RetentionPolicy.RUNTIME) +@Target(METHOD) public @interface Theory { - boolean nullsAccepted() default true; + boolean nullsAccepted() default true; }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java index 615cc3e..f15fb28 100644 --- a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java +++ b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java @@ -1,19 +1,19 @@ -/** - * - */ package org.junit.experimental.theories.internal; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import org.junit.Assume; import org.junit.experimental.theories.DataPoint; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.ParameterSignature; import org.junit.experimental.theories.ParameterSupplier; import org.junit.experimental.theories.PotentialAssignment; +import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.TestClass; @@ -21,107 +21,184 @@ import org.junit.runners.model.TestClass; * Supplies Theory parameters based on all public members of the target class. */ public class AllMembersSupplier extends ParameterSupplier { - static class MethodParameterValue extends PotentialAssignment { - private final FrameworkMethod fMethod; - - private MethodParameterValue(FrameworkMethod dataPointMethod) { - fMethod= dataPointMethod; - } - - @Override - public Object getValue() throws CouldNotGenerateValueException { - try { - return fMethod.invokeExplosively(null); - } catch (IllegalArgumentException e) { - throw new RuntimeException( - "unexpected: argument length is checked"); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "unexpected: getMethods returned an inaccessible method"); - } catch (Throwable e) { - throw new CouldNotGenerateValueException(); - // do nothing, just look for more values - } - } - - @Override - public String getDescription() throws CouldNotGenerateValueException { - return fMethod.getName(); - } - } - - private final TestClass fClass; - - /** - * Constructs a new supplier for {@code type} - */ - public AllMembersSupplier(TestClass type) { - fClass= type; - } - - @Override - public List<PotentialAssignment> getValueSources(ParameterSignature sig) { - List<PotentialAssignment> list= new ArrayList<PotentialAssignment>(); - - addFields(sig, list); - addSinglePointMethods(sig, list); - addMultiPointMethods(list); - - return list; - } - - private void addMultiPointMethods(List<PotentialAssignment> list) { - for (FrameworkMethod dataPointsMethod : fClass - .getAnnotatedMethods(DataPoints.class)) - try { - addArrayValues(dataPointsMethod.getName(), list, dataPointsMethod.invokeExplosively(null)); - } catch (Throwable e) { - // ignore and move on - } - } - - @SuppressWarnings("deprecation") - private void addSinglePointMethods(ParameterSignature sig, - List<PotentialAssignment> list) { - for (FrameworkMethod dataPointMethod : fClass - .getAnnotatedMethods(DataPoint.class)) { - Class<?> type= sig.getType(); - if ((dataPointMethod.producesType(type))) - list.add(new MethodParameterValue(dataPointMethod)); - } - } - - private void addFields(ParameterSignature sig, - List<PotentialAssignment> list) { - for (final Field field : fClass.getJavaClass().getFields()) { - if (Modifier.isStatic(field.getModifiers())) { - Class<?> type= field.getType(); - if (sig.canAcceptArrayType(type) - && field.getAnnotation(DataPoints.class) != null) { - addArrayValues(field.getName(), list, getStaticFieldValue(field)); - } else if (sig.canAcceptType(type) - && field.getAnnotation(DataPoint.class) != null) { - list.add(PotentialAssignment - .forValue(field.getName(), getStaticFieldValue(field))); - } - } - } - } - - private void addArrayValues(String name, List<PotentialAssignment> list, Object array) { - for (int i= 0; i < Array.getLength(array); i++) - list.add(PotentialAssignment.forValue(name + "[" + i + "]", Array.get(array, i))); - } - - private Object getStaticFieldValue(final Field field) { - try { - return field.get(null); - } catch (IllegalArgumentException e) { - throw new RuntimeException( - "unexpected: field from getClass doesn't exist on object"); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "unexpected: getFields returned an inaccessible field"); - } - } + static class MethodParameterValue extends PotentialAssignment { + private final FrameworkMethod method; + + private MethodParameterValue(FrameworkMethod dataPointMethod) { + method = dataPointMethod; + } + + @Override + public Object getValue() throws CouldNotGenerateValueException { + try { + return method.invokeExplosively(null); + } catch (IllegalArgumentException e) { + throw new RuntimeException( + "unexpected: argument length is checked"); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "unexpected: getMethods returned an inaccessible method"); + } catch (Throwable throwable) { + DataPoint annotation = method.getAnnotation(DataPoint.class); + Assume.assumeTrue(annotation == null || !isAssignableToAnyOf(annotation.ignoredExceptions(), throwable)); + + throw new CouldNotGenerateValueException(throwable); + } + } + + @Override + public String getDescription() throws CouldNotGenerateValueException { + return method.getName(); + } + } + + private final TestClass clazz; + + /** + * Constructs a new supplier for {@code type} + */ + public AllMembersSupplier(TestClass type) { + clazz = type; + } + + @Override + public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable { + List<PotentialAssignment> list = new ArrayList<PotentialAssignment>(); + + addSinglePointFields(sig, list); + addMultiPointFields(sig, list); + addSinglePointMethods(sig, list); + addMultiPointMethods(sig, list); + + return list; + } + + private void addMultiPointMethods(ParameterSignature sig, List<PotentialAssignment> list) throws Throwable { + for (FrameworkMethod dataPointsMethod : getDataPointsMethods(sig)) { + Class<?> returnType = dataPointsMethod.getReturnType(); + + if ((returnType.isArray() && sig.canPotentiallyAcceptType(returnType.getComponentType())) || + Iterable.class.isAssignableFrom(returnType)) { + try { + addDataPointsValues(returnType, sig, dataPointsMethod.getName(), list, + dataPointsMethod.invokeExplosively(null)); + } catch (Throwable throwable) { + DataPoints annotation = dataPointsMethod.getAnnotation(DataPoints.class); + if (annotation != null && isAssignableToAnyOf(annotation.ignoredExceptions(), throwable)) { + return; + } else { + throw throwable; + } + } + } + } + } + + private void addSinglePointMethods(ParameterSignature sig, List<PotentialAssignment> list) { + for (FrameworkMethod dataPointMethod : getSingleDataPointMethods(sig)) { + if (sig.canAcceptType(dataPointMethod.getType())) { + list.add(new MethodParameterValue(dataPointMethod)); + } + } + } + + private void addMultiPointFields(ParameterSignature sig, List<PotentialAssignment> list) { + for (final Field field : getDataPointsFields(sig)) { + Class<?> type = field.getType(); + addDataPointsValues(type, sig, field.getName(), list, getStaticFieldValue(field)); + } + } + + private void addSinglePointFields(ParameterSignature sig, List<PotentialAssignment> list) { + for (final Field field : getSingleDataPointFields(sig)) { + Object value = getStaticFieldValue(field); + + if (sig.canAcceptValue(value)) { + list.add(PotentialAssignment.forValue(field.getName(), value)); + } + } + } + + private void addDataPointsValues(Class<?> type, ParameterSignature sig, String name, + List<PotentialAssignment> list, Object value) { + if (type.isArray()) { + addArrayValues(sig, name, list, value); + } + else if (Iterable.class.isAssignableFrom(type)) { + addIterableValues(sig, name, list, (Iterable<?>) value); + } + } + + private void addArrayValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Object array) { + for (int i = 0; i < Array.getLength(array); i++) { + Object value = Array.get(array, i); + if (sig.canAcceptValue(value)) { + list.add(PotentialAssignment.forValue(name + "[" + i + "]", value)); + } + } + } + + private void addIterableValues(ParameterSignature sig, String name, List<PotentialAssignment> list, Iterable<?> iterable) { + Iterator<?> iterator = iterable.iterator(); + int i = 0; + while (iterator.hasNext()) { + Object value = iterator.next(); + if (sig.canAcceptValue(value)) { + list.add(PotentialAssignment.forValue(name + "[" + i + "]", value)); + } + i += 1; + } + } + + private Object getStaticFieldValue(final Field field) { + try { + return field.get(null); + } catch (IllegalArgumentException e) { + throw new RuntimeException( + "unexpected: field from getClass doesn't exist on object"); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "unexpected: getFields returned an inaccessible field"); + } + } + + private static boolean isAssignableToAnyOf(Class<?>[] typeArray, Object target) { + for (Class<?> type : typeArray) { + if (type.isAssignableFrom(target.getClass())) { + return true; + } + } + return false; + } + + protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) { + return clazz.getAnnotatedMethods(DataPoints.class); + } + + protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) { + List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoint.class); + Collection<Field> validFields = new ArrayList<Field>(); + + for (FrameworkField frameworkField : fields) { + validFields.add(frameworkField.getField()); + } + + return validFields; + } + + protected Collection<Field> getDataPointsFields(ParameterSignature sig) { + List<FrameworkField> fields = clazz.getAnnotatedFields(DataPoints.class); + Collection<Field> validFields = new ArrayList<Field>(); + + for (FrameworkField frameworkField : fields) { + validFields.add(frameworkField.getField()); + } + + return validFields; + } + + protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) { + return clazz.getAnnotatedMethods(DataPoint.class); + } + }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/internal/Assignments.java b/src/main/java/org/junit/experimental/theories/internal/Assignments.java index bd94f00..a94c8a5 100644 --- a/src/main/java/org/junit/experimental/theories/internal/Assignments.java +++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java @@ -1,8 +1,8 @@ -/** - * - */ package org.junit.experimental.theories.internal; +import static java.util.Collections.emptyList; + +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -19,115 +19,136 @@ import org.junit.runners.model.TestClass; * parameters */ public class Assignments { - private List<PotentialAssignment> fAssigned; - - private final List<ParameterSignature> fUnassigned; - - private final TestClass fClass; - - private Assignments(List<PotentialAssignment> assigned, - List<ParameterSignature> unassigned, TestClass testClass) { - fUnassigned= unassigned; - fAssigned= assigned; - fClass= testClass; - } - - /** - * Returns a new assignment list for {@code testMethod}, with no params - * assigned. - */ - public static Assignments allUnassigned(Method testMethod, - TestClass testClass) throws Exception { - List<ParameterSignature> signatures; - signatures= ParameterSignature.signatures(testClass - .getOnlyConstructor()); - signatures.addAll(ParameterSignature.signatures(testMethod)); - return new Assignments(new ArrayList<PotentialAssignment>(), - signatures, testClass); - } - - public boolean isComplete() { - return fUnassigned.size() == 0; - } - - public ParameterSignature nextUnassigned() { - return fUnassigned.get(0); - } - - public Assignments assignNext(PotentialAssignment source) { - List<PotentialAssignment> assigned= new ArrayList<PotentialAssignment>( - fAssigned); - assigned.add(source); - - return new Assignments(assigned, fUnassigned.subList(1, fUnassigned - .size()), fClass); - } - - public Object[] getActualValues(int start, int stop, boolean nullsOk) - throws CouldNotGenerateValueException { - Object[] values= new Object[stop - start]; - for (int i= start; i < stop; i++) { - Object value= fAssigned.get(i).getValue(); - if (value == null && !nullsOk) - throw new CouldNotGenerateValueException(); - values[i - start]= value; - } - return values; - } - - public List<PotentialAssignment> potentialsForNextUnassigned() - throws InstantiationException, IllegalAccessException { - ParameterSignature unassigned= nextUnassigned(); - return getSupplier(unassigned).getValueSources(unassigned); - } - - public ParameterSupplier getSupplier(ParameterSignature unassigned) - throws InstantiationException, IllegalAccessException { - ParameterSupplier supplier= getAnnotatedSupplier(unassigned); - if (supplier != null) - return supplier; - - return new AllMembersSupplier(fClass); - } - - public ParameterSupplier getAnnotatedSupplier(ParameterSignature unassigned) - throws InstantiationException, IllegalAccessException { - ParametersSuppliedBy annotation= unassigned - .findDeepAnnotation(ParametersSuppliedBy.class); - if (annotation == null) - return null; - return annotation.value().newInstance(); - } - - public Object[] getConstructorArguments(boolean nullsOk) - throws CouldNotGenerateValueException { - return getActualValues(0, getConstructorParameterCount(), nullsOk); - } - - public Object[] getMethodArguments(boolean nullsOk) - throws CouldNotGenerateValueException { - return getActualValues(getConstructorParameterCount(), - fAssigned.size(), nullsOk); - } - - public Object[] getAllArguments(boolean nullsOk) - throws CouldNotGenerateValueException { - return getActualValues(0, fAssigned.size(), nullsOk); - } - - private int getConstructorParameterCount() { - List<ParameterSignature> signatures= ParameterSignature - .signatures(fClass.getOnlyConstructor()); - int constructorParameterCount= signatures.size(); - return constructorParameterCount; - } - - public Object[] getArgumentStrings(boolean nullsOk) - throws CouldNotGenerateValueException { - Object[] values= new Object[fAssigned.size()]; - for (int i= 0; i < values.length; i++) { - values[i]= fAssigned.get(i).getDescription(); - } - return values; - } + private final List<PotentialAssignment> assigned; + + private final List<ParameterSignature> unassigned; + + private final TestClass clazz; + + private Assignments(List<PotentialAssignment> assigned, + List<ParameterSignature> unassigned, TestClass clazz) { + this.unassigned = unassigned; + this.assigned = assigned; + this.clazz = clazz; + } + + /** + * Returns a new assignment list for {@code testMethod}, with no params + * assigned. + */ + public static Assignments allUnassigned(Method testMethod, + TestClass testClass) { + List<ParameterSignature> signatures; + signatures = ParameterSignature.signatures(testClass + .getOnlyConstructor()); + signatures.addAll(ParameterSignature.signatures(testMethod)); + return new Assignments(new ArrayList<PotentialAssignment>(), + signatures, testClass); + } + + public boolean isComplete() { + return unassigned.size() == 0; + } + + public ParameterSignature nextUnassigned() { + return unassigned.get(0); + } + + public Assignments assignNext(PotentialAssignment source) { + List<PotentialAssignment> assigned = new ArrayList<PotentialAssignment>( + this.assigned); + assigned.add(source); + + return new Assignments(assigned, unassigned.subList(1, + unassigned.size()), clazz); + } + + public Object[] getActualValues(int start, int stop) + throws CouldNotGenerateValueException { + Object[] values = new Object[stop - start]; + for (int i = start; i < stop; i++) { + values[i - start] = assigned.get(i).getValue(); + } + return values; + } + + public List<PotentialAssignment> potentialsForNextUnassigned() + throws Throwable { + ParameterSignature unassigned = nextUnassigned(); + List<PotentialAssignment> assignments = getSupplier(unassigned).getValueSources(unassigned); + + if (assignments.size() == 0) { + assignments = generateAssignmentsFromTypeAlone(unassigned); + } + + return assignments; + } + + private List<PotentialAssignment> generateAssignmentsFromTypeAlone(ParameterSignature unassigned) { + Class<?> paramType = unassigned.getType(); + + if (paramType.isEnum()) { + return new EnumSupplier(paramType).getValueSources(unassigned); + } else if (paramType.equals(Boolean.class) || paramType.equals(boolean.class)) { + return new BooleanSupplier().getValueSources(unassigned); + } else { + return emptyList(); + } + } + + private ParameterSupplier getSupplier(ParameterSignature unassigned) + throws Exception { + ParametersSuppliedBy annotation = unassigned + .findDeepAnnotation(ParametersSuppliedBy.class); + + if (annotation != null) { + return buildParameterSupplierFromClass(annotation.value()); + } else { + return new AllMembersSupplier(clazz); + } + } + + private ParameterSupplier buildParameterSupplierFromClass( + Class<? extends ParameterSupplier> cls) throws Exception { + Constructor<?>[] supplierConstructors = cls.getConstructors(); + + for (Constructor<?> constructor : supplierConstructors) { + Class<?>[] parameterTypes = constructor.getParameterTypes(); + if (parameterTypes.length == 1 + && parameterTypes[0].equals(TestClass.class)) { + return (ParameterSupplier) constructor.newInstance(clazz); + } + } + + return cls.newInstance(); + } + + public Object[] getConstructorArguments() + throws CouldNotGenerateValueException { + return getActualValues(0, getConstructorParameterCount()); + } + + public Object[] getMethodArguments() throws CouldNotGenerateValueException { + return getActualValues(getConstructorParameterCount(), assigned.size()); + } + + public Object[] getAllArguments() throws CouldNotGenerateValueException { + return getActualValues(0, assigned.size()); + } + + private int getConstructorParameterCount() { + List<ParameterSignature> signatures = ParameterSignature + .signatures(clazz.getOnlyConstructor()); + int constructorParameterCount = signatures.size(); + return constructorParameterCount; + } + + public Object[] getArgumentStrings(boolean nullsOk) + throws CouldNotGenerateValueException { + Object[] values = new Object[assigned.size()]; + for (int i = 0; i < values.length; i++) { + values[i] = assigned.get(i).getDescription(); + } + return values; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java new file mode 100644 index 0000000..5f7032f --- /dev/null +++ b/src/main/java/org/junit/experimental/theories/internal/BooleanSupplier.java @@ -0,0 +1,18 @@ +package org.junit.experimental.theories.internal; + +import java.util.Arrays; +import java.util.List; + +import org.junit.experimental.theories.ParameterSignature; +import org.junit.experimental.theories.ParameterSupplier; +import org.junit.experimental.theories.PotentialAssignment; + +public class BooleanSupplier extends ParameterSupplier { + + @Override + public List<PotentialAssignment> getValueSources(ParameterSignature sig) { + return Arrays.asList(PotentialAssignment.forValue("true", true), + PotentialAssignment.forValue("false", false)); + } + +} diff --git a/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java new file mode 100644 index 0000000..1f3ab90 --- /dev/null +++ b/src/main/java/org/junit/experimental/theories/internal/EnumSupplier.java @@ -0,0 +1,30 @@ +package org.junit.experimental.theories.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.experimental.theories.ParameterSignature; +import org.junit.experimental.theories.ParameterSupplier; +import org.junit.experimental.theories.PotentialAssignment; + +public class EnumSupplier extends ParameterSupplier { + + private Class<?> enumType; + + public EnumSupplier(Class<?> enumType) { + this.enumType = enumType; + } + + @Override + public List<PotentialAssignment> getValueSources(ParameterSignature sig) { + Object[] enumValues = enumType.getEnumConstants(); + + List<PotentialAssignment> assignments = new ArrayList<PotentialAssignment>(); + for (Object value : enumValues) { + assignments.add(PotentialAssignment.forValue(value.toString(), value)); + } + + return assignments; + } + +} diff --git a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java index 285bc3a..5b9e947 100644 --- a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java +++ b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java @@ -1,49 +1,50 @@ -/** - * - */ package org.junit.experimental.theories.internal; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; - -public class ParameterizedAssertionError extends RuntimeException { - private static final long serialVersionUID = 1L; - - public ParameterizedAssertionError(Throwable targetException, - String methodName, Object... params) { - super(String.format("%s(%s)", methodName, join(", ", params)), - targetException); - } - - @Override public boolean equals(Object obj) { - return toString().equals(obj.toString()); - } - - public static String join(String delimiter, Object... params) { - return join(delimiter, Arrays.asList(params)); - } - - public static String join(String delimiter, - Collection<Object> values) { - StringBuffer buffer = new StringBuffer(); - Iterator<Object> iter = values.iterator(); - while (iter.hasNext()) { - Object next = iter.next(); - buffer.append(stringValueOf(next)); - if (iter.hasNext()) { - buffer.append(delimiter); - } - } - return buffer.toString(); - } - - private static String stringValueOf(Object next) { - try { - return String.valueOf(next); - } catch (Throwable e) { - return "[toString failed]"; - } - } +public class ParameterizedAssertionError extends AssertionError { + private static final long serialVersionUID = 1L; + + public ParameterizedAssertionError(Throwable targetException, + String methodName, Object... params) { + super(String.format("%s(%s)", methodName, join(", ", params))); + this.initCause(targetException); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ParameterizedAssertionError && toString().equals(obj.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public static String join(String delimiter, Object... params) { + return join(delimiter, Arrays.asList(params)); + } + + public static String join(String delimiter, Collection<Object> values) { + StringBuilder sb = new StringBuilder(); + Iterator<Object> iter = values.iterator(); + while (iter.hasNext()) { + Object next = iter.next(); + sb.append(stringValueOf(next)); + if (iter.hasNext()) { + sb.append(delimiter); + } + } + return sb.toString(); + } + + private static String stringValueOf(Object next) { + try { + return String.valueOf(next); + } catch (Throwable e) { + return "[toString failed]"; + } + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java new file mode 100644 index 0000000..7b571e3 --- /dev/null +++ b/src/main/java/org/junit/experimental/theories/internal/SpecificDataPointsSupplier.java @@ -0,0 +1,90 @@ +package org.junit.experimental.theories.internal; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.junit.experimental.theories.DataPoint; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.FromDataPoints; +import org.junit.experimental.theories.ParameterSignature; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +public class SpecificDataPointsSupplier extends AllMembersSupplier { + + public SpecificDataPointsSupplier(TestClass testClass) { + super(testClass); + } + + @Override + protected Collection<Field> getSingleDataPointFields(ParameterSignature sig) { + Collection<Field> fields = super.getSingleDataPointFields(sig); + String requestedName = sig.getAnnotation(FromDataPoints.class).value(); + + List<Field> fieldsWithMatchingNames = new ArrayList<Field>(); + + for (Field field : fields) { + String[] fieldNames = field.getAnnotation(DataPoint.class).value(); + if (Arrays.asList(fieldNames).contains(requestedName)) { + fieldsWithMatchingNames.add(field); + } + } + + return fieldsWithMatchingNames; + } + + @Override + protected Collection<Field> getDataPointsFields(ParameterSignature sig) { + Collection<Field> fields = super.getDataPointsFields(sig); + String requestedName = sig.getAnnotation(FromDataPoints.class).value(); + + List<Field> fieldsWithMatchingNames = new ArrayList<Field>(); + + for (Field field : fields) { + String[] fieldNames = field.getAnnotation(DataPoints.class).value(); + if (Arrays.asList(fieldNames).contains(requestedName)) { + fieldsWithMatchingNames.add(field); + } + } + + return fieldsWithMatchingNames; + } + + @Override + protected Collection<FrameworkMethod> getSingleDataPointMethods(ParameterSignature sig) { + Collection<FrameworkMethod> methods = super.getSingleDataPointMethods(sig); + String requestedName = sig.getAnnotation(FromDataPoints.class).value(); + + List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>(); + + for (FrameworkMethod method : methods) { + String[] methodNames = method.getAnnotation(DataPoint.class).value(); + if (Arrays.asList(methodNames).contains(requestedName)) { + methodsWithMatchingNames.add(method); + } + } + + return methodsWithMatchingNames; + } + + @Override + protected Collection<FrameworkMethod> getDataPointsMethods(ParameterSignature sig) { + Collection<FrameworkMethod> methods = super.getDataPointsMethods(sig); + String requestedName = sig.getAnnotation(FromDataPoints.class).value(); + + List<FrameworkMethod> methodsWithMatchingNames = new ArrayList<FrameworkMethod>(); + + for (FrameworkMethod method : methods) { + String[] methodNames = method.getAnnotation(DataPoints.class).value(); + if (Arrays.asList(methodNames).contains(requestedName)) { + methodsWithMatchingNames.add(method); + } + } + + return methodsWithMatchingNames; + } + +} diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java index d6ede64..a19f20a 100644 --- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java +++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java @@ -1,13 +1,31 @@ package org.junit.experimental.theories.suppliers; +import static java.lang.annotation.ElementType.PARAMETER; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.junit.experimental.theories.ParametersSuppliedBy; - +/** + * Annotating a {@link org.junit.experimental.theories.Theory Theory} method int + * parameter with @TestedOn causes it to be supplied with values from the + * ints array given when run as a theory by the + * {@link org.junit.experimental.theories.Theories Theories} runner. For + * example, the below method would be called three times by the Theories runner, + * once with each of the int parameters specified. + * + * <pre> + * @Theory + * public void shouldPassForSomeInts(@TestedOn(ints={1, 2, 3}) int param) { + * ... + * } + * </pre> + */ @ParametersSuppliedBy(TestedOnSupplier.class) @Retention(RetentionPolicy.RUNTIME) +@Target(PARAMETER) public @interface TestedOn { - int[] ints(); + int[] ints(); } diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java index f80298f..dc3d0c9 100644 --- a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java +++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java @@ -1,23 +1,25 @@ package org.junit.experimental.theories.suppliers; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.junit.experimental.theories.ParameterSignature; import org.junit.experimental.theories.ParameterSupplier; import org.junit.experimental.theories.PotentialAssignment; - - +/** + * @see org.junit.experimental.theories.suppliers.TestedOn + * @see org.junit.experimental.theories.ParameterSupplier + */ public class TestedOnSupplier extends ParameterSupplier { - @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) { - List<PotentialAssignment> list = new ArrayList<PotentialAssignment>(); - TestedOn testedOn = sig.getAnnotation(TestedOn.class); - int[] ints = testedOn.ints(); - for (final int i : ints) { - list.add(PotentialAssignment.forValue(Arrays.asList(ints).toString(), i)); - } - return list; - } + @Override + public List<PotentialAssignment> getValueSources(ParameterSignature sig) { + List<PotentialAssignment> list = new ArrayList<PotentialAssignment>(); + TestedOn testedOn = sig.getAnnotation(TestedOn.class); + int[] ints = testedOn.ints(); + for (final int i : ints) { + list.add(PotentialAssignment.forValue("ints", i)); + } + return list; + } } diff --git a/src/main/java/org/junit/internal/ArrayComparisonFailure.java b/src/main/java/org/junit/internal/ArrayComparisonFailure.java index 08851de..8627d6e 100644 --- a/src/main/java/org/junit/internal/ArrayComparisonFailure.java +++ b/src/main/java/org/junit/internal/ArrayComparisonFailure.java @@ -7,53 +7,61 @@ import org.junit.Assert; /** * Thrown when two array elements differ + * * @see Assert#assertArrayEquals(String, Object[], Object[]) */ public class ArrayComparisonFailure extends AssertionError { - private static final long serialVersionUID= 1L; - - private List<Integer> fIndices= new ArrayList<Integer>(); - private final String fMessage; - private final AssertionError fCause; - - /** - * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's - * dimension that was not equal - * @param cause the exception that caused the array's content to fail the assertion test - * @param index the array position of the objects that are not equal. - * @see Assert#assertArrayEquals(String, Object[], Object[]) - */ - public ArrayComparisonFailure(String message, AssertionError cause, int index) { - fMessage= message; - fCause= cause; - addDimension(index); - } - - public void addDimension(int index) { - fIndices.add(0, index); - } - - @Override - public String getMessage() { - StringBuilder builder= new StringBuilder(); - if (fMessage != null) - builder.append(fMessage); - builder.append("arrays first differed at element "); - for (int each : fIndices) { - builder.append("["); - builder.append(each); - builder.append("]"); - } - builder.append("; "); - builder.append(fCause.getMessage()); - return builder.toString(); - } - - /** - * {@inheritDoc} - */ - @Override public String toString() { - return getMessage(); - } + private static final long serialVersionUID = 1L; + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final List<Integer> fIndices = new ArrayList<Integer>(); + private final String fMessage; + + /** + * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's + * dimension that was not equal + * + * @param cause the exception that caused the array's content to fail the assertion test + * @param index the array position of the objects that are not equal. + * @see Assert#assertArrayEquals(String, Object[], Object[]) + */ + public ArrayComparisonFailure(String message, AssertionError cause, int index) { + this.fMessage = message; + initCause(cause); + addDimension(index); + } + + public void addDimension(int index) { + fIndices.add(0, index); + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(); + if (fMessage != null) { + sb.append(fMessage); + } + sb.append("arrays first differed at element "); + for (int each : fIndices) { + sb.append("["); + sb.append(each); + sb.append("]"); + } + sb.append("; "); + sb.append(getCause().getMessage()); + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getMessage(); + } } diff --git a/src/main/java/org/junit/internal/AssumptionViolatedException.java b/src/main/java/org/junit/internal/AssumptionViolatedException.java index 8e11268..880d73f 100644 --- a/src/main/java/org/junit/internal/AssumptionViolatedException.java +++ b/src/main/java/org/junit/internal/AssumptionViolatedException.java @@ -5,36 +5,107 @@ import org.hamcrest.Matcher; import org.hamcrest.SelfDescribing; import org.hamcrest.StringDescription; +/** + * An exception class used to implement <i>assumptions</i> (state in which a given test + * is meaningful and should or should not be executed). A test for which an assumption + * fails should not generate a test case failure. + * + * @see org.junit.Assume + */ public class AssumptionViolatedException extends RuntimeException implements SelfDescribing { - private static final long serialVersionUID= 1L; - - private final Object fValue; - - private final Matcher<?> fMatcher; - - public AssumptionViolatedException(Object value, Matcher<?> matcher) { - super(value instanceof Throwable ? (Throwable) value : null); - fValue= value; - fMatcher= matcher; - } - - public AssumptionViolatedException(String assumption) { - this(assumption, null); - } - - @Override - public String getMessage() { - return StringDescription.asString(this); - } - - public void describeTo(Description description) { - if (fMatcher != null) { - description.appendText("got: "); - description.appendValue(fValue); - description.appendText(", expected: "); - description.appendDescriptionOf(fMatcher); - } else { - description.appendText("failed assumption: " + fValue); - } - } -}
\ No newline at end of file + private static final long serialVersionUID = 2L; + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final String fAssumption; + private final boolean fValueMatcher; + private final Object fValue; + private final Matcher<?> fMatcher; + + /** + * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead. + */ + @Deprecated + public AssumptionViolatedException(String assumption, boolean hasValue, Object value, Matcher<?> matcher) { + this.fAssumption = assumption; + this.fValue = value; + this.fMatcher = matcher; + this.fValueMatcher = hasValue; + + if (value instanceof Throwable) { + initCause((Throwable) value); + } + } + + /** + * An assumption exception with the given <i>value</i> (String or + * Throwable) and an additional failing {@link Matcher}. + * + * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead. + */ + @Deprecated + public AssumptionViolatedException(Object value, Matcher<?> matcher) { + this(null, true, value, matcher); + } + + /** + * An assumption exception with the given <i>value</i> (String or + * Throwable) and an additional failing {@link Matcher}. + * + * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead. + */ + @Deprecated + public AssumptionViolatedException(String assumption, Object value, Matcher<?> matcher) { + this(assumption, true, value, matcher); + } + + /** + * An assumption exception with the given message only. + * + * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead. + */ + @Deprecated + public AssumptionViolatedException(String assumption) { + this(assumption, false, null, null); + } + + /** + * An assumption exception with the given message and a cause. + * + * @deprecated Please use {@link org.junit.AssumptionViolatedException} instead. + */ + @Deprecated + public AssumptionViolatedException(String assumption, Throwable e) { + this(assumption, false, null, null); + initCause(e); + } + + @Override + public String getMessage() { + return StringDescription.asString(this); + } + + public void describeTo(Description description) { + if (fAssumption != null) { + description.appendText(fAssumption); + } + + if (fValueMatcher) { + // a value was passed in when this instance was constructed; print it + if (fAssumption != null) { + description.appendText(": "); + } + + description.appendText("got: "); + description.appendValue(fValue); + + if (fMatcher != null) { + description.appendText(", expected: "); + description.appendDescriptionOf(fMatcher); + } + } + } +} diff --git a/src/main/java/org/junit/internal/Classes.java b/src/main/java/org/junit/internal/Classes.java new file mode 100644 index 0000000..154603d --- /dev/null +++ b/src/main/java/org/junit/internal/Classes.java @@ -0,0 +1,18 @@ +package org.junit.internal;
+
+import static java.lang.Thread.currentThread;
+
+/**
+ * Miscellaneous functions dealing with classes.
+ */
+public class Classes {
+ /**
+ * Returns Class.forName for {@code className} using the current thread's class loader.
+ *
+ * @param className Name of the class.
+ * @throws ClassNotFoundException
+ */
+ public static Class<?> getClass(String className) throws ClassNotFoundException {
+ return Class.forName(className, true, currentThread().getContextClassLoader());
+ }
+}
diff --git a/src/main/java/org/junit/internal/ComparisonCriteria.java b/src/main/java/org/junit/internal/ComparisonCriteria.java index e97011d..e6d49a4 100644 --- a/src/main/java/org/junit/internal/ComparisonCriteria.java +++ b/src/main/java/org/junit/internal/ComparisonCriteria.java @@ -1,6 +1,7 @@ package org.junit.internal; import java.lang.reflect.Array; +import java.util.Arrays; import org.junit.Assert; @@ -9,68 +10,74 @@ import org.junit.Assert; * may demand exact equality, or, for example, equality within a given delta. */ public abstract class ComparisonCriteria { - /** - * Asserts that two arrays are equal, according to the criteria defined by - * the concrete subclass. If they are not, an {@link AssertionError} is - * thrown with the given message. If <code>expecteds</code> and - * <code>actuals</code> are <code>null</code>, they are considered equal. - * - * @param message - * the identifying message for the {@link AssertionError} ( - * <code>null</code> okay) - * @param expecteds - * Object array or array of arrays (multi-dimensional array) with - * expected values. - * @param actuals - * Object array or array of arrays (multi-dimensional array) with - * actual values - */ - public void arrayEquals(String message, Object expecteds, Object actuals) - throws ArrayComparisonFailure { - if (expecteds == actuals) - return; - String header= message == null ? "" : message + ": "; + /** + * Asserts that two arrays are equal, according to the criteria defined by + * the concrete subclass. If they are not, an {@link AssertionError} is + * thrown with the given message. If <code>expecteds</code> and + * <code>actuals</code> are <code>null</code>, they are considered equal. + * + * @param message the identifying message for the {@link AssertionError} ( + * <code>null</code> okay) + * @param expecteds Object array or array of arrays (multi-dimensional array) with + * expected values. + * @param actuals Object array or array of arrays (multi-dimensional array) with + * actual values + */ + public void arrayEquals(String message, Object expecteds, Object actuals) + throws ArrayComparisonFailure { + if (expecteds == actuals + || Arrays.deepEquals(new Object[] {expecteds}, new Object[] {actuals})) { + // The reflection-based loop below is potentially very slow, especially for primitive + // arrays. The deepEquals check allows us to circumvent it in the usual case where + // the arrays are exactly equal. + return; + } + String header = message == null ? "" : message + ": "; - int expectedsLength= assertArraysAreSameLength(expecteds, - actuals, header); + int expectedsLength = assertArraysAreSameLength(expecteds, + actuals, header); - for (int i= 0; i < expectedsLength; i++) { - Object expected= Array.get(expecteds, i); - Object actual= Array.get(actuals, i); + for (int i = 0; i < expectedsLength; i++) { + Object expected = Array.get(expecteds, i); + Object actual = Array.get(actuals, i); - if (isArray(expected) && isArray(actual)) { - try { - arrayEquals(message, expected, actual); - } catch (ArrayComparisonFailure e) { - e.addDimension(i); - throw e; - } - } else - try { - assertElementsEqual(expected, actual); - } catch (AssertionError e) { - throw new ArrayComparisonFailure(header, e, i); - } - } - } + if (isArray(expected) && isArray(actual)) { + try { + arrayEquals(message, expected, actual); + } catch (ArrayComparisonFailure e) { + e.addDimension(i); + throw e; + } + } else { + try { + assertElementsEqual(expected, actual); + } catch (AssertionError e) { + throw new ArrayComparisonFailure(header, e, i); + } + } + } + } - private boolean isArray(Object expected) { - return expected != null && expected.getClass().isArray(); - } + private boolean isArray(Object expected) { + return expected != null && expected.getClass().isArray(); + } - private int assertArraysAreSameLength(Object expecteds, - Object actuals, String header) { - if (expecteds == null) - Assert.fail(header + "expected array was null"); - if (actuals == null) - Assert.fail(header + "actual array was null"); - int actualsLength= Array.getLength(actuals); - int expectedsLength= Array.getLength(expecteds); - if (actualsLength != expectedsLength) - Assert.fail(header + "array lengths differed, expected.length=" - + expectedsLength + " actual.length=" + actualsLength); - return expectedsLength; - } + private int assertArraysAreSameLength(Object expecteds, + Object actuals, String header) { + if (expecteds == null) { + Assert.fail(header + "expected array was null"); + } + if (actuals == null) { + Assert.fail(header + "actual array was null"); + } + int actualsLength = Array.getLength(actuals); + int expectedsLength = Array.getLength(expecteds); + if (actualsLength != expectedsLength) { + Assert.fail(header + "array lengths differed, expected.length=" + + expectedsLength + " actual.length=" + actualsLength); + } + return expectedsLength; + } - protected abstract void assertElementsEqual(Object expected, Object actual); + protected abstract void assertElementsEqual(Object expected, Object actual); } diff --git a/src/main/java/org/junit/internal/ExactComparisonCriteria.java b/src/main/java/org/junit/internal/ExactComparisonCriteria.java index 0a632ff..a267f7f 100644 --- a/src/main/java/org/junit/internal/ExactComparisonCriteria.java +++ b/src/main/java/org/junit/internal/ExactComparisonCriteria.java @@ -3,8 +3,8 @@ package org.junit.internal; import org.junit.Assert; public class ExactComparisonCriteria extends ComparisonCriteria { - @Override - protected void assertElementsEqual(Object expected, Object actual) { - Assert.assertEquals(expected, actual); - } + @Override + protected void assertElementsEqual(Object expected, Object actual) { + Assert.assertEquals(expected, actual); + } } diff --git a/src/main/java/org/junit/internal/InexactComparisonCriteria.java b/src/main/java/org/junit/internal/InexactComparisonCriteria.java index ef3d7ff..16e804b 100644 --- a/src/main/java/org/junit/internal/InexactComparisonCriteria.java +++ b/src/main/java/org/junit/internal/InexactComparisonCriteria.java @@ -3,17 +3,22 @@ package org.junit.internal; import org.junit.Assert; public class InexactComparisonCriteria extends ComparisonCriteria { - public double fDelta; + public Object fDelta; - public InexactComparisonCriteria(double delta) { - fDelta= delta; - } + public InexactComparisonCriteria(double delta) { + fDelta = delta; + } - @Override - protected void assertElementsEqual(Object expected, Object actual) { - if (expected instanceof Double) - Assert.assertEquals((Double)expected, (Double)actual, fDelta); - else - Assert.assertEquals((Float)expected, (Float)actual, fDelta); - } + public InexactComparisonCriteria(float delta) { + fDelta = delta; + } + + @Override + protected void assertElementsEqual(Object expected, Object actual) { + if (expected instanceof Double) { + Assert.assertEquals((Double) expected, (Double) actual, (Double) fDelta); + } else { + Assert.assertEquals((Float) expected, (Float) actual, (Float) fDelta); + } + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/JUnitSystem.java b/src/main/java/org/junit/internal/JUnitSystem.java index 6d9c242..cf0f2c0 100644 --- a/src/main/java/org/junit/internal/JUnitSystem.java +++ b/src/main/java/org/junit/internal/JUnitSystem.java @@ -3,6 +3,12 @@ package org.junit.internal; import java.io.PrintStream; public interface JUnitSystem { - void exit(int i); - PrintStream out(); + + /** + * Will be removed in the next major release + */ + @Deprecated + void exit(int code); + + PrintStream out(); } diff --git a/src/main/java/org/junit/internal/MethodSorter.java b/src/main/java/org/junit/internal/MethodSorter.java new file mode 100644 index 0000000..d8e661a --- /dev/null +++ b/src/main/java/org/junit/internal/MethodSorter.java @@ -0,0 +1,72 @@ +package org.junit.internal; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; + +import org.junit.FixMethodOrder; + +public class MethodSorter { + /** + * DEFAULT sort order + */ + public static final Comparator<Method> DEFAULT = new Comparator<Method>() { + public int compare(Method m1, Method m2) { + int i1 = m1.getName().hashCode(); + int i2 = m2.getName().hashCode(); + if (i1 != i2) { + return i1 < i2 ? -1 : 1; + } + return NAME_ASCENDING.compare(m1, m2); + } + }; + + /** + * Method name ascending lexicographic sort order, with {@link Method#toString()} as a tiebreaker + */ + public static final Comparator<Method> NAME_ASCENDING = new Comparator<Method>() { + public int compare(Method m1, Method m2) { + final int comparison = m1.getName().compareTo(m2.getName()); + if (comparison != 0) { + return comparison; + } + return m1.toString().compareTo(m2.toString()); + } + }; + + /** + * Gets declared methods of a class in a predictable order, unless @FixMethodOrder(MethodSorters.JVM) is specified. + * + * Using the JVM order is unwise since the Java platform does not + * specify any particular order, and in fact JDK 7 returns a more or less + * random order; well-written test code would not assume any order, but some + * does, and a predictable failure is better than a random failure on + * certain platforms. By default, uses an unspecified but deterministic order. + * + * @param clazz a class + * @return same as {@link Class#getDeclaredMethods} but sorted + * @see <a href="http://bugs.sun.com/view_bug.do?bug_id=7023180">JDK + * (non-)bug #7023180</a> + */ + public static Method[] getDeclaredMethods(Class<?> clazz) { + Comparator<Method> comparator = getSorter(clazz.getAnnotation(FixMethodOrder.class)); + + Method[] methods = clazz.getDeclaredMethods(); + if (comparator != null) { + Arrays.sort(methods, comparator); + } + + return methods; + } + + private MethodSorter() { + } + + private static Comparator<Method> getSorter(FixMethodOrder fixMethodOrder) { + if (fixMethodOrder == null) { + return DEFAULT; + } + + return fixMethodOrder.value().getComparator(); + } +} diff --git a/src/main/java/org/junit/internal/RealSystem.java b/src/main/java/org/junit/internal/RealSystem.java index 1067c6d..e64e1fe 100644 --- a/src/main/java/org/junit/internal/RealSystem.java +++ b/src/main/java/org/junit/internal/RealSystem.java @@ -4,12 +4,16 @@ import java.io.PrintStream; public class RealSystem implements JUnitSystem { - public void exit(int code) { - System.exit(code); - } + /** + * Will be removed in the next major release + */ + @Deprecated + public void exit(int code) { + System.exit(code); + } - public PrintStream out() { - return System.out; - } + public PrintStream out() { + return System.out; + } } diff --git a/src/main/java/org/junit/internal/TextListener.java b/src/main/java/org/junit/internal/TextListener.java index 2b1c679..9aa56c7 100644 --- a/src/main/java/org/junit/internal/TextListener.java +++ b/src/main/java/org/junit/internal/TextListener.java @@ -11,88 +11,91 @@ import org.junit.runner.notification.RunListener; public class TextListener extends RunListener { - private final PrintStream fWriter; - - public TextListener(JUnitSystem system) { - this(system.out()); - } - - public TextListener(PrintStream writer) { - this.fWriter= writer; - } - - @Override - public void testRunFinished(Result result) { - printHeader(result.getRunTime()); - printFailures(result); - printFooter(result); - } - - @Override - public void testStarted(Description description) { - fWriter.append('.'); - } - - @Override - public void testFailure(Failure failure) { - fWriter.append('E'); - } - - @Override - public void testIgnored(Description description) { - fWriter.append('I'); - } - - /* - * Internal methods - */ - - private PrintStream getWriter() { - return fWriter; - } - - protected void printHeader(long runTime) { - getWriter().println(); - getWriter().println("Time: " + elapsedTimeAsString(runTime)); - } - - protected void printFailures(Result result) { - List<Failure> failures= result.getFailures(); - if (failures.size() == 0) - return; - if (failures.size() == 1) - getWriter().println("There was " + failures.size() + " failure:"); - else - getWriter().println("There were " + failures.size() + " failures:"); - int i= 1; - for (Failure each : failures) - printFailure(each, "" + i++); - } - - protected void printFailure(Failure each, String prefix) { - getWriter().println(prefix + ") " + each.getTestHeader()); - getWriter().print(each.getTrace()); - } - - protected void printFooter(Result result) { - if (result.wasSuccessful()) { - getWriter().println(); - getWriter().print("OK"); - getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")"); - - } else { - getWriter().println(); - getWriter().println("FAILURES!!!"); - getWriter().println("Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount()); - } - getWriter().println(); - } - - /** - * Returns the formatted string of the elapsed time. Duplicated from - * BaseTestRunner. Fix it. - */ - protected String elapsedTimeAsString(long runTime) { - return NumberFormat.getInstance().format((double) runTime / 1000); - } + private final PrintStream writer; + + public TextListener(JUnitSystem system) { + this(system.out()); + } + + public TextListener(PrintStream writer) { + this.writer = writer; + } + + @Override + public void testRunFinished(Result result) { + printHeader(result.getRunTime()); + printFailures(result); + printFooter(result); + } + + @Override + public void testStarted(Description description) { + writer.append('.'); + } + + @Override + public void testFailure(Failure failure) { + writer.append('E'); + } + + @Override + public void testIgnored(Description description) { + writer.append('I'); + } + + /* + * Internal methods + */ + + private PrintStream getWriter() { + return writer; + } + + protected void printHeader(long runTime) { + getWriter().println(); + getWriter().println("Time: " + elapsedTimeAsString(runTime)); + } + + protected void printFailures(Result result) { + List<Failure> failures = result.getFailures(); + if (failures.size() == 0) { + return; + } + if (failures.size() == 1) { + getWriter().println("There was " + failures.size() + " failure:"); + } else { + getWriter().println("There were " + failures.size() + " failures:"); + } + int i = 1; + for (Failure each : failures) { + printFailure(each, "" + i++); + } + } + + protected void printFailure(Failure each, String prefix) { + getWriter().println(prefix + ") " + each.getTestHeader()); + getWriter().print(each.getTrace()); + } + + protected void printFooter(Result result) { + if (result.wasSuccessful()) { + getWriter().println(); + getWriter().print("OK"); + getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")"); + + } else { + getWriter().println(); + getWriter().println("FAILURES!!!"); + getWriter().println("Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount()); + } + getWriter().println(); + } + + /** + * Returns the formatted string of the elapsed time. Duplicated from + * BaseTestRunner. Fix it. + */ + protected String elapsedTimeAsString(long runTime) { + return NumberFormat.getInstance().format((double) runTime / 1000); + } } diff --git a/src/main/java/org/junit/internal/Throwables.java b/src/main/java/org/junit/internal/Throwables.java new file mode 100644 index 0000000..86dceef --- /dev/null +++ b/src/main/java/org/junit/internal/Throwables.java @@ -0,0 +1,42 @@ +package org.junit.internal; + +/** + * Miscellaneous functions dealing with {@code Throwable}. + * + * @author kcooney@google.com (Kevin Cooney) + * @since 4.12 + */ +public final class Throwables { + + private Throwables() { + } + + /** + * Rethrows the given {@code Throwable}, allowing the caller to + * declare that it throws {@code Exception}. This is useful when + * your callers have nothing reasonable they can do when a + * {@code Throwable} is thrown. This is declared to return {@code Exception} + * so it can be used in a {@code throw} clause: + * <pre> + * try { + * doSomething(); + * } catch (Throwable e} { + * throw Throwables.rethrowAsException(e); + * } + * doSomethingLater(); + * </pre> + * + * @param e exception to rethrow + * @return does not return anything + * @since 4.12 + */ + public static Exception rethrowAsException(Throwable e) throws Exception { + Throwables.<Exception>rethrow(e); + return null; // we never get here + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void rethrow(Throwable e) throws T { + throw (T) e; + } +} diff --git a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java index d3bd50a..d86ec95 100644 --- a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java +++ b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import java.util.Arrays; @@ -10,48 +7,50 @@ import org.junit.runner.Runner; import org.junit.runners.model.RunnerBuilder; public class AllDefaultPossibilitiesBuilder extends RunnerBuilder { - private final boolean fCanUseSuiteMethod; - - public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) { - fCanUseSuiteMethod= canUseSuiteMethod; - } - - @Override - public Runner runnerForClass(Class<?> testClass) throws Throwable { - List<RunnerBuilder> builders= Arrays.asList( - ignoredBuilder(), - annotatedBuilder(), - suiteMethodBuilder(), - junit3Builder(), - junit4Builder()); - - for (RunnerBuilder each : builders) { - Runner runner= each.safeRunnerForClass(testClass); - if (runner != null) - return runner; - } - return null; - } - - protected JUnit4Builder junit4Builder() { - return new JUnit4Builder(); - } - - protected JUnit3Builder junit3Builder() { - return new JUnit3Builder(); - } - - protected AnnotatedBuilder annotatedBuilder() { - return new AnnotatedBuilder(this); - } - - protected IgnoredBuilder ignoredBuilder() { - return new IgnoredBuilder(); - } - - protected RunnerBuilder suiteMethodBuilder() { - if (fCanUseSuiteMethod) - return new SuiteMethodBuilder(); - return new NullBuilder(); - } + private final boolean canUseSuiteMethod; + + public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) { + this.canUseSuiteMethod = canUseSuiteMethod; + } + + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + List<RunnerBuilder> builders = Arrays.asList( + ignoredBuilder(), + annotatedBuilder(), + suiteMethodBuilder(), + junit3Builder(), + junit4Builder()); + + for (RunnerBuilder each : builders) { + Runner runner = each.safeRunnerForClass(testClass); + if (runner != null) { + return runner; + } + } + return null; + } + + protected JUnit4Builder junit4Builder() { + return new JUnit4Builder(); + } + + protected JUnit3Builder junit3Builder() { + return new JUnit3Builder(); + } + + protected AnnotatedBuilder annotatedBuilder() { + return new AnnotatedBuilder(this); + } + + protected IgnoredBuilder ignoredBuilder() { + return new IgnoredBuilder(); + } + + protected RunnerBuilder suiteMethodBuilder() { + if (canUseSuiteMethod) { + return new SuiteMethodBuilder(); + } + return new NullBuilder(); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java b/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java index 8ed9ca7..04d7a68 100644 --- a/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java +++ b/src/main/java/org/junit/internal/builders/AnnotatedBuilder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.runner.RunWith; @@ -8,38 +5,112 @@ import org.junit.runner.Runner; import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerBuilder; +import java.lang.reflect.Modifier; + + +/** + * The {@code AnnotatedBuilder} is a strategy for constructing runners for test class that have been annotated with the + * {@code @RunWith} annotation. All tests within this class will be executed using the runner that was specified within + * the annotation. + * <p> + * If a runner supports inner member classes, the member classes will inherit the runner from the enclosing class, e.g.: + * <pre> + * @RunWith(MyRunner.class) + * public class MyTest { + * // some tests might go here + * + * public class MyMemberClass { + * @Test + * public void thisTestRunsWith_MyRunner() { + * // some test logic + * } + * + * // some more tests might go here + * } + * + * @RunWith(AnotherRunner.class) + * public class AnotherMemberClass { + * // some tests might go here + * + * public class DeepInnerClass { + * @Test + * public void thisTestRunsWith_AnotherRunner() { + * // some test logic + * } + * } + * + * public class DeepInheritedClass extends SuperTest { + * @Test + * public void thisTestRunsWith_SuperRunner() { + * // some test logic + * } + * } + * } + * } + * + * @RunWith(SuperRunner.class) + * public class SuperTest { + * // some tests might go here + * } + * </pre> + * The key points to note here are: + * <ul> + * <li>If there is no RunWith annotation, no runner will be created.</li> + * <li>The resolve step is inside-out, e.g. the closest RunWith annotation wins</li> + * <li>RunWith annotations are inherited and work as if the class was annotated itself.</li> + * <li>The default JUnit runner does not support inner member classes, + * so this is only valid for custom runners that support inner member classes.</li> + * <li>Custom runners with support for inner classes may or may not support RunWith annotations for member + * classes. Please refer to the custom runner documentation.</li> + * </ul> + * + * @see org.junit.runners.model.RunnerBuilder + * @see org.junit.runner.RunWith + * @since 4.0 + */ public class AnnotatedBuilder extends RunnerBuilder { - private static final String CONSTRUCTOR_ERROR_FORMAT= "Custom runner class %s should have a public constructor with signature %s(Class testClass)"; - - private RunnerBuilder fSuiteBuilder; - - public AnnotatedBuilder(RunnerBuilder suiteBuilder) { - fSuiteBuilder= suiteBuilder; - } - - @Override - public Runner runnerForClass(Class<?> testClass) throws Exception { - RunWith annotation= testClass.getAnnotation(RunWith.class); - if (annotation != null) - return buildRunner(annotation.value(), testClass); - return null; - } - - public Runner buildRunner(Class<? extends Runner> runnerClass, - Class<?> testClass) throws Exception { - try { - return runnerClass.getConstructor(Class.class).newInstance( - new Object[] { testClass }); - } catch (NoSuchMethodException e) { - try { - return runnerClass.getConstructor(Class.class, - RunnerBuilder.class).newInstance( - new Object[] { testClass, fSuiteBuilder }); - } catch (NoSuchMethodException e2) { - String simpleName= runnerClass.getSimpleName(); - throw new InitializationError(String.format( - CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName)); - } - } - } + private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)"; + + private final RunnerBuilder suiteBuilder; + + public AnnotatedBuilder(RunnerBuilder suiteBuilder) { + this.suiteBuilder = suiteBuilder; + } + + @Override + public Runner runnerForClass(Class<?> testClass) throws Exception { + for (Class<?> currentTestClass = testClass; currentTestClass != null; + currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) { + RunWith annotation = currentTestClass.getAnnotation(RunWith.class); + if (annotation != null) { + return buildRunner(annotation.value(), testClass); + } + } + + return null; + } + + private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) { + if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) { + return currentTestClass.getEnclosingClass(); + } else { + return null; + } + } + + public Runner buildRunner(Class<? extends Runner> runnerClass, + Class<?> testClass) throws Exception { + try { + return runnerClass.getConstructor(Class.class).newInstance(testClass); + } catch (NoSuchMethodException e) { + try { + return runnerClass.getConstructor(Class.class, + RunnerBuilder.class).newInstance(testClass, suiteBuilder); + } catch (NoSuchMethodException e2) { + String simpleName = runnerClass.getSimpleName(); + throw new InitializationError(String.format( + CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName)); + } + } + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/IgnoredBuilder.java b/src/main/java/org/junit/internal/builders/IgnoredBuilder.java index 6be342c..71940c8 100644 --- a/src/main/java/org/junit/internal/builders/IgnoredBuilder.java +++ b/src/main/java/org/junit/internal/builders/IgnoredBuilder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.Ignore; @@ -8,10 +5,11 @@ import org.junit.runner.Runner; import org.junit.runners.model.RunnerBuilder; public class IgnoredBuilder extends RunnerBuilder { - @Override - public Runner runnerForClass(Class<?> testClass) { - if (testClass.getAnnotation(Ignore.class) != null) - return new IgnoredClassRunner(testClass); - return null; - } + @Override + public Runner runnerForClass(Class<?> testClass) { + if (testClass.getAnnotation(Ignore.class) != null) { + return new IgnoredClassRunner(testClass); + } + return null; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java b/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java index b4200a8..7c8926b 100644 --- a/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java +++ b/src/main/java/org/junit/internal/builders/IgnoredClassRunner.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.runner.Description; @@ -8,19 +5,19 @@ import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; public class IgnoredClassRunner extends Runner { - private final Class<?> fTestClass; + private final Class<?> clazz; - public IgnoredClassRunner(Class<?> testClass) { - fTestClass= testClass; - } + public IgnoredClassRunner(Class<?> testClass) { + clazz = testClass; + } - @Override - public void run(RunNotifier notifier) { - notifier.fireTestIgnored(getDescription()); - } + @Override + public void run(RunNotifier notifier) { + notifier.fireTestIgnored(getDescription()); + } - @Override - public Description getDescription() { - return Description.createSuiteDescription(fTestClass); - } + @Override + public Description getDescription() { + return Description.createSuiteDescription(clazz); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/JUnit3Builder.java b/src/main/java/org/junit/internal/builders/JUnit3Builder.java index ddb070b..8b6b371 100644 --- a/src/main/java/org/junit/internal/builders/JUnit3Builder.java +++ b/src/main/java/org/junit/internal/builders/JUnit3Builder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.internal.runners.JUnit38ClassRunner; @@ -8,14 +5,15 @@ import org.junit.runner.Runner; import org.junit.runners.model.RunnerBuilder; public class JUnit3Builder extends RunnerBuilder { - @Override - public Runner runnerForClass(Class<?> testClass) throws Throwable { - if (isPre4Test(testClass)) - return new JUnit38ClassRunner(testClass); - return null; - } + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + if (isPre4Test(testClass)) { + return new JUnit38ClassRunner(testClass); + } + return null; + } - boolean isPre4Test(Class<?> testClass) { - return junit.framework.TestCase.class.isAssignableFrom(testClass); - } + boolean isPre4Test(Class<?> testClass) { + return junit.framework.TestCase.class.isAssignableFrom(testClass); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/JUnit4Builder.java b/src/main/java/org/junit/internal/builders/JUnit4Builder.java index 4380db7..6a00678 100644 --- a/src/main/java/org/junit/internal/builders/JUnit4Builder.java +++ b/src/main/java/org/junit/internal/builders/JUnit4Builder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.runner.Runner; @@ -8,8 +5,8 @@ import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.RunnerBuilder; public class JUnit4Builder extends RunnerBuilder { - @Override - public Runner runnerForClass(Class<?> testClass) throws Throwable { - return new BlockJUnit4ClassRunner(testClass); - } + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + return new BlockJUnit4ClassRunner(testClass); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/NullBuilder.java b/src/main/java/org/junit/internal/builders/NullBuilder.java index 9d43d69..c8d306e 100644 --- a/src/main/java/org/junit/internal/builders/NullBuilder.java +++ b/src/main/java/org/junit/internal/builders/NullBuilder.java @@ -1,14 +1,11 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.runner.Runner; import org.junit.runners.model.RunnerBuilder; public class NullBuilder extends RunnerBuilder { - @Override - public Runner runnerForClass(Class<?> each) throws Throwable { - return null; - } + @Override + public Runner runnerForClass(Class<?> each) throws Throwable { + return null; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java b/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java index 659bf31..953e6cf 100644 --- a/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java +++ b/src/main/java/org/junit/internal/builders/SuiteMethodBuilder.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.builders; import org.junit.internal.runners.SuiteMethod; @@ -8,19 +5,20 @@ import org.junit.runner.Runner; import org.junit.runners.model.RunnerBuilder; public class SuiteMethodBuilder extends RunnerBuilder { - @Override - public Runner runnerForClass(Class<?> each) throws Throwable { - if (hasSuiteMethod(each)) - return new SuiteMethod(each); - return null; - } + @Override + public Runner runnerForClass(Class<?> each) throws Throwable { + if (hasSuiteMethod(each)) { + return new SuiteMethod(each); + } + return null; + } - public boolean hasSuiteMethod(Class<?> testClass) { - try { - testClass.getMethod("suite"); - } catch (NoSuchMethodException e) { - return false; - } - return true; - } + public boolean hasSuiteMethod(Class<?> testClass) { + try { + testClass.getMethod("suite"); + } catch (NoSuchMethodException e) { + return false; + } + return true; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java b/src/main/java/org/junit/internal/matchers/CombinableMatcher.java deleted file mode 100644 index e9e6947..0000000 --- a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.anyOf; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public class CombinableMatcher<T> extends BaseMatcher<T> { - - private final Matcher<? extends T> fMatcher; - - public CombinableMatcher(Matcher<? extends T> matcher) { - fMatcher= matcher; - } - - public boolean matches(Object item) { - return fMatcher.matches(item); - } - - public void describeTo(Description description) { - description.appendDescriptionOf(fMatcher); - } - - @SuppressWarnings("unchecked") - public CombinableMatcher<T> and(Matcher<? extends T> matcher) { - return new CombinableMatcher<T>(allOf(matcher, fMatcher)); - } - - @SuppressWarnings("unchecked") - public CombinableMatcher<T> or(Matcher<? extends T> matcher) { - return new CombinableMatcher<T>(anyOf(matcher, fMatcher)); - } -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/Each.java b/src/main/java/org/junit/internal/matchers/Each.java deleted file mode 100644 index 527db3b..0000000 --- a/src/main/java/org/junit/internal/matchers/Each.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.CoreMatchers.not; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public class Each { - public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) { - final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual))); - - return new BaseMatcher<Iterable<T>>() { - public boolean matches(Object item) { - return allItemsAre.matches(item); - } - - public void describeTo(Description description) { - description.appendText("each "); - individual.describeTo(description); - } - }; - } -} diff --git a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java b/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java deleted file mode 100644 index 4436a83..0000000 --- a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.core.AllOf.allOf; -import static org.hamcrest.core.IsEqual.equalTo; - -import java.util.ArrayList; -import java.util.Collection; - -import org.hamcrest.Description; -import org.hamcrest.Factory; -import org.hamcrest.Matcher; - -// Copied (hopefully temporarily) from hamcrest-library -public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> { - private final Matcher<? extends T> elementMatcher; - - public IsCollectionContaining(Matcher<? extends T> elementMatcher) { - this.elementMatcher = elementMatcher; - } - - @Override - public boolean matchesSafely(Iterable<T> collection) { - for (T item : collection) { - if (elementMatcher.matches(item)){ - return true; - } - } - return false; - } - - public void describeTo(Description description) { - description - .appendText("a collection containing ") - .appendDescriptionOf(elementMatcher); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) { - return new IsCollectionContaining<T>(elementMatcher); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItem(T element) { - return hasItem(equalTo(element)); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T>... elementMatchers) { - Collection<Matcher<? extends Iterable<T>>> all - = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length); - for (Matcher<? extends T> elementMatcher : elementMatchers) { - all.add(hasItem(elementMatcher)); - } - return allOf(all); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItems(T... elements) { - Collection<Matcher<? extends Iterable<T>>> all - = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length); - for (T element : elements) { - all.add(hasItem(element)); - } - return allOf(all); - } - -} diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java new file mode 100644 index 0000000..5d45ba3 --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java @@ -0,0 +1,56 @@ +package org.junit.internal.matchers; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +/** + * A matcher that delegates to throwableMatcher and in addition appends the + * stacktrace of the actual Throwable in case of a mismatch. + */ +public class StacktracePrintingMatcher<T extends Throwable> extends + org.hamcrest.TypeSafeMatcher<T> { + + private final Matcher<T> throwableMatcher; + + public StacktracePrintingMatcher(Matcher<T> throwableMatcher) { + this.throwableMatcher = throwableMatcher; + } + + public void describeTo(Description description) { + throwableMatcher.describeTo(description); + } + + @Override + protected boolean matchesSafely(T item) { + return throwableMatcher.matches(item); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + throwableMatcher.describeMismatch(item, description); + description.appendText("\nStacktrace was: "); + description.appendText(readStacktrace(item)); + } + + private String readStacktrace(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + @Factory + public static <T extends Throwable> Matcher<T> isThrowable( + Matcher<T> throwableMatcher) { + return new StacktracePrintingMatcher<T>(throwableMatcher); + } + + @Factory + public static <T extends Exception> Matcher<T> isException( + Matcher<T> exceptionMatcher) { + return new StacktracePrintingMatcher<T>(exceptionMatcher); + } +} diff --git a/src/main/java/org/junit/internal/matchers/StringContains.java b/src/main/java/org/junit/internal/matchers/StringContains.java deleted file mode 100644 index e5f5334..0000000 --- a/src/main/java/org/junit/internal/matchers/StringContains.java +++ /dev/null @@ -1,31 +0,0 @@ -/* Copyright (c) 2000-2006 hamcrest.org - */ -package org.junit.internal.matchers; - -import org.hamcrest.Factory; -import org.hamcrest.Matcher; - -/** - * Tests if the argument is a string that contains a substring. - */ -public class StringContains extends SubstringMatcher { - public StringContains(String substring) { - super(substring); - } - - @Override - protected boolean evalSubstringOf(String s) { - return s.indexOf(substring) >= 0; - } - - @Override - protected String relationship() { - return "containing"; - } - - @Factory - public static Matcher<String> containsString(String substring) { - return new StringContains(substring); - } - -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java b/src/main/java/org/junit/internal/matchers/SubstringMatcher.java deleted file mode 100644 index 1c65240..0000000 --- a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.junit.internal.matchers; - -import org.hamcrest.Description; - -public abstract class SubstringMatcher extends TypeSafeMatcher<String> { - - protected final String substring; - - protected SubstringMatcher(final String substring) { - this.substring = substring; - } - - @Override - public boolean matchesSafely(String item) { - return evalSubstringOf(item); - } - - public void describeTo(Description description) { - description.appendText("a string ") - .appendText(relationship()) - .appendText(" ") - .appendValue(substring); - } - - protected abstract boolean evalSubstringOf(String string); - - protected abstract String relationship(); -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java new file mode 100644 index 0000000..22ce8bd --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java @@ -0,0 +1,50 @@ +package org.junit.internal.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * A matcher that applies a delegate matcher to the cause of the current Throwable, returning the result of that + * match. + * + * @param <T> the type of the throwable being matched + */ +public class ThrowableCauseMatcher<T extends Throwable> extends + TypeSafeMatcher<T> { + + private final Matcher<? extends Throwable> causeMatcher; + + public ThrowableCauseMatcher(Matcher<? extends Throwable> causeMatcher) { + this.causeMatcher = causeMatcher; + } + + public void describeTo(Description description) { + description.appendText("exception with cause "); + description.appendDescriptionOf(causeMatcher); + } + + @Override + protected boolean matchesSafely(T item) { + return causeMatcher.matches(item.getCause()); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + description.appendText("cause "); + causeMatcher.describeMismatch(item.getCause(), description); + } + + /** + * Returns a matcher that verifies that the outer exception has a cause for which the supplied matcher + * evaluates to true. + * + * @param matcher to apply to the cause of the outer exception + * @param <T> type of the outer exception + */ + @Factory + public static <T extends Throwable> Matcher<T> hasCause(final Matcher<? extends Throwable> matcher) { + return new ThrowableCauseMatcher<T>(matcher); + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java new file mode 100644 index 0000000..74386a8 --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java @@ -0,0 +1,37 @@ +package org.junit.internal.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class ThrowableMessageMatcher<T extends Throwable> extends + TypeSafeMatcher<T> { + + private final Matcher<String> matcher; + + public ThrowableMessageMatcher(Matcher<String> matcher) { + this.matcher = matcher; + } + + public void describeTo(Description description) { + description.appendText("exception with message "); + description.appendDescriptionOf(matcher); + } + + @Override + protected boolean matchesSafely(T item) { + return matcher.matches(item.getMessage()); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + description.appendText("message "); + matcher.describeMismatch(item.getMessage(), description); + } + + @Factory + public static <T extends Throwable> Matcher<T> hasMessage(final Matcher<String> matcher) { + return new ThrowableMessageMatcher<T>(matcher); + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java index 794a174..4e2cc12 100644 --- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java +++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java @@ -3,13 +3,16 @@ package org.junit.internal.matchers; import java.lang.reflect.Method; import org.hamcrest.BaseMatcher; +import org.junit.internal.MethodSorter; /** * Convenient base class for Matchers that require a non-null value of a specific type. * This simply implements the null check, checks the type and then casts. * * @author Joe Walnes + * @deprecated Please use {@link org.hamcrest.TypeSafeMatcher}. */ +@Deprecated public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> { private Class<?> expectedType; @@ -23,27 +26,27 @@ public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> { protected TypeSafeMatcher() { expectedType = findExpectedType(getClass()); } - + private static Class<?> findExpectedType(Class<?> fromClass) { for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) { - for (Method method : c.getDeclaredMethods()) { + for (Method method : MethodSorter.getDeclaredMethods(c)) { if (isMatchesSafelyMethod(method)) { return method.getParameterTypes()[0]; } } } - + throw new Error("Cannot determine correct type for matchesSafely() method."); } - + private static boolean isMatchesSafelyMethod(Method method) { - return method.getName().equals("matchesSafely") - && method.getParameterTypes().length == 1 - && !method.isSynthetic(); + return method.getName().equals("matchesSafely") + && method.getParameterTypes().length == 1 + && !method.isSynthetic(); } - + protected TypeSafeMatcher(Class<T> expectedType) { - this.expectedType = expectedType; + this.expectedType = expectedType; } /** diff --git a/src/main/java/org/junit/internal/requests/ClassRequest.java b/src/main/java/org/junit/internal/requests/ClassRequest.java index 53bf520..3d6b100 100644 --- a/src/main/java/org/junit/internal/requests/ClassRequest.java +++ b/src/main/java/org/junit/internal/requests/ClassRequest.java @@ -1,26 +1,39 @@ package org.junit.internal.requests; - import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.runner.Request; import org.junit.runner.Runner; public class ClassRequest extends Request { - private final Class<?> fTestClass; + private final Object runnerLock = new Object(); - private boolean fCanUseSuiteMethod; + /* + * We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses + * reflection to access this field. See + * https://github.com/junit-team/junit/issues/960 + */ + private final Class<?> fTestClass; + private final boolean canUseSuiteMethod; + private volatile Runner runner; - public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) { - fTestClass= testClass; - fCanUseSuiteMethod= canUseSuiteMethod; - } + public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) { + this.fTestClass = testClass; + this.canUseSuiteMethod = canUseSuiteMethod; + } - public ClassRequest(Class<?> testClass) { - this(testClass, true); - } + public ClassRequest(Class<?> testClass) { + this(testClass, true); + } - @Override - public Runner getRunner() { - return new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass); - } + @Override + public Runner getRunner() { + if (runner == null) { + synchronized (runnerLock) { + if (runner == null) { + runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass); + } + } + } + return runner; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/requests/FilterRequest.java b/src/main/java/org/junit/internal/requests/FilterRequest.java index e5d98d1..066cba3 100644 --- a/src/main/java/org/junit/internal/requests/FilterRequest.java +++ b/src/main/java/org/junit/internal/requests/FilterRequest.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.requests; import org.junit.internal.runners.ErrorReportingRunner; @@ -13,30 +10,36 @@ import org.junit.runner.manipulation.NoTestsRemainException; * A filtered {@link Request}. */ public final class FilterRequest extends Request { - private final Request fRequest; - private final Filter fFilter; + private final Request request; + /* + * We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses + * reflection to access this field. See + * https://github.com/junit-team/junit/issues/960 + */ + private final Filter fFilter; - /** - * Creates a filtered Request - * @param classRequest a {@link Request} describing your Tests - * @param filter {@link Filter} to apply to the Tests described in - * <code>classRequest</code> - */ - public FilterRequest(Request classRequest, Filter filter) { - fRequest= classRequest; - fFilter= filter; - } + /** + * Creates a filtered Request + * + * @param request a {@link Request} describing your Tests + * @param filter {@link Filter} to apply to the Tests described in + * <code>request</code> + */ + public FilterRequest(Request request, Filter filter) { + this.request = request; + this.fFilter = filter; + } - @Override - public Runner getRunner() { - try { - Runner runner= fRequest.getRunner(); - fFilter.apply(runner); - return runner; - } catch (NoTestsRemainException e) { - return new ErrorReportingRunner(Filter.class, new Exception(String - .format("No tests found matching %s from %s", fFilter - .describe(), fRequest.toString()))); - } - } + @Override + public Runner getRunner() { + try { + Runner runner = request.getRunner(); + fFilter.apply(runner); + return runner; + } catch (NoTestsRemainException e) { + return new ErrorReportingRunner(Filter.class, new Exception(String + .format("No tests found matching %s from %s", fFilter + .describe(), request.toString()))); + } + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/requests/SortingRequest.java b/src/main/java/org/junit/internal/requests/SortingRequest.java index 3c6f4f5..77061da 100644 --- a/src/main/java/org/junit/internal/requests/SortingRequest.java +++ b/src/main/java/org/junit/internal/requests/SortingRequest.java @@ -8,18 +8,18 @@ import org.junit.runner.Runner; import org.junit.runner.manipulation.Sorter; public class SortingRequest extends Request { - private final Request fRequest; - private final Comparator<Description> fComparator; + private final Request request; + private final Comparator<Description> comparator; - public SortingRequest(Request request, Comparator<Description> comparator) { - fRequest= request; - fComparator= comparator; - } + public SortingRequest(Request request, Comparator<Description> comparator) { + this.request = request; + this.comparator = comparator; + } - @Override - public Runner getRunner() { - Runner runner= fRequest.getRunner(); - new Sorter(fComparator).apply(runner); - return runner; - } + @Override + public Runner getRunner() { + Runner runner = request.getRunner(); + new Sorter(comparator).apply(runner); + return runner; + } } diff --git a/src/main/java/org/junit/internal/runners/ClassRoadie.java b/src/main/java/org/junit/internal/runners/ClassRoadie.java index 1f77d37..df1b453 100644 --- a/src/main/java/org/junit/internal/runners/ClassRoadie.java +++ b/src/main/java/org/junit/internal/runners/ClassRoadie.java @@ -4,6 +4,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; +import org.junit.internal.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; @@ -11,69 +12,70 @@ import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated -public -class ClassRoadie { - private RunNotifier fNotifier; - private TestClass fTestClass; - private Description fDescription; - private final Runnable fRunnable; - - public ClassRoadie(RunNotifier notifier, TestClass testClass, - Description description, Runnable runnable) { - fNotifier= notifier; - fTestClass= testClass; - fDescription= description; - fRunnable= runnable; - } +public class ClassRoadie { + private RunNotifier notifier; + private TestClass testClass; + private Description description; + private final Runnable runnable; - protected void runUnprotected() { - fRunnable.run(); - }; + public ClassRoadie(RunNotifier notifier, TestClass testClass, + Description description, Runnable runnable) { + this.notifier = notifier; + this.testClass = testClass; + this.description = description; + this.runnable = runnable; + } - protected void addFailure(Throwable targetException) { - fNotifier.fireTestFailure(new Failure(fDescription, targetException)); - } + protected void runUnprotected() { + runnable.run(); + } - public void runProtected() { - try { - runBefores(); - runUnprotected(); - } catch (FailedBefore e) { - } finally { - runAfters(); - } - } + protected void addFailure(Throwable targetException) { + notifier.fireTestFailure(new Failure(description, targetException)); + } - private void runBefores() throws FailedBefore { - try { - try { - List<Method> befores= fTestClass.getBefores(); - for (Method before : befores) - before.invoke(null); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } catch (org.junit.internal.AssumptionViolatedException e) { - throw new FailedBefore(); - } catch (Throwable e) { - addFailure(e); - throw new FailedBefore(); - } - } + public void runProtected() { + try { + runBefores(); + runUnprotected(); + } catch (FailedBefore e) { + } finally { + runAfters(); + } + } - private void runAfters() { - List<Method> afters= fTestClass.getAfters(); - for (Method after : afters) - try { - after.invoke(null); - } catch (InvocationTargetException e) { - addFailure(e.getTargetException()); - } catch (Throwable e) { - addFailure(e); // Untested, but seems impossible - } - } -}
\ No newline at end of file + private void runBefores() throws FailedBefore { + try { + try { + List<Method> befores = testClass.getBefores(); + for (Method before : befores) { + before.invoke(null); + } + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } catch (AssumptionViolatedException e) { + throw new FailedBefore(); + } catch (Throwable e) { + addFailure(e); + throw new FailedBefore(); + } + } + + private void runAfters() { + List<Method> afters = testClass.getAfters(); + for (Method after : afters) { + try { + after.invoke(null); + } catch (InvocationTargetException e) { + addFailure(e.getTargetException()); + } catch (Throwable e) { + addFailure(e); // Untested, but seems impossible + } + } + } +} diff --git a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java index 200b6f0..1d32beb 100644 --- a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java +++ b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java @@ -11,50 +11,58 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.InitializationError; public class ErrorReportingRunner extends Runner { - private final List<Throwable> fCauses; - - private final Class<?> fTestClass; - - public ErrorReportingRunner(Class<?> testClass, Throwable cause) { - fTestClass= testClass; - fCauses= getCauses(cause); - } - - @Override - public Description getDescription() { - Description description= Description.createSuiteDescription(fTestClass); - for (Throwable each : fCauses) - description.addChild(describeCause(each)); - return description; - } - - @Override - public void run(RunNotifier notifier) { - for (Throwable each : fCauses) - runCause(each, notifier); - } - - @SuppressWarnings("deprecation") - private List<Throwable> getCauses(Throwable cause) { - if (cause instanceof InvocationTargetException) - return getCauses(cause.getCause()); - if (cause instanceof InitializationError) - return ((InitializationError) cause).getCauses(); - if (cause instanceof org.junit.internal.runners.InitializationError) - return ((org.junit.internal.runners.InitializationError) cause) - .getCauses(); - return Arrays.asList(cause); - } - - private Description describeCause(Throwable child) { - return Description.createTestDescription(fTestClass, - "initializationError"); - } - - private void runCause(Throwable child, RunNotifier notifier) { - Description description= describeCause(child); - notifier.fireTestStarted(description); - notifier.fireTestFailure(new Failure(description, child)); - notifier.fireTestFinished(description); - } -}
\ No newline at end of file + private final List<Throwable> causes; + + private final Class<?> testClass; + + public ErrorReportingRunner(Class<?> testClass, Throwable cause) { + if (testClass == null) { + throw new NullPointerException("Test class cannot be null"); + } + this.testClass = testClass; + causes = getCauses(cause); + } + + @Override + public Description getDescription() { + Description description = Description.createSuiteDescription(testClass); + for (Throwable each : causes) { + description.addChild(describeCause(each)); + } + return description; + } + + @Override + public void run(RunNotifier notifier) { + for (Throwable each : causes) { + runCause(each, notifier); + } + } + + @SuppressWarnings("deprecation") + private List<Throwable> getCauses(Throwable cause) { + if (cause instanceof InvocationTargetException) { + return getCauses(cause.getCause()); + } + if (cause instanceof InitializationError) { + return ((InitializationError) cause).getCauses(); + } + if (cause instanceof org.junit.internal.runners.InitializationError) { + return ((org.junit.internal.runners.InitializationError) cause) + .getCauses(); + } + return Arrays.asList(cause); + } + + private Description describeCause(Throwable child) { + return Description.createTestDescription(testClass, + "initializationError"); + } + + private void runCause(Throwable child, RunNotifier notifier) { + Description description = describeCause(child); + notifier.fireTestStarted(description); + notifier.fireTestFailure(new Failure(description, child)); + notifier.fireTestFinished(description); + } +} diff --git a/src/main/java/org/junit/internal/runners/FailedBefore.java b/src/main/java/org/junit/internal/runners/FailedBefore.java index 29dcba4..1036cb6 100644 --- a/src/main/java/org/junit/internal/runners/FailedBefore.java +++ b/src/main/java/org/junit/internal/runners/FailedBefore.java @@ -2,13 +2,12 @@ package org.junit.internal.runners; import org.junit.runners.BlockJUnit4ClassRunner; - /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated class FailedBefore extends Exception { - private static final long serialVersionUID= 1L; + private static final long serialVersionUID = 1L; }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/InitializationError.java b/src/main/java/org/junit/internal/runners/InitializationError.java index 5715ec5..52065ec 100644 --- a/src/main/java/org/junit/internal/runners/InitializationError.java +++ b/src/main/java/org/junit/internal/runners/InitializationError.java @@ -3,28 +3,35 @@ package org.junit.internal.runners; import java.util.Arrays; import java.util.List; -@Deprecated /** - * Use the published version: {@link org.junit.runners.InitializationError} + * Use the published version: + * {@link org.junit.runners.model.InitializationError} * This may disappear as soon as 1 April 2009 */ +@Deprecated public class InitializationError extends Exception { - private static final long serialVersionUID= 1L; - private final List<Throwable> fErrors; + private static final long serialVersionUID = 1L; + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final List<Throwable> fErrors; + + public InitializationError(List<Throwable> errors) { + this.fErrors = errors; + } - public InitializationError(List<Throwable> errors) { - fErrors= errors; - } + public InitializationError(Throwable... errors) { + this(Arrays.asList(errors)); + } - public InitializationError(Throwable... errors) { - this(Arrays.asList(errors)); - } - - public InitializationError(String string) { - this(new Exception(string)); - } + public InitializationError(String string) { + this(new Exception(string)); + } - public List<Throwable> getCauses() { - return fErrors; - } + public List<Throwable> getCauses() { + return fErrors; + } } diff --git a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java index 0028d0c..631fcf2 100644 --- a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java +++ b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java @@ -17,142 +17,164 @@ import org.junit.runner.manipulation.Sortable; import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; public class JUnit38ClassRunner extends Runner implements Filterable, Sortable { - private final class OldTestClassAdaptingListener implements - TestListener { - private final RunNotifier fNotifier; - - private OldTestClassAdaptingListener(RunNotifier notifier) { - fNotifier= notifier; - } - - public void endTest(Test test) { - fNotifier.fireTestFinished(asDescription(test)); - } - - public void startTest(Test test) { - fNotifier.fireTestStarted(asDescription(test)); - } - - // Implement junit.framework.TestListener - public void addError(Test test, Throwable t) { - Failure failure= new Failure(asDescription(test), t); - fNotifier.fireTestFailure(failure); - } - - private Description asDescription(Test test) { - if (test instanceof Describable) { - Describable facade= (Describable) test; - return facade.getDescription(); - } - return Description.createTestDescription(getEffectiveClass(test), getName(test)); - } - - private Class<? extends Test> getEffectiveClass(Test test) { - return test.getClass(); - } - - private String getName(Test test) { - if (test instanceof TestCase) - return ((TestCase) test).getName(); - else - return test.toString(); - } - - public void addFailure(Test test, AssertionFailedError t) { - addError(test, t); - } - } - - private Test fTest; - - public JUnit38ClassRunner(Class<?> klass) { - this(new TestSuite(klass.asSubclass(TestCase.class))); - } - - public JUnit38ClassRunner(Test test) { - super(); - setTest(test); - } - - @Override - public void run(RunNotifier notifier) { - TestResult result= new TestResult(); - result.addListener(createAdaptingListener(notifier)); - getTest().run(result); - } - - public TestListener createAdaptingListener(final RunNotifier notifier) { - return new OldTestClassAdaptingListener(notifier); - } - - @Override - public Description getDescription() { - return makeDescription(getTest()); - } - - private static Description makeDescription(Test test) { - if (test instanceof TestCase) { - TestCase tc= (TestCase) test; - return Description.createTestDescription(tc.getClass(), tc.getName()); - } else if (test instanceof TestSuite) { - TestSuite ts= (TestSuite) test; - String name= ts.getName() == null ? createSuiteDescription(ts) : ts.getName(); - Description description= Description.createSuiteDescription(name); - int n= ts.testCount(); - for (int i= 0; i < n; i++) { - Description made= makeDescription(ts.testAt(i)); - description.addChild(made); - } - return description; - } else if (test instanceof Describable) { - Describable adapter= (Describable) test; - return adapter.getDescription(); - } else if (test instanceof TestDecorator) { - TestDecorator decorator= (TestDecorator) test; - return makeDescription(decorator.getTest()); - } else { - // This is the best we can do in this case - return Description.createSuiteDescription(test.getClass()); - } - } - - private static String createSuiteDescription(TestSuite ts) { - int count= ts.countTestCases(); - String example = count == 0 ? "" : String.format(" [example: %s]", ts.testAt(0)); - return String.format("TestSuite with %s tests%s", count, example); - } - - public void filter(Filter filter) throws NoTestsRemainException { - if (getTest() instanceof Filterable) { - Filterable adapter= (Filterable) getTest(); - adapter.filter(filter); - } else if (getTest() instanceof TestSuite) { - TestSuite suite= (TestSuite) getTest(); - TestSuite filtered= new TestSuite(suite.getName()); - int n= suite.testCount(); - for (int i= 0; i < n; i++) { - Test test= suite.testAt(i); - if (filter.shouldRun(makeDescription(test))) - filtered.addTest(test); - } - setTest(filtered); - } - } - - public void sort(Sorter sorter) { - if (getTest() instanceof Sortable) { - Sortable adapter= (Sortable) getTest(); - adapter.sort(sorter); - } - } - - private void setTest(Test test) { - fTest = test; - } - - private Test getTest() { - return fTest; - } + private static final class OldTestClassAdaptingListener implements + TestListener { + private final RunNotifier notifier; + + private OldTestClassAdaptingListener(RunNotifier notifier) { + this.notifier = notifier; + } + + public void endTest(Test test) { + notifier.fireTestFinished(asDescription(test)); + } + + public void startTest(Test test) { + notifier.fireTestStarted(asDescription(test)); + } + + // Implement junit.framework.TestListener + public void addError(Test test, Throwable e) { + Failure failure = new Failure(asDescription(test), e); + notifier.fireTestFailure(failure); + } + + private Description asDescription(Test test) { + if (test instanceof Describable) { + Describable facade = (Describable) test; + return facade.getDescription(); + } + return Description.createTestDescription(getEffectiveClass(test), getName(test)); + } + + private Class<? extends Test> getEffectiveClass(Test test) { + return test.getClass(); + } + + private String getName(Test test) { + if (test instanceof TestCase) { + return ((TestCase) test).getName(); + } else { + return test.toString(); + } + } + + public void addFailure(Test test, AssertionFailedError t) { + addError(test, t); + } + } + + private volatile Test test; + + public JUnit38ClassRunner(Class<?> klass) { + this(new TestSuite(klass.asSubclass(TestCase.class))); + } + + public JUnit38ClassRunner(Test test) { + super(); + setTest(test); + } + + @Override + public void run(RunNotifier notifier) { + TestResult result = new TestResult(); + result.addListener(createAdaptingListener(notifier)); + getTest().run(result); + } + + public TestListener createAdaptingListener(final RunNotifier notifier) { + return new OldTestClassAdaptingListener(notifier); + } + + @Override + public Description getDescription() { + return makeDescription(getTest()); + } + + private static Description makeDescription(Test test) { + if (test instanceof TestCase) { + TestCase tc = (TestCase) test; + return Description.createTestDescription(tc.getClass(), tc.getName(), + getAnnotations(tc)); + } else if (test instanceof TestSuite) { + TestSuite ts = (TestSuite) test; + String name = ts.getName() == null ? createSuiteDescription(ts) : ts.getName(); + Description description = Description.createSuiteDescription(name); + int n = ts.testCount(); + for (int i = 0; i < n; i++) { + Description made = makeDescription(ts.testAt(i)); + description.addChild(made); + } + return description; + } else if (test instanceof Describable) { + Describable adapter = (Describable) test; + return adapter.getDescription(); + } else if (test instanceof TestDecorator) { + TestDecorator decorator = (TestDecorator) test; + return makeDescription(decorator.getTest()); + } else { + // This is the best we can do in this case + return Description.createSuiteDescription(test.getClass()); + } + } + + /** + * Get the annotations associated with given TestCase. + * @param test the TestCase. + */ + private static Annotation[] getAnnotations(TestCase test) { + try { + Method m = test.getClass().getMethod(test.getName()); + return m.getDeclaredAnnotations(); + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } + return new Annotation[0]; + } + + private static String createSuiteDescription(TestSuite ts) { + int count = ts.countTestCases(); + String example = count == 0 ? "" : String.format(" [example: %s]", ts.testAt(0)); + return String.format("TestSuite with %s tests%s", count, example); + } + + public void filter(Filter filter) throws NoTestsRemainException { + if (getTest() instanceof Filterable) { + Filterable adapter = (Filterable) getTest(); + adapter.filter(filter); + } else if (getTest() instanceof TestSuite) { + TestSuite suite = (TestSuite) getTest(); + TestSuite filtered = new TestSuite(suite.getName()); + int n = suite.testCount(); + for (int i = 0; i < n; i++) { + Test test = suite.testAt(i); + if (filter.shouldRun(makeDescription(test))) { + filtered.addTest(test); + } + } + setTest(filtered); + if (filtered.testCount() == 0) { + throw new NoTestsRemainException(); + } + } + } + + public void sort(Sorter sorter) { + if (getTest() instanceof Sortable) { + Sortable adapter = (Sortable) getTest(); + adapter.sort(sorter); + } + } + + private void setTest(Test test) { + this.test = test; + } + + private Test getTest() { + return test; + } } diff --git a/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java index d732880..69a23c4 100644 --- a/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java +++ b/src/main/java/org/junit/internal/runners/JUnit4ClassRunner.java @@ -21,125 +21,127 @@ import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. - * - * This may disappear as soon as 1 April 2009 */ @Deprecated public class JUnit4ClassRunner extends Runner implements Filterable, Sortable { - private final List<Method> fTestMethods; - private TestClass fTestClass; - - public JUnit4ClassRunner(Class<?> klass) throws InitializationError { - fTestClass= new TestClass(klass); - fTestMethods= getTestMethods(); - validate(); - } - - protected List<Method> getTestMethods() { - return fTestClass.getTestMethods(); - } - - protected void validate() throws InitializationError { - MethodValidator methodValidator= new MethodValidator(fTestClass); - methodValidator.validateMethodsForDefaultRunner(); - methodValidator.assertValid(); - } - - @Override - public void run(final RunNotifier notifier) { - new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() { - public void run() { - runMethods(notifier); - } - }).runProtected(); - } - - protected void runMethods(final RunNotifier notifier) { - for (Method method : fTestMethods) - invokeTestMethod(method, notifier); - } - - @Override - public Description getDescription() { - Description spec= Description.createSuiteDescription(getName(), classAnnotations()); - List<Method> testMethods= fTestMethods; - for (Method method : testMethods) - spec.addChild(methodDescription(method)); - return spec; - } - - protected Annotation[] classAnnotations() { - return fTestClass.getJavaClass().getAnnotations(); - } - - protected String getName() { - return getTestClass().getName(); - } - - protected Object createTest() throws Exception { - return getTestClass().getConstructor().newInstance(); - } - - protected void invokeTestMethod(Method method, RunNotifier notifier) { - Description description= methodDescription(method); - Object test; - try { - test= createTest(); - } catch (InvocationTargetException e) { - testAborted(notifier, description, e.getCause()); - return; - } catch (Exception e) { - testAborted(notifier, description, e); - return; - } - TestMethod testMethod= wrapMethod(method); - new MethodRoadie(test, testMethod, notifier, description).run(); - } - - private void testAborted(RunNotifier notifier, Description description, - Throwable e) { - notifier.fireTestStarted(description); - notifier.fireTestFailure(new Failure(description, e)); - notifier.fireTestFinished(description); - } - - protected TestMethod wrapMethod(Method method) { - return new TestMethod(method, fTestClass); - } - - protected String testName(Method method) { - return method.getName(); - } - - protected Description methodDescription(Method method) { - return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method)); - } - - protected Annotation[] testAnnotations(Method method) { - return method.getAnnotations(); - } - - public void filter(Filter filter) throws NoTestsRemainException { - for (Iterator<Method> iter= fTestMethods.iterator(); iter.hasNext();) { - Method method= iter.next(); - if (!filter.shouldRun(methodDescription(method))) - iter.remove(); - } - if (fTestMethods.isEmpty()) - throw new NoTestsRemainException(); - } - - public void sort(final Sorter sorter) { - Collections.sort(fTestMethods, new Comparator<Method>() { - public int compare(Method o1, Method o2) { - return sorter.compare(methodDescription(o1), methodDescription(o2)); - } - }); - } - - protected TestClass getTestClass() { - return fTestClass; - } + private final List<Method> testMethods; + private TestClass testClass; + + public JUnit4ClassRunner(Class<?> klass) throws InitializationError { + testClass = new TestClass(klass); + testMethods = getTestMethods(); + validate(); + } + + protected List<Method> getTestMethods() { + return testClass.getTestMethods(); + } + + protected void validate() throws InitializationError { + MethodValidator methodValidator = new MethodValidator(testClass); + methodValidator.validateMethodsForDefaultRunner(); + methodValidator.assertValid(); + } + + @Override + public void run(final RunNotifier notifier) { + new ClassRoadie(notifier, testClass, getDescription(), new Runnable() { + public void run() { + runMethods(notifier); + } + }).runProtected(); + } + + protected void runMethods(final RunNotifier notifier) { + for (Method method : testMethods) { + invokeTestMethod(method, notifier); + } + } + + @Override + public Description getDescription() { + Description spec = Description.createSuiteDescription(getName(), classAnnotations()); + List<Method> testMethods = this.testMethods; + for (Method method : testMethods) { + spec.addChild(methodDescription(method)); + } + return spec; + } + + protected Annotation[] classAnnotations() { + return testClass.getJavaClass().getAnnotations(); + } + + protected String getName() { + return getTestClass().getName(); + } + + protected Object createTest() throws Exception { + return getTestClass().getConstructor().newInstance(); + } + + protected void invokeTestMethod(Method method, RunNotifier notifier) { + Description description = methodDescription(method); + Object test; + try { + test = createTest(); + } catch (InvocationTargetException e) { + testAborted(notifier, description, e.getCause()); + return; + } catch (Exception e) { + testAborted(notifier, description, e); + return; + } + TestMethod testMethod = wrapMethod(method); + new MethodRoadie(test, testMethod, notifier, description).run(); + } + + private void testAborted(RunNotifier notifier, Description description, + Throwable e) { + notifier.fireTestStarted(description); + notifier.fireTestFailure(new Failure(description, e)); + notifier.fireTestFinished(description); + } + + protected TestMethod wrapMethod(Method method) { + return new TestMethod(method, testClass); + } + + protected String testName(Method method) { + return method.getName(); + } + + protected Description methodDescription(Method method) { + return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method)); + } + + protected Annotation[] testAnnotations(Method method) { + return method.getAnnotations(); + } + + public void filter(Filter filter) throws NoTestsRemainException { + for (Iterator<Method> iter = testMethods.iterator(); iter.hasNext(); ) { + Method method = iter.next(); + if (!filter.shouldRun(methodDescription(method))) { + iter.remove(); + } + } + if (testMethods.isEmpty()) { + throw new NoTestsRemainException(); + } + } + + public void sort(final Sorter sorter) { + Collections.sort(testMethods, new Comparator<Method>() { + public int compare(Method o1, Method o2) { + return sorter.compare(methodDescription(o1), methodDescription(o2)); + } + }); + } + + protected TestClass getTestClass() { + return testClass; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/MethodRoadie.java b/src/main/java/org/junit/internal/runners/MethodRoadie.java index 4407821..01a476b 100644 --- a/src/main/java/org/junit/internal/runners/MethodRoadie.java +++ b/src/main/java/org/junit/internal/runners/MethodRoadie.java @@ -15,143 +15,149 @@ import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.TestTimedOutException; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated public class MethodRoadie { - private final Object fTest; - private final RunNotifier fNotifier; - private final Description fDescription; - private TestMethod fTestMethod; + private final Object test; + private final RunNotifier notifier; + private final Description description; + private TestMethod testMethod; - public MethodRoadie(Object test, TestMethod method, RunNotifier notifier, Description description) { - fTest= test; - fNotifier= notifier; - fDescription= description; - fTestMethod= method; - } + public MethodRoadie(Object test, TestMethod method, RunNotifier notifier, Description description) { + this.test = test; + this.notifier = notifier; + this.description = description; + testMethod = method; + } - public void run() { - if (fTestMethod.isIgnored()) { - fNotifier.fireTestIgnored(fDescription); - return; - } - fNotifier.fireTestStarted(fDescription); - try { - long timeout= fTestMethod.getTimeout(); - if (timeout > 0) - runWithTimeout(timeout); - else - runTest(); - } finally { - fNotifier.fireTestFinished(fDescription); - } - } + public void run() { + if (testMethod.isIgnored()) { + notifier.fireTestIgnored(description); + return; + } + notifier.fireTestStarted(description); + try { + long timeout = testMethod.getTimeout(); + if (timeout > 0) { + runWithTimeout(timeout); + } else { + runTest(); + } + } finally { + notifier.fireTestFinished(description); + } + } - private void runWithTimeout(final long timeout) { - runBeforesThenTestThenAfters(new Runnable() { - - public void run() { - ExecutorService service= Executors.newSingleThreadExecutor(); - Callable<Object> callable= new Callable<Object>() { - public Object call() throws Exception { - runTestMethod(); - return null; - } - }; - Future<Object> result= service.submit(callable); - service.shutdown(); - try { - boolean terminated= service.awaitTermination(timeout, - TimeUnit.MILLISECONDS); - if (!terminated) - service.shutdownNow(); - result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation - } catch (TimeoutException e) { - addFailure(new Exception(String.format("test timed out after %d milliseconds", timeout))); - } catch (Exception e) { - addFailure(e); - } - } - }); - } - - public void runTest() { - runBeforesThenTestThenAfters(new Runnable() { - public void run() { - runTestMethod(); - } - }); - } + private void runWithTimeout(final long timeout) { + runBeforesThenTestThenAfters(new Runnable() { - public void runBeforesThenTestThenAfters(Runnable test) { - try { - runBefores(); - test.run(); - } catch (FailedBefore e) { - } catch (Exception e) { - throw new RuntimeException("test should never throw an exception to this level"); - } finally { - runAfters(); - } - } - - protected void runTestMethod() { - try { - fTestMethod.invoke(fTest); - if (fTestMethod.expectsException()) - addFailure(new AssertionError("Expected exception: " + fTestMethod.getExpectedException().getName())); - } catch (InvocationTargetException e) { - Throwable actual= e.getTargetException(); - if (actual instanceof AssumptionViolatedException) - return; - else if (!fTestMethod.expectsException()) - addFailure(actual); - else if (fTestMethod.isUnexpected(actual)) { - String message= "Unexpected exception, expected<" + fTestMethod.getExpectedException().getName() + "> but was<" - + actual.getClass().getName() + ">"; - addFailure(new Exception(message, actual)); - } - } catch (Throwable e) { - addFailure(e); - } - } - - private void runBefores() throws FailedBefore { - try { - try { - List<Method> befores= fTestMethod.getBefores(); - for (Method before : befores) - before.invoke(fTest); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } catch (AssumptionViolatedException e) { - throw new FailedBefore(); - } catch (Throwable e) { - addFailure(e); - throw new FailedBefore(); - } - } + public void run() { + ExecutorService service = Executors.newSingleThreadExecutor(); + Callable<Object> callable = new Callable<Object>() { + public Object call() throws Exception { + runTestMethod(); + return null; + } + }; + Future<Object> result = service.submit(callable); + service.shutdown(); + try { + boolean terminated = service.awaitTermination(timeout, + TimeUnit.MILLISECONDS); + if (!terminated) { + service.shutdownNow(); + } + result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation + } catch (TimeoutException e) { + addFailure(new TestTimedOutException(timeout, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + addFailure(e); + } + } + }); + } - private void runAfters() { - List<Method> afters= fTestMethod.getAfters(); - for (Method after : afters) - try { - after.invoke(fTest); - } catch (InvocationTargetException e) { - addFailure(e.getTargetException()); - } catch (Throwable e) { - addFailure(e); // Untested, but seems impossible - } - } + public void runTest() { + runBeforesThenTestThenAfters(new Runnable() { + public void run() { + runTestMethod(); + } + }); + } - protected void addFailure(Throwable e) { - fNotifier.fireTestFailure(new Failure(fDescription, e)); - } + public void runBeforesThenTestThenAfters(Runnable test) { + try { + runBefores(); + test.run(); + } catch (FailedBefore e) { + } catch (Exception e) { + throw new RuntimeException("test should never throw an exception to this level"); + } finally { + runAfters(); + } + } + + protected void runTestMethod() { + try { + testMethod.invoke(test); + if (testMethod.expectsException()) { + addFailure(new AssertionError("Expected exception: " + testMethod.getExpectedException().getName())); + } + } catch (InvocationTargetException e) { + Throwable actual = e.getTargetException(); + if (actual instanceof AssumptionViolatedException) { + return; + } else if (!testMethod.expectsException()) { + addFailure(actual); + } else if (testMethod.isUnexpected(actual)) { + String message = "Unexpected exception, expected<" + testMethod.getExpectedException().getName() + "> but was<" + + actual.getClass().getName() + ">"; + addFailure(new Exception(message, actual)); + } + } catch (Throwable e) { + addFailure(e); + } + } + + private void runBefores() throws FailedBefore { + try { + try { + List<Method> befores = testMethod.getBefores(); + for (Method before : befores) { + before.invoke(test); + } + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } catch (AssumptionViolatedException e) { + throw new FailedBefore(); + } catch (Throwable e) { + addFailure(e); + throw new FailedBefore(); + } + } + + private void runAfters() { + List<Method> afters = testMethod.getAfters(); + for (Method after : afters) { + try { + after.invoke(test); + } catch (InvocationTargetException e) { + addFailure(e.getTargetException()); + } catch (Throwable e) { + addFailure(e); // Untested, but seems impossible + } + } + } + + protected void addFailure(Throwable e) { + notifier.fireTestFailure(new Failure(description, e)); + } } diff --git a/src/main/java/org/junit/internal/runners/MethodValidator.java b/src/main/java/org/junit/internal/runners/MethodValidator.java index cadc93f..ba9c9d1 100644 --- a/src/main/java/org/junit/internal/runners/MethodValidator.java +++ b/src/main/java/org/junit/internal/runners/MethodValidator.java @@ -15,77 +15,83 @@ import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated public class MethodValidator { - private final List<Throwable> fErrors= new ArrayList<Throwable>(); + private final List<Throwable> errors = new ArrayList<Throwable>(); - private TestClass fTestClass; + private TestClass testClass; - public MethodValidator(TestClass testClass) { - fTestClass = testClass; - } + public MethodValidator(TestClass testClass) { + this.testClass = testClass; + } - public void validateInstanceMethods() { - validateTestMethods(After.class, false); - validateTestMethods(Before.class, false); - validateTestMethods(Test.class, false); - - List<Method> methods= fTestClass.getAnnotatedMethods(Test.class); - if (methods.size() == 0) - fErrors.add(new Exception("No runnable methods")); - } + public void validateInstanceMethods() { + validateTestMethods(After.class, false); + validateTestMethods(Before.class, false); + validateTestMethods(Test.class, false); - public void validateStaticMethods() { - validateTestMethods(BeforeClass.class, true); - validateTestMethods(AfterClass.class, true); - } - - public List<Throwable> validateMethodsForDefaultRunner() { - validateNoArgConstructor(); - validateStaticMethods(); - validateInstanceMethods(); - return fErrors; - } - - public void assertValid() throws InitializationError { - if (!fErrors.isEmpty()) - throw new InitializationError(fErrors); - } + List<Method> methods = testClass.getAnnotatedMethods(Test.class); + if (methods.size() == 0) { + errors.add(new Exception("No runnable methods")); + } + } - public void validateNoArgConstructor() { - try { - fTestClass.getConstructor(); - } catch (Exception e) { - fErrors.add(new Exception("Test class should have public zero-argument constructor", e)); - } - } + public void validateStaticMethods() { + validateTestMethods(BeforeClass.class, true); + validateTestMethods(AfterClass.class, true); + } - private void validateTestMethods(Class<? extends Annotation> annotation, - boolean isStatic) { - List<Method> methods= fTestClass.getAnnotatedMethods(annotation); - - for (Method each : methods) { - if (Modifier.isStatic(each.getModifiers()) != isStatic) { - String state= isStatic ? "should" : "should not"; - fErrors.add(new Exception("Method " + each.getName() + "() " + public List<Throwable> validateMethodsForDefaultRunner() { + validateNoArgConstructor(); + validateStaticMethods(); + validateInstanceMethods(); + return errors; + } + + public void assertValid() throws InitializationError { + if (!errors.isEmpty()) { + throw new InitializationError(errors); + } + } + + public void validateNoArgConstructor() { + try { + testClass.getConstructor(); + } catch (Exception e) { + errors.add(new Exception("Test class should have public zero-argument constructor", e)); + } + } + + private void validateTestMethods(Class<? extends Annotation> annotation, + boolean isStatic) { + List<Method> methods = testClass.getAnnotatedMethods(annotation); + + for (Method each : methods) { + if (Modifier.isStatic(each.getModifiers()) != isStatic) { + String state = isStatic ? "should" : "should not"; + errors.add(new Exception("Method " + each.getName() + "() " + state + " be static")); - } - if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) - fErrors.add(new Exception("Class " + each.getDeclaringClass().getName() + } + if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) { + errors.add(new Exception("Class " + each.getDeclaringClass().getName() + " should be public")); - if (!Modifier.isPublic(each.getModifiers())) - fErrors.add(new Exception("Method " + each.getName() + } + if (!Modifier.isPublic(each.getModifiers())) { + errors.add(new Exception("Method " + each.getName() + " should be public")); - if (each.getReturnType() != Void.TYPE) - fErrors.add(new Exception("Method " + each.getName() + } + if (each.getReturnType() != Void.TYPE) { + errors.add(new Exception("Method " + each.getName() + " should be void")); - if (each.getParameterTypes().length != 0) - fErrors.add(new Exception("Method " + each.getName() + } + if (each.getParameterTypes().length != 0) { + errors.add(new Exception("Method " + each.getName() + " should have no parameters")); - } - } + } + } + } } diff --git a/src/main/java/org/junit/internal/runners/SuiteMethod.java b/src/main/java/org/junit/internal/runners/SuiteMethod.java index 4e8bebc..e336983 100644 --- a/src/main/java/org/junit/internal/runners/SuiteMethod.java +++ b/src/main/java/org/junit/internal/runners/SuiteMethod.java @@ -6,7 +6,8 @@ import java.lang.reflect.Modifier; import junit.framework.Test; -/** Runner for use with JUnit 3.8.x-style AllTests classes +/** + * Runner for use with JUnit 3.8.x-style AllTests classes * (those that only implement a static <code>suite()</code> * method). For example: * <pre> @@ -19,22 +20,22 @@ import junit.framework.Test; * </pre> */ public class SuiteMethod extends JUnit38ClassRunner { - public SuiteMethod(Class<?> klass) throws Throwable { - super(testFromSuiteMethod(klass)); - } + public SuiteMethod(Class<?> klass) throws Throwable { + super(testFromSuiteMethod(klass)); + } - public static Test testFromSuiteMethod(Class<?> klass) throws Throwable { - Method suiteMethod= null; - Test suite= null; - try { - suiteMethod= klass.getMethod("suite"); - if (! Modifier.isStatic(suiteMethod.getModifiers())) { - throw new Exception(klass.getName() + ".suite() must be static"); - } - suite= (Test) suiteMethod.invoke(null); // static method - } catch (InvocationTargetException e) { - throw e.getCause(); - } - return suite; - } + public static Test testFromSuiteMethod(Class<?> klass) throws Throwable { + Method suiteMethod = null; + Test suite = null; + try { + suiteMethod = klass.getMethod("suite"); + if (!Modifier.isStatic(suiteMethod.getModifiers())) { + throw new Exception(klass.getName() + ".suite() must be static"); + } + suite = (Test) suiteMethod.invoke(null); // static method + } catch (InvocationTargetException e) { + throw e.getCause(); + } + return suite; + } } diff --git a/src/main/java/org/junit/internal/runners/TestClass.java b/src/main/java/org/junit/internal/runners/TestClass.java index 1ca2b9d..1abaeea 100644 --- a/src/main/java/org/junit/internal/runners/TestClass.java +++ b/src/main/java/org/junit/internal/runners/TestClass.java @@ -11,92 +11,99 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.internal.MethodSorter; import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated public class TestClass { - private final Class<?> fClass; - - public TestClass(Class<?> klass) { - fClass= klass; - } - - public List<Method> getTestMethods() { - return getAnnotatedMethods(Test.class); - } - - List<Method> getBefores() { - return getAnnotatedMethods(BeforeClass.class); - } - - List<Method> getAfters() { - return getAnnotatedMethods(AfterClass.class); - } - - public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) { - List<Method> results= new ArrayList<Method>(); - for (Class<?> eachClass : getSuperClasses(fClass)) { - Method[] methods= eachClass.getDeclaredMethods(); - for (Method eachMethod : methods) { - Annotation annotation= eachMethod.getAnnotation(annotationClass); - if (annotation != null && ! isShadowed(eachMethod, results)) - results.add(eachMethod); - } - } - if (runsTopToBottom(annotationClass)) - Collections.reverse(results); - return results; - } - - private boolean runsTopToBottom(Class< ? extends Annotation> annotation) { - return annotation.equals(Before.class) || annotation.equals(BeforeClass.class); - } - - private boolean isShadowed(Method method, List<Method> results) { - for (Method each : results) { - if (isShadowed(method, each)) - return true; - } - return false; - } - - private boolean isShadowed(Method current, Method previous) { - if (! previous.getName().equals(current.getName())) - return false; - if (previous.getParameterTypes().length != current.getParameterTypes().length) - return false; - for (int i= 0; i < previous.getParameterTypes().length; i++) { - if (! previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) - return false; - } - return true; - } - - private List<Class<?>> getSuperClasses(Class< ?> testClass) { - ArrayList<Class<?>> results= new ArrayList<Class<?>>(); - Class<?> current= testClass; - while (current != null) { - results.add(current); - current= current.getSuperclass(); - } - return results; - } - - public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException { - return fClass.getConstructor(); - } - - public Class<?> getJavaClass() { - return fClass; - } - - public String getName() { - return fClass.getName(); - } + private final Class<?> klass; + + public TestClass(Class<?> klass) { + this.klass = klass; + } + + public List<Method> getTestMethods() { + return getAnnotatedMethods(Test.class); + } + + List<Method> getBefores() { + return getAnnotatedMethods(BeforeClass.class); + } + + List<Method> getAfters() { + return getAnnotatedMethods(AfterClass.class); + } + + public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) { + List<Method> results = new ArrayList<Method>(); + for (Class<?> eachClass : getSuperClasses(klass)) { + Method[] methods = MethodSorter.getDeclaredMethods(eachClass); + for (Method eachMethod : methods) { + Annotation annotation = eachMethod.getAnnotation(annotationClass); + if (annotation != null && !isShadowed(eachMethod, results)) { + results.add(eachMethod); + } + } + } + if (runsTopToBottom(annotationClass)) { + Collections.reverse(results); + } + return results; + } + + private boolean runsTopToBottom(Class<? extends Annotation> annotation) { + return annotation.equals(Before.class) || annotation.equals(BeforeClass.class); + } + + private boolean isShadowed(Method method, List<Method> results) { + for (Method each : results) { + if (isShadowed(method, each)) { + return true; + } + } + return false; + } + + private boolean isShadowed(Method current, Method previous) { + if (!previous.getName().equals(current.getName())) { + return false; + } + if (previous.getParameterTypes().length != current.getParameterTypes().length) { + return false; + } + for (int i = 0; i < previous.getParameterTypes().length; i++) { + if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) { + return false; + } + } + return true; + } + + private List<Class<?>> getSuperClasses(Class<?> testClass) { + ArrayList<Class<?>> results = new ArrayList<Class<?>>(); + Class<?> current = testClass; + while (current != null) { + results.add(current); + current = current.getSuperclass(); + } + return results; + } + + public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException { + return klass.getConstructor(); + } + + public Class<?> getJavaClass() { + return klass; + } + + public String getName() { + return klass.getName(); + } } diff --git a/src/main/java/org/junit/internal/runners/TestMethod.java b/src/main/java/org/junit/internal/runners/TestMethod.java index a06213c..821e193 100644 --- a/src/main/java/org/junit/internal/runners/TestMethod.java +++ b/src/main/java/org/junit/internal/runners/TestMethod.java @@ -13,57 +13,59 @@ import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be - * removed in the next release. Please use + * removed in the next major release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated public class TestMethod { - private final Method fMethod; - private TestClass fTestClass; + private final Method method; + private TestClass testClass; - public TestMethod(Method method, TestClass testClass) { - fMethod= method; - fTestClass= testClass; - } + public TestMethod(Method method, TestClass testClass) { + this.method = method; + this.testClass = testClass; + } - public boolean isIgnored() { - return fMethod.getAnnotation(Ignore.class) != null; - } + public boolean isIgnored() { + return method.getAnnotation(Ignore.class) != null; + } - public long getTimeout() { - Test annotation= fMethod.getAnnotation(Test.class); - if (annotation == null) - return 0; - long timeout= annotation.timeout(); - return timeout; - } + public long getTimeout() { + Test annotation = method.getAnnotation(Test.class); + if (annotation == null) { + return 0; + } + long timeout = annotation.timeout(); + return timeout; + } - protected Class<? extends Throwable> getExpectedException() { - Test annotation= fMethod.getAnnotation(Test.class); - if (annotation == null || annotation.expected() == None.class) - return null; - else - return annotation.expected(); - } + protected Class<? extends Throwable> getExpectedException() { + Test annotation = method.getAnnotation(Test.class); + if (annotation == null || annotation.expected() == None.class) { + return null; + } else { + return annotation.expected(); + } + } - boolean isUnexpected(Throwable exception) { - return ! getExpectedException().isAssignableFrom(exception.getClass()); - } + boolean isUnexpected(Throwable exception) { + return !getExpectedException().isAssignableFrom(exception.getClass()); + } - boolean expectsException() { - return getExpectedException() != null; - } + boolean expectsException() { + return getExpectedException() != null; + } - List<Method> getBefores() { - return fTestClass.getAnnotatedMethods(Before.class); - } + List<Method> getBefores() { + return testClass.getAnnotatedMethods(Before.class); + } - List<Method> getAfters() { - return fTestClass.getAnnotatedMethods(After.class); - } + List<Method> getAfters() { + return testClass.getAnnotatedMethods(After.class); + } - public void invoke(Object test) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { - fMethod.invoke(test); - } + public void invoke(Object test) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + method.invoke(test); + } } diff --git a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java index a7d534c..e094809 100644 --- a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java +++ b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.runners.model; import org.junit.internal.AssumptionViolatedException; @@ -10,42 +7,42 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.MultipleFailureException; public class EachTestNotifier { - private final RunNotifier fNotifier; - - private final Description fDescription; - - public EachTestNotifier(RunNotifier notifier, Description description) { - fNotifier= notifier; - fDescription= description; - } - - public void addFailure(Throwable targetException) { - if (targetException instanceof MultipleFailureException) { - addMultipleFailureException((MultipleFailureException) targetException); - } else { - fNotifier - .fireTestFailure(new Failure(fDescription, targetException)); - } - } - - private void addMultipleFailureException(MultipleFailureException mfe) { - for (Throwable each : mfe.getFailures()) - addFailure(each); - } - - public void addFailedAssumption(AssumptionViolatedException e) { - fNotifier.fireTestAssumptionFailed(new Failure(fDescription, e)); - } - - public void fireTestFinished() { - fNotifier.fireTestFinished(fDescription); - } - - public void fireTestStarted() { - fNotifier.fireTestStarted(fDescription); - } - - public void fireTestIgnored() { - fNotifier.fireTestIgnored(fDescription); - } + private final RunNotifier notifier; + + private final Description description; + + public EachTestNotifier(RunNotifier notifier, Description description) { + this.notifier = notifier; + this.description = description; + } + + public void addFailure(Throwable targetException) { + if (targetException instanceof MultipleFailureException) { + addMultipleFailureException((MultipleFailureException) targetException); + } else { + notifier.fireTestFailure(new Failure(description, targetException)); + } + } + + private void addMultipleFailureException(MultipleFailureException mfe) { + for (Throwable each : mfe.getFailures()) { + addFailure(each); + } + } + + public void addFailedAssumption(AssumptionViolatedException e) { + notifier.fireTestAssumptionFailed(new Failure(description, e)); + } + + public void fireTestFinished() { + notifier.fireTestFinished(description); + } + + public void fireTestStarted() { + notifier.fireTestStarted(description); + } + + public void fireTestIgnored() { + notifier.fireTestIgnored(description); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java b/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java index 3316806..054f042 100644 --- a/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java +++ b/src/main/java/org/junit/internal/runners/model/MultipleFailureException.java @@ -4,9 +4,9 @@ import java.util.List; @Deprecated public class MultipleFailureException extends org.junit.runners.model.MultipleFailureException { - private static final long serialVersionUID= 1L; + private static final long serialVersionUID = 1L; - public MultipleFailureException(List<Throwable> errors) { - super(errors); - } + public MultipleFailureException(List<Throwable> errors) { + super(errors); + } } diff --git a/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java b/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java index 9150d90..79d5c05 100644 --- a/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java +++ b/src/main/java/org/junit/internal/runners/model/ReflectiveCallable.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.runners.model; import java.lang.reflect.InvocationTargetException; @@ -10,13 +7,13 @@ import java.lang.reflect.InvocationTargetException; * wrapping it in an InvocationTargetException. */ public abstract class ReflectiveCallable { - public Object run() throws Throwable { - try { - return runReflectiveCall(); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } + public Object run() throws Throwable { + try { + return runReflectiveCall(); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } - protected abstract Object runReflectiveCall() throws Throwable; + protected abstract Object runReflectiveCall() throws Throwable; }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java deleted file mode 100644 index e7df8bf..0000000 --- a/src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.junit.internal.runners.rules; - -import java.lang.annotation.Annotation; -import java.util.List; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.rules.TestRule; -import org.junit.runners.model.FrameworkField; -import org.junit.runners.model.TestClass; - -/** - * A RuleFieldValidator validates the rule fields of a - * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the - * {@code TestClass} are written to a list of errors. - * - * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR} - * validates fields with a {@link ClassRule} annotation and the - * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation. - */ -public enum RuleFieldValidator { - /** - * Validates fields with a {@link ClassRule} annotation. - */ - CLASS_RULE_VALIDATOR(ClassRule.class, true), - /** - * Validates fields with a {@link Rule} annotation. - */ - RULE_VALIDATOR(Rule.class, false); - - private final Class<? extends Annotation> fAnnotation; - - private final boolean fOnlyStaticFields; - - private RuleFieldValidator(Class<? extends Annotation> annotation, - boolean onlyStaticFields) { - this.fAnnotation= annotation; - this.fOnlyStaticFields= onlyStaticFields; - } - - /** - * Validate the {@link org.junit.runners.model.TestClass} and adds reasons - * for rejecting the class to a list of errors. - * @param target the {@code TestClass} to validate. - * @param errors the list of errors. - */ - public void validate(TestClass target, List<Throwable> errors) { - List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation); - for (FrameworkField each : fields) - validateField(each, errors); - } - - private void validateField(FrameworkField field, List<Throwable> errors) { - optionallyValidateStatic(field, errors); - validatePublic(field, errors); - validateTestRuleOrMethodRule(field, errors); - } - - private void optionallyValidateStatic(FrameworkField field, - List<Throwable> errors) { - if (fOnlyStaticFields && !field.isStatic()) - addError(errors, field, "must be static."); - } - - private void validatePublic(FrameworkField field, List<Throwable> errors) { - if (!field.isPublic()) - addError(errors, field, "must be public."); - } - - private void validateTestRuleOrMethodRule(FrameworkField field, - List<Throwable> errors) { - if (!isMethodRule(field) && !isTestRule(field)) - addError(errors, field, "must implement MethodRule or TestRule."); - } - - private boolean isTestRule(FrameworkField target) { - return TestRule.class.isAssignableFrom(target.getType()); - } - - @SuppressWarnings("deprecation") - private boolean isMethodRule(FrameworkField target) { - return org.junit.rules.MethodRule.class.isAssignableFrom(target - .getType()); - } - - private void addError(List<Throwable> errors, FrameworkField field, - String suffix) { - String message= "The @" + fAnnotation.getSimpleName() + " '" - + field.getName() + "' " + suffix; - errors.add(new Exception(message)); - } -} diff --git a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java index 23f921b..36de4f1 100644 --- a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java +++ b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java @@ -1,91 +1,279 @@ package org.junit.internal.runners.rules; -import java.lang.annotation.Annotation; -import java.util.List; import org.junit.ClassRule; import org.junit.Rule; +import org.junit.rules.MethodRule; import org.junit.rules.TestRule; -import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.TestClass; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + /** - * A RuleFieldValidator validates the rule fields of a - * {@link TestClass}. All reasons for rejecting the + * A RuleMemberValidator validates the rule fields/methods of a + * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the * {@code TestClass} are written to a list of errors. * - * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR} + * <p>There are four slightly different validators. The {@link #CLASS_RULE_VALIDATOR} * validates fields with a {@link ClassRule} annotation and the - * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation. + * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.</p> + * + * <p>The {@link #CLASS_RULE_METHOD_VALIDATOR} + * validates methods with a {@link ClassRule} annotation and the + * {@link #RULE_METHOD_VALIDATOR} validates methods with a {@link Rule} annotation.</p> */ -public enum RuleMemberValidator { - /** - * Validates fields with a {@link ClassRule} annotation. - */ - CLASS_RULE_VALIDATOR(ClassRule.class, true), - /** - * Validates fields with a {@link Rule} annotation. - */ - RULE_VALIDATOR(Rule.class, false); - - private final Class<? extends Annotation> fAnnotation; - - private final boolean fOnlyStaticFields; - - private RuleMemberValidator(Class<? extends Annotation> annotation, - boolean onlyStaticFields) { - this.fAnnotation= annotation; - this.fOnlyStaticFields= onlyStaticFields; - } - - /** - * Validate the {@link TestClass} and adds reasons - * for rejecting the class to a list of errors. - * @param target the {@code TestClass} to validate. - * @param errors the list of errors. - */ - public void validate(TestClass target, List<Throwable> errors) { - List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation); - for (FrameworkField each : fields) - validateField(each, errors); - } - - private void validateField(FrameworkField field, List<Throwable> errors) { - optionallyValidateStatic(field, errors); - validatePublic(field, errors); - validateTestRuleOrMethodRule(field, errors); - } - - private void optionallyValidateStatic(FrameworkField field, - List<Throwable> errors) { - if (fOnlyStaticFields && !field.isStatic()) - addError(errors, field, "must be static."); - } - - private void validatePublic(FrameworkField field, List<Throwable> errors) { - if (!field.isPublic()) - addError(errors, field, "must be public."); - } - - private void validateTestRuleOrMethodRule(FrameworkField field, - List<Throwable> errors) { - if (!isMethodRule(field) && !isTestRule(field)) - addError(errors, field, "must implement MethodRule or TestRule."); - } - - private boolean isTestRule(FrameworkField target) { - return TestRule.class.isAssignableFrom(target.getType()); - } - - @SuppressWarnings("deprecation") - private boolean isMethodRule(FrameworkField target) { - return org.junit.rules.MethodRule.class.isAssignableFrom(target - .getType()); - } - - private void addError(List<Throwable> errors, FrameworkField field, - String suffix) { - String message= "The @" + fAnnotation.getSimpleName() + " '" - + field.getName() + "' " + suffix; - errors.add(new Exception(message)); - } +public class RuleMemberValidator { + /** + * Validates fields with a {@link ClassRule} annotation. + */ + public static final RuleMemberValidator CLASS_RULE_VALIDATOR = + classRuleValidatorBuilder() + .withValidator(new DeclaringClassMustBePublic()) + .withValidator(new MemberMustBeStatic()) + .withValidator(new MemberMustBePublic()) + .withValidator(new FieldMustBeATestRule()) + .build(); + /** + * Validates fields with a {@link Rule} annotation. + */ + public static final RuleMemberValidator RULE_VALIDATOR = + testRuleValidatorBuilder() + .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) + .withValidator(new MemberMustBePublic()) + .withValidator(new FieldMustBeARule()) + .build(); + /** + * Validates methods with a {@link ClassRule} annotation. + */ + public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR = + classRuleValidatorBuilder() + .forMethods() + .withValidator(new DeclaringClassMustBePublic()) + .withValidator(new MemberMustBeStatic()) + .withValidator(new MemberMustBePublic()) + .withValidator(new MethodMustBeATestRule()) + .build(); + + /** + * Validates methods with a {@link Rule} annotation. + */ + public static final RuleMemberValidator RULE_METHOD_VALIDATOR = + testRuleValidatorBuilder() + .forMethods() + .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) + .withValidator(new MemberMustBePublic()) + .withValidator(new MethodMustBeARule()) + .build(); + + private final Class<? extends Annotation> annotation; + private final boolean methods; + private final List<RuleValidator> validatorStrategies; + + RuleMemberValidator(Builder builder) { + this.annotation = builder.annotation; + this.methods = builder.methods; + this.validatorStrategies = builder.validators; + } + + /** + * Validate the {@link org.junit.runners.model.TestClass} and adds reasons + * for rejecting the class to a list of errors. + * + * @param target the {@code TestClass} to validate. + * @param errors the list of errors. + */ + public void validate(TestClass target, List<Throwable> errors) { + List<? extends FrameworkMember<?>> members = methods ? target.getAnnotatedMethods(annotation) + : target.getAnnotatedFields(annotation); + + for (FrameworkMember<?> each : members) { + validateMember(each, errors); + } + } + + private void validateMember(FrameworkMember<?> member, List<Throwable> errors) { + for (RuleValidator strategy : validatorStrategies) { + strategy.validate(member, annotation, errors); + } + } + + private static Builder classRuleValidatorBuilder() { + return new Builder(ClassRule.class); + } + + private static Builder testRuleValidatorBuilder() { + return new Builder(Rule.class); + } + + private static class Builder { + private final Class<? extends Annotation> annotation; + private boolean methods; + private final List<RuleValidator> validators; + + private Builder(Class<? extends Annotation> annotation) { + this.annotation = annotation; + this.methods = false; + this.validators = new ArrayList<RuleValidator>(); + } + + Builder forMethods() { + methods = true; + return this; + } + + Builder withValidator(RuleValidator validator) { + validators.add(validator); + return this; + } + + RuleMemberValidator build() { + return new RuleMemberValidator(this); + } + } + + private static boolean isRuleType(FrameworkMember<?> member) { + return isMethodRule(member) || isTestRule(member); + } + + private static boolean isTestRule(FrameworkMember<?> member) { + return TestRule.class.isAssignableFrom(member.getType()); + } + + private static boolean isMethodRule(FrameworkMember<?> member) { + return MethodRule.class.isAssignableFrom(member.getType()); + } + + /** + * Encapsulates a single piece of validation logic, used to determine if {@link org.junit.Rule} and + * {@link org.junit.ClassRule} annotations have been used correctly + */ + interface RuleValidator { + /** + * Examine the given member and add any violations of the strategy's validation logic to the given list of errors + * @param member The member (field or member) to examine + * @param annotation The type of rule annotation on the member + * @param errors The list of errors to add validation violations to + */ + void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors); + } + + /** + * Requires the validated member to be non-static + */ + private static final class MemberMustBeNonStaticOrAlsoClassRule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + boolean isMethodRuleMember = isMethodRule(member); + boolean isClassRuleAnnotated = (member.getAnnotation(ClassRule.class) != null); + + // We disallow: + // - static MethodRule members + // - static @Rule annotated members + // - UNLESS they're also @ClassRule annotated + // Note that MethodRule cannot be annotated with @ClassRule + if (member.isStatic() && (isMethodRuleMember || !isClassRuleAnnotated)) { + String message; + if (isMethodRule(member)) { + message = "must not be static."; + } else { + message = "must not be static or it must be annotated with @ClassRule."; + } + errors.add(new ValidationError(member, annotation, message)); + } + } + } + + /** + * Requires the member to be static + */ + private static final class MemberMustBeStatic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!member.isStatic()) { + errors.add(new ValidationError(member, annotation, + "must be static.")); + } + } + } + + /** + * Requires the member's declaring class to be public + */ + private static final class DeclaringClassMustBePublic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isDeclaringClassPublic(member)) { + errors.add(new ValidationError(member, annotation, + "must be declared in a public class.")); + } + } + + private boolean isDeclaringClassPublic(FrameworkMember<?> member) { + return Modifier.isPublic(member.getDeclaringClass().getModifiers()); + } + } + + /** + * Requires the member to be public + */ + private static final class MemberMustBePublic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!member.isPublic()) { + errors.add(new ValidationError(member, annotation, + "must be public.")); + } + } + } + + /** + * Requires the member is a field implementing {@link org.junit.rules.MethodRule} or {@link org.junit.rules.TestRule} + */ + private static final class FieldMustBeARule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isRuleType(member)) { + errors.add(new ValidationError(member, annotation, + "must implement MethodRule or TestRule.")); + } + } + } + + /** + * Require the member to return an implementation of {@link org.junit.rules.MethodRule} or + * {@link org.junit.rules.TestRule} + */ + private static final class MethodMustBeARule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isRuleType(member)) { + errors.add(new ValidationError(member, annotation, + "must return an implementation of MethodRule or TestRule.")); + } + } + } + + /** + * Require the member to return an implementation of {@link org.junit.rules.TestRule} + */ + private static final class MethodMustBeATestRule implements RuleValidator { + public void validate(FrameworkMember<?> member, + Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isTestRule(member)) { + errors.add(new ValidationError(member, annotation, + "must return an implementation of TestRule.")); + } + } + } + + /** + * Requires the member is a field implementing {@link org.junit.rules.TestRule} + */ + private static final class FieldMustBeATestRule implements RuleValidator { + + public void validate(FrameworkMember<?> member, + Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isTestRule(member)) { + errors.add(new ValidationError(member, annotation, + "must implement TestRule.")); + } + } + } } diff --git a/src/main/java/org/junit/internal/runners/rules/ValidationError.java b/src/main/java/org/junit/internal/runners/rules/ValidationError.java new file mode 100644 index 0000000..d1af8ae --- /dev/null +++ b/src/main/java/org/junit/internal/runners/rules/ValidationError.java @@ -0,0 +1,11 @@ +package org.junit.internal.runners.rules; + +import org.junit.runners.model.FrameworkMember; + +import java.lang.annotation.Annotation; + +class ValidationError extends Exception { + public ValidationError(FrameworkMember<?> member, Class<? extends Annotation> annotation, String suffix) { + super(String.format("The @%s '%s' %s", annotation.getSimpleName(), member.getName(), suffix)); + } +} diff --git a/src/main/java/org/junit/internal/runners/statements/ExpectException.java b/src/main/java/org/junit/internal/runners/statements/ExpectException.java index ddfef07..d0636bd 100644 --- a/src/main/java/org/junit/internal/runners/statements/ExpectException.java +++ b/src/main/java/org/junit/internal/runners/statements/ExpectException.java @@ -1,38 +1,36 @@ -/** - * - */ package org.junit.internal.runners.statements; import org.junit.internal.AssumptionViolatedException; import org.junit.runners.model.Statement; public class ExpectException extends Statement { - private Statement fNext; - private final Class<? extends Throwable> fExpected; - - public ExpectException(Statement next, Class<? extends Throwable> expected) { - fNext= next; - fExpected= expected; - } - - @Override - public void evaluate() throws Exception { - boolean complete = false; - try { - fNext.evaluate(); - complete = true; - } catch (AssumptionViolatedException e) { - throw e; - } catch (Throwable e) { - if (!fExpected.isAssignableFrom(e.getClass())) { - String message= "Unexpected exception, expected<" - + fExpected.getName() + "> but was<" - + e.getClass().getName() + ">"; - throw new Exception(message, e); - } - } - if (complete) - throw new AssertionError("Expected exception: " - + fExpected.getName()); - } + private final Statement next; + private final Class<? extends Throwable> expected; + + public ExpectException(Statement next, Class<? extends Throwable> expected) { + this.next = next; + this.expected = expected; + } + + @Override + public void evaluate() throws Exception { + boolean complete = false; + try { + next.evaluate(); + complete = true; + } catch (AssumptionViolatedException e) { + throw e; + } catch (Throwable e) { + if (!expected.isAssignableFrom(e.getClass())) { + String message = "Unexpected exception, expected<" + + expected.getName() + "> but was<" + + e.getClass().getName() + ">"; + throw new Exception(message, e); + } + } + if (complete) { + throw new AssertionError("Expected exception: " + + expected.getName()); + } + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/statements/Fail.java b/src/main/java/org/junit/internal/runners/statements/Fail.java index e7d0d5c..e55875c 100644 --- a/src/main/java/org/junit/internal/runners/statements/Fail.java +++ b/src/main/java/org/junit/internal/runners/statements/Fail.java @@ -2,16 +2,15 @@ package org.junit.internal.runners.statements; import org.junit.runners.model.Statement; - public class Fail extends Statement { - private final Throwable fError; + private final Throwable error; - public Fail(Throwable e) { - fError= e; - } + public Fail(Throwable e) { + error = e; + } - @Override - public void evaluate() throws Throwable { - throw fError; - } + @Override + public void evaluate() throws Throwable { + throw error; + } } diff --git a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java index bff7c72..7f4f0d5 100644 --- a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java +++ b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java @@ -1,71 +1,311 @@ -/** - * - */ package org.junit.internal.runners.statements; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; +import org.junit.runners.model.TestTimedOutException; public class FailOnTimeout extends Statement { - private final Statement fOriginalStatement; - - private final long fTimeout; - - public FailOnTimeout(Statement originalStatement, long timeout) { - fOriginalStatement= originalStatement; - fTimeout= timeout; - } - - @Override - public void evaluate() throws Throwable { - StatementThread thread= evaluateStatement(); - if (!thread.fFinished) - throwExceptionForUnfinishedThread(thread); - } - - private StatementThread evaluateStatement() throws InterruptedException { - StatementThread thread= new StatementThread(fOriginalStatement); - thread.start(); - thread.join(fTimeout); - thread.interrupt(); - return thread; - } - - private void throwExceptionForUnfinishedThread(StatementThread thread) - throws Throwable { - if (thread.fExceptionThrownByOriginalStatement != null) - throw thread.fExceptionThrownByOriginalStatement; - else - throwTimeoutException(thread); - } - - private void throwTimeoutException(StatementThread thread) throws Exception { - Exception exception= new Exception(String.format( - "test timed out after %d milliseconds", fTimeout)); - exception.setStackTrace(thread.getStackTrace()); - throw exception; - } - - private static class StatementThread extends Thread { - private final Statement fStatement; - - private boolean fFinished= false; - - private Throwable fExceptionThrownByOriginalStatement= null; - - public StatementThread(Statement statement) { - fStatement= statement; - } - - @Override - public void run() { - try { - fStatement.evaluate(); - fFinished= true; - } catch (InterruptedException e) { - //don't log the InterruptedException - } catch (Throwable e) { - fExceptionThrownByOriginalStatement= e; - } - } - } -}
\ No newline at end of file + private final Statement originalStatement; + private final TimeUnit timeUnit; + private final long timeout; + private final boolean lookForStuckThread; + private volatile ThreadGroup threadGroup = null; + + /** + * Returns a new builder for building an instance. + * + * @since 4.12 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates an instance wrapping the given statement with the given timeout in milliseconds. + * + * @param statement the statement to wrap + * @param timeoutMillis the timeout in milliseconds + * @deprecated use {@link #builder()} instead. + */ + @Deprecated + public FailOnTimeout(Statement statement, long timeoutMillis) { + this(builder().withTimeout(timeoutMillis, TimeUnit.MILLISECONDS), statement); + } + + private FailOnTimeout(Builder builder, Statement statement) { + originalStatement = statement; + timeout = builder.timeout; + timeUnit = builder.unit; + lookForStuckThread = builder.lookForStuckThread; + } + + /** + * Builder for {@link FailOnTimeout}. + * + * @since 4.12 + */ + public static class Builder { + private boolean lookForStuckThread = false; + private long timeout = 0; + private TimeUnit unit = TimeUnit.SECONDS; + + private Builder() { + } + + /** + * Specifies the time to wait before timing out the test. + * + * <p>If this is not called, or is called with a {@code timeout} of + * {@code 0}, the returned {@code Statement} will wait forever for the + * test to complete, however the test will still launch from a separate + * thread. This can be useful for disabling timeouts in environments + * where they are dynamically set based on some property. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code this} for method chaining. + */ + public Builder withTimeout(long timeout, TimeUnit unit) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout must be non-negative"); + } + if (unit == null) { + throw new NullPointerException("TimeUnit cannot be null"); + } + this.timeout = timeout; + this.unit = unit; + return this; + } + + /** + * Specifies whether to look for a stuck thread. If a timeout occurs and this + * feature is enabled, the test will look for a thread that appears to be stuck + * and dump its backtrace. This feature is experimental. Behavior may change + * after the 4.12 release in response to feedback. + * + * @param enable {@code true} to enable the feature + * @return {@code this} for method chaining. + */ + public Builder withLookingForStuckThread(boolean enable) { + this.lookForStuckThread = enable; + return this; + } + + /** + * Builds a {@link FailOnTimeout} instance using the values in this builder, + * wrapping the given statement. + * + * @param statement + */ + public FailOnTimeout build(Statement statement) { + if (statement == null) { + throw new NullPointerException("statement cannot be null"); + } + return new FailOnTimeout(this, statement); + } + } + + @Override + public void evaluate() throws Throwable { + CallableStatement callable = new CallableStatement(); + FutureTask<Throwable> task = new FutureTask<Throwable>(callable); + threadGroup = new ThreadGroup("FailOnTimeoutGroup"); + Thread thread = new Thread(threadGroup, task, "Time-limited test"); + thread.setDaemon(true); + thread.start(); + callable.awaitStarted(); + Throwable throwable = getResult(task, thread); + if (throwable != null) { + throw throwable; + } + } + + /** + * Wait for the test task, returning the exception thrown by the test if the + * test failed, an exception indicating a timeout if the test timed out, or + * {@code null} if the test passed. + */ + private Throwable getResult(FutureTask<Throwable> task, Thread thread) { + try { + if (timeout > 0) { + return task.get(timeout, timeUnit); + } else { + return task.get(); + } + } catch (InterruptedException e) { + return e; // caller will re-throw; no need to call Thread.interrupt() + } catch (ExecutionException e) { + // test failed; have caller re-throw the exception thrown by the test + return e.getCause(); + } catch (TimeoutException e) { + return createTimeoutException(thread); + } + } + + private Exception createTimeoutException(Thread thread) { + StackTraceElement[] stackTrace = thread.getStackTrace(); + final Thread stuckThread = lookForStuckThread ? getStuckThread(thread) : null; + Exception currThreadException = new TestTimedOutException(timeout, timeUnit); + if (stackTrace != null) { + currThreadException.setStackTrace(stackTrace); + thread.interrupt(); + } + if (stuckThread != null) { + Exception stuckThreadException = + new Exception ("Appears to be stuck in thread " + + stuckThread.getName()); + stuckThreadException.setStackTrace(getStackTrace(stuckThread)); + return new MultipleFailureException( + Arrays.<Throwable>asList(currThreadException, stuckThreadException)); + } else { + return currThreadException; + } + } + + /** + * Retrieves the stack trace for a given thread. + * @param thread The thread whose stack is to be retrieved. + * @return The stack trace; returns a zero-length array if the thread has + * terminated or the stack cannot be retrieved for some other reason. + */ + private StackTraceElement[] getStackTrace(Thread thread) { + try { + return thread.getStackTrace(); + } catch (SecurityException e) { + return new StackTraceElement[0]; + } + } + + /** + * Determines whether the test appears to be stuck in some thread other than + * the "main thread" (the one created to run the test). This feature is experimental. + * Behavior may change after the 4.12 release in response to feedback. + * @param mainThread The main thread created by {@code evaluate()} + * @return The thread which appears to be causing the problem, if different from + * {@code mainThread}, or {@code null} if the main thread appears to be the + * problem or if the thread cannot be determined. The return value is never equal + * to {@code mainThread}. + */ + private Thread getStuckThread(Thread mainThread) { + if (threadGroup == null) { + return null; + } + Thread[] threadsInGroup = getThreadArray(threadGroup); + if (threadsInGroup == null) { + return null; + } + + // Now that we have all the threads in the test's thread group: Assume that + // any thread we're "stuck" in is RUNNABLE. Look for all RUNNABLE threads. + // If just one, we return that (unless it equals threadMain). If there's more + // than one, pick the one that's using the most CPU time, if this feature is + // supported. + Thread stuckThread = null; + long maxCpuTime = 0; + for (Thread thread : threadsInGroup) { + if (thread.getState() == Thread.State.RUNNABLE) { + long threadCpuTime = cpuTime(thread); + if (stuckThread == null || threadCpuTime > maxCpuTime) { + stuckThread = thread; + maxCpuTime = threadCpuTime; + } + } + } + return (stuckThread == mainThread) ? null : stuckThread; + } + + /** + * Returns all active threads belonging to a thread group. + * @param group The thread group. + * @return The active threads in the thread group. The result should be a + * complete list of the active threads at some point in time. Returns {@code null} + * if this cannot be determined, e.g. because new threads are being created at an + * extremely fast rate. + */ + private Thread[] getThreadArray(ThreadGroup group) { + final int count = group.activeCount(); // this is just an estimate + int enumSize = Math.max(count * 2, 100); + int enumCount; + Thread[] threads; + int loopCount = 0; + while (true) { + threads = new Thread[enumSize]; + enumCount = group.enumerate(threads); + if (enumCount < enumSize) { + break; + } + // if there are too many threads to fit into the array, enumerate's result + // is >= the array's length; therefore we can't trust that it returned all + // the threads. Try again. + enumSize += 100; + if (++loopCount >= 5) { + return null; + } + // threads are proliferating too fast for us. Bail before we get into + // trouble. + } + return copyThreads(threads, enumCount); + } + + /** + * Returns an array of the first {@code count} Threads in {@code threads}. + * (Use instead of Arrays.copyOf to maintain compatibility with Java 1.5.) + * @param threads The source array. + * @param count The maximum length of the result array. + * @return The first {@count} (at most) elements of {@code threads}. + */ + private Thread[] copyThreads(Thread[] threads, int count) { + int length = Math.min(count, threads.length); + Thread[] result = new Thread[length]; + for (int i = 0; i < length; i++) { + result[i] = threads[i]; + } + return result; + } + + /** + * Returns the CPU time used by a thread, if possible. + * @param thr The thread to query. + * @return The CPU time used by {@code thr}, or 0 if it cannot be determined. + */ + private long cpuTime (Thread thr) { + ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); + if (mxBean.isThreadCpuTimeSupported()) { + try { + return mxBean.getThreadCpuTime(thr.getId()); + } catch (UnsupportedOperationException e) { + } + } + return 0; + } + + private class CallableStatement implements Callable<Throwable> { + private final CountDownLatch startLatch = new CountDownLatch(1); + + public Throwable call() throws Exception { + try { + startLatch.countDown(); + originalStatement.evaluate(); + } catch (Exception e) { + throw e; + } catch (Throwable e) { + return e; + } + return null; + } + + public void awaitStarted() throws InterruptedException { + startLatch.await(); + } + } +} diff --git a/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java b/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java index e2e81e1..68c0545 100644 --- a/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java +++ b/src/main/java/org/junit/internal/runners/statements/InvokeMethod.java @@ -1,22 +1,19 @@ -/** - * - */ package org.junit.internal.runners.statements; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; public class InvokeMethod extends Statement { - private final FrameworkMethod fTestMethod; - private Object fTarget; - - public InvokeMethod(FrameworkMethod testMethod, Object target) { - fTestMethod= testMethod; - fTarget= target; - } - - @Override - public void evaluate() throws Throwable { - fTestMethod.invokeExplosively(fTarget); - } + private final FrameworkMethod testMethod; + private final Object target; + + public InvokeMethod(FrameworkMethod testMethod, Object target) { + this.testMethod = testMethod; + this.target = target; + } + + @Override + public void evaluate() throws Throwable { + testMethod.invokeExplosively(target); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/statements/RunAfters.java b/src/main/java/org/junit/internal/runners/statements/RunAfters.java index 475ec72..7512a7d 100644 --- a/src/main/java/org/junit/internal/runners/statements/RunAfters.java +++ b/src/main/java/org/junit/internal/runners/statements/RunAfters.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.runners.statements; import java.util.ArrayList; @@ -11,33 +8,34 @@ import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; public class RunAfters extends Statement { - private final Statement fNext; + private final Statement next; - private final Object fTarget; + private final Object target; - private final List<FrameworkMethod> fAfters; - - public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) { - fNext= next; - fAfters= afters; - fTarget= target; - } + private final List<FrameworkMethod> afters; - @Override - public void evaluate() throws Throwable { - List<Throwable> errors = new ArrayList<Throwable>(); - try { - fNext.evaluate(); - } catch (Throwable e) { - errors.add(e); - } finally { - for (FrameworkMethod each : fAfters) - try { - each.invokeExplosively(fTarget); - } catch (Throwable e) { - errors.add(e); - } - } - MultipleFailureException.assertEmpty(errors); - } + public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) { + this.next = next; + this.afters = afters; + this.target = target; + } + + @Override + public void evaluate() throws Throwable { + List<Throwable> errors = new ArrayList<Throwable>(); + try { + next.evaluate(); + } catch (Throwable e) { + errors.add(e); + } finally { + for (FrameworkMethod each : afters) { + try { + each.invokeExplosively(target); + } catch (Throwable e) { + errors.add(e); + } + } + } + MultipleFailureException.assertEmpty(errors); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/runners/statements/RunBefores.java b/src/main/java/org/junit/internal/runners/statements/RunBefores.java index 66a34e1..238fbe7 100644 --- a/src/main/java/org/junit/internal/runners/statements/RunBefores.java +++ b/src/main/java/org/junit/internal/runners/statements/RunBefores.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.internal.runners.statements; import java.util.List; @@ -9,22 +6,23 @@ import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; public class RunBefores extends Statement { - private final Statement fNext; + private final Statement next; - private final Object fTarget; + private final Object target; - private final List<FrameworkMethod> fBefores; + private final List<FrameworkMethod> befores; - public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) { - fNext= next; - fBefores= befores; - fTarget= target; - } + public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) { + this.next = next; + this.befores = befores; + this.target = target; + } - @Override - public void evaluate() throws Throwable { - for (FrameworkMethod before : fBefores) - before.invokeExplosively(fTarget); - fNext.evaluate(); - } + @Override + public void evaluate() throws Throwable { + for (FrameworkMethod before : befores) { + before.invokeExplosively(target); + } + next.evaluate(); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/matchers/JUnitMatchers.java b/src/main/java/org/junit/matchers/JUnitMatchers.java index 837ed33..13407cc 100644 --- a/src/main/java/org/junit/matchers/JUnitMatchers.java +++ b/src/main/java/org/junit/matchers/JUnitMatchers.java @@ -1,83 +1,113 @@ package org.junit.matchers; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; -import org.junit.internal.matchers.CombinableMatcher; -import org.junit.internal.matchers.Each; -import org.junit.internal.matchers.IsCollectionContaining; -import org.junit.internal.matchers.StringContains; +import org.hamcrest.core.CombinableMatcher.CombinableBothMatcher; +import org.hamcrest.core.CombinableMatcher.CombinableEitherMatcher; +import org.junit.internal.matchers.StacktracePrintingMatcher; /** * Convenience import class: these are useful matchers for use with the assertThat method, but they are * not currently included in the basic CoreMatchers class from hamcrest. + * + * @since 4.4 */ public class JUnitMatchers { - /** - * @param element - * @return A matcher matching any collection containing element - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(T element) { - return IsCollectionContaining.hasItem(element); - } + /** + * @return A matcher matching any collection containing element + * @deprecated Please use {@link CoreMatchers#hasItem(Object)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<? super T>> hasItem(T element) { + return CoreMatchers.hasItem(element); + } - /** - * @param elementMatcher - * @return A matcher matching any collection containing an element matching elementMatcher - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(org.hamcrest.Matcher<? extends T> elementMatcher) { - return IsCollectionContaining.<T>hasItem(elementMatcher); - } + /** + * @return A matcher matching any collection containing an element matching elementMatcher + * @deprecated Please use {@link CoreMatchers#hasItem(Matcher)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> elementMatcher) { + return CoreMatchers.<T>hasItem(elementMatcher); + } - /** - * @param elements - * @return A matcher matching any collection containing every element in elements - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... elements) { - return IsCollectionContaining.hasItems(elements); - } + /** + * @return A matcher matching any collection containing every element in elements + * @deprecated Please use {@link CoreMatchers#hasItems(Object...)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> hasItems(T... elements) { + return CoreMatchers.hasItems(elements); + } - /** - * @param elementMatchers - * @return A matcher matching any collection containing at least one element that matches - * each matcher in elementMatcher (this may be one element matching all matchers, - * or different elements matching each matcher) - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? extends T>... elementMatchers) { - return IsCollectionContaining.<T>hasItems(elementMatchers); - } + /** + * @return A matcher matching any collection containing at least one element that matches + * each matcher in elementMatcher (this may be one element matching all matchers, + * or different elements matching each matcher) + * @deprecated Please use {@link CoreMatchers#hasItems(Matcher...)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... elementMatchers) { + return CoreMatchers.hasItems(elementMatchers); + } - /** - * @param elementMatcher - * @return A matcher matching any collection in which every element matches elementMatcher - */ - public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) { - return Each.each(elementMatcher); - } + /** + * @return A matcher matching any collection in which every element matches elementMatcher + * @deprecated Please use {@link CoreMatchers#everyItem(Matcher)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) { + return CoreMatchers.everyItem(elementMatcher); + } - /** - * @param substring - * @return a matcher matching any string that contains substring - */ - public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) { - return StringContains.containsString(substring); - } - - /** - * This is useful for fluently combining matchers that must both pass. For example: - * <pre> - * assertThat(string, both(containsString("a")).and(containsString("b"))); - * </pre> - */ - public static <T> CombinableMatcher<T> both(Matcher<T> matcher) { - return new CombinableMatcher<T>(matcher); - } - - /** - * This is useful for fluently combining matchers where either may pass, for example: - * <pre> - * assertThat(string, either(containsString("a")).or(containsString("b"))); - * </pre> - */ - public static <T> CombinableMatcher<T> either(Matcher<T> matcher) { - return new CombinableMatcher<T>(matcher); - } + /** + * @return a matcher matching any string that contains substring + * @deprecated Please use {@link CoreMatchers#containsString(String)} instead. + */ + @Deprecated + public static Matcher<java.lang.String> containsString(java.lang.String substring) { + return CoreMatchers.containsString(substring); + } + + /** + * This is useful for fluently combining matchers that must both pass. For example: + * <pre> + * assertThat(string, both(containsString("a")).and(containsString("b"))); + * </pre> + * + * @deprecated Please use {@link CoreMatchers#both(Matcher)} instead. + */ + @Deprecated + public static <T> CombinableBothMatcher<T> both(Matcher<? super T> matcher) { + return CoreMatchers.both(matcher); + } + + /** + * This is useful for fluently combining matchers where either may pass, for example: + * <pre> + * assertThat(string, either(containsString("a")).or(containsString("b"))); + * </pre> + * + * @deprecated Please use {@link CoreMatchers#either(Matcher)} instead. + */ + @Deprecated + public static <T> CombinableEitherMatcher<T> either(Matcher<? super T> matcher) { + return CoreMatchers.either(matcher); + } + + /** + * @return A matcher that delegates to throwableMatcher and in addition + * appends the stacktrace of the actual Throwable in case of a mismatch. + */ + public static <T extends Throwable> Matcher<T> isThrowable(Matcher<T> throwableMatcher) { + return StacktracePrintingMatcher.isThrowable(throwableMatcher); + } + + /** + * @return A matcher that delegates to exceptionMatcher and in addition + * appends the stacktrace of the actual Exception in case of a mismatch. + */ + public static <T extends Exception> Matcher<T> isException(Matcher<T> exceptionMatcher) { + return StacktracePrintingMatcher.isException(exceptionMatcher); + } } diff --git a/src/main/java/org/junit/package-info.java b/src/main/java/org/junit/package-info.java index bb60d0d..fb12f25 100644 --- a/src/main/java/org/junit/package-info.java +++ b/src/main/java/org/junit/package-info.java @@ -1,6 +1,6 @@ /** * Provides JUnit core classes and annotations. - * + * * Corresponds to junit.framework in Junit 3.x. * * @since 4.0 diff --git a/src/main/java/org/junit/rules/DisableOnDebug.java b/src/main/java/org/junit/rules/DisableOnDebug.java new file mode 100644 index 0000000..afa6dee --- /dev/null +++ b/src/main/java/org/junit/rules/DisableOnDebug.java @@ -0,0 +1,127 @@ +package org.junit.rules; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * The {@code DisableOnDebug} Rule allows you to label certain rules to be + * disabled when debugging. + * <p> + * The most illustrative use case is for tests that make use of the + * {@link Timeout} rule, when ran in debug mode the test may terminate on + * timeout abruptly during debugging. Developers may disable the timeout, or + * increase the timeout by making a code change on tests that need debugging and + * remember revert the change afterwards or rules such as {@link Timeout} that + * may be disabled during debugging may be wrapped in a {@code DisableOnDebug}. + * <p> + * The important benefit of this feature is that you can disable such rules + * without any making any modifications to your test class to remove them during + * debugging. + * <p> + * This does nothing to tackle timeouts or time sensitive code under test when + * debugging and may make this less useful in such circumstances. + * <p> + * Example usage: + * + * <pre> + * public static class DisableTimeoutOnDebugSampleTest { + * + * @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)) { + return true; + } else if (argument.startsWith("-agentlib:jdwp")) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if the JVM is in debug mode. This method may be used + * by test classes to take additional action to disable code paths that + * interfere with debugging if required. + * + * @return {@code true} if the current JVM is in debug mode, {@code false} + * otherwise + */ + public boolean isDebugging() { + return debugging; + } + +} diff --git a/src/main/java/org/junit/rules/ErrorCollector.java b/src/main/java/org/junit/rules/ErrorCollector.java index 3522a65..8c6600e 100644 --- a/src/main/java/org/junit/rules/ErrorCollector.java +++ b/src/main/java/org/junit/rules/ErrorCollector.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.rules; import static org.junit.Assert.assertThat; @@ -16,70 +13,72 @@ import org.junit.runners.model.MultipleFailureException; * The ErrorCollector rule allows execution of a test to continue after the * first problem is found (for example, to collect _all_ the incorrect rows in a * table, and report them all at once): - * + * * <pre> * public static class UsesErrorCollectorTwice { * @Rule * public ErrorCollector collector= new ErrorCollector(); - * - * @Test - * public void example() { - * collector.addError(new Throwable("first thing went wrong")); - * collector.addError(new Throwable("second thing went wrong")); - * collector.checkThat(getResult(), not(containsString("ERROR!"))); - * // all lines will run, and then a combined failure logged at the end. - * } + * + * @Test + * public void example() { + * collector.addError(new Throwable("first thing went wrong")); + * collector.addError(new Throwable("second thing went wrong")); + * collector.checkThat(getResult(), not(containsString("ERROR!"))); + * // all lines will run, and then a combined failure logged at the end. + * } * } * </pre> + * + * @since 4.7 */ public class ErrorCollector extends Verifier { - private List<Throwable> errors= new ArrayList<Throwable>(); + private List<Throwable> errors = new ArrayList<Throwable>(); - @Override - protected void verify() throws Throwable { - MultipleFailureException.assertEmpty(errors); - } + @Override + protected void verify() throws Throwable { + MultipleFailureException.assertEmpty(errors); + } - /** - * Adds a Throwable to the table. Execution continues, but the test will fail at the end. - */ - public void addError(Throwable error) { - errors.add(error); - } + /** + * Adds a Throwable to the table. Execution continues, but the test will fail at the end. + */ + public void addError(Throwable error) { + errors.add(error); + } - /** - * Adds a failure to the table if {@code matcher} does not match {@code value}. - * Execution continues, but the test will fail at the end if the match fails. - */ - public <T> void checkThat(final T value, final Matcher<T> matcher) { - checkThat("", value, matcher); - } + /** + * Adds a failure to the table if {@code matcher} does not match {@code value}. + * Execution continues, but the test will fail at the end if the match fails. + */ + public <T> void checkThat(final T value, final Matcher<T> matcher) { + checkThat("", value, matcher); + } - /** - * Adds a failure with the given {@code reason} - * to the table if {@code matcher} does not match {@code value}. - * Execution continues, but the test will fail at the end if the match fails. - */ - public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) { - checkSucceeds(new Callable<Object>() { - public Object call() throws Exception { - assertThat(reason, value, matcher); - return value; - } - }); - } + /** + * Adds a failure with the given {@code reason} + * to the table if {@code matcher} does not match {@code value}. + * Execution continues, but the test will fail at the end if the match fails. + */ + public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) { + checkSucceeds(new Callable<Object>() { + public Object call() throws Exception { + assertThat(reason, value, matcher); + return value; + } + }); + } - /** - * Adds to the table the exception, if any, thrown from {@code callable}. - * Execution continues, but the test will fail at the end if - * {@code callable} threw an exception. - */ - public Object checkSucceeds(Callable<Object> callable) { - try { - return callable.call(); - } catch (Throwable e) { - addError(e); - return null; - } - } -}
\ No newline at end of file + /** + * Adds to the table the exception, if any, thrown from {@code callable}. + * Execution continues, but the test will fail at the end if + * {@code callable} threw an exception. + */ + public <T> T checkSucceeds(Callable<T> callable) { + try { + return callable.call(); + } catch (Throwable e) { + addError(e); + return null; + } + } +} diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java index bac2fba..4d61712 100644 --- a/src/main/java/org/junit/rules/ExpectedException.java +++ b/src/main/java/org/junit/rules/ExpectedException.java @@ -1,136 +1,270 @@ package org.junit.rules; +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.matchers.JUnitMatchers.both; -import static org.junit.matchers.JUnitMatchers.containsString; -import org.hamcrest.Description; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause; +import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; + import org.hamcrest.Matcher; import org.hamcrest.StringDescription; -import org.junit.Assert; -import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.AssumptionViolatedException; import org.junit.runners.model.Statement; /** - * The ExpectedException Rule allows in-test specification of expected exception - * types and messages: - * - * <pre> - * // These tests all pass. - * public static class HasExpectedException { - * @Rule - * public ExpectedException thrown= ExpectedException.none(); - * - * @Test - * public void throwsNothing() { - * // no exception expected, none thrown: passes. - * } + * The {@code ExpectedException} rule allows you to verify that your code + * throws a specific exception. + * + * <h3>Usage</h3> + * + * <pre> public class SimpleExpectedExceptionTest { + * @Rule + * public ExpectedException thrown= ExpectedException.none(); + * + * @Test + * public void throwsNothing() { + * // no exception expected, none thrown: passes. + * } + * + * @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * } + * }</pre> * - * @Test - * public void throwsNullPointerException() { - * thrown.expect(NullPointerException.class); - * throw new NullPointerException(); - * } - * - * @Test - * public void throwsNullPointerExceptionWithMessage() { - * thrown.expect(NullPointerException.class); - * thrown.expectMessage("happened?"); - * thrown.expectMessage(startsWith("What")); - * throw new NullPointerException("What happened?"); - * } - * } - * </pre> + * <p> + * You have to add the {@code ExpectedException} rule to your test. + * This doesn't affect your existing tests (see {@code throwsNothing()}). + * After specifiying the type of the expected exception your test is + * successful when such an exception is thrown and it fails if a + * different or no exception is thrown. + * + * <p> + * Instead of specifying the exception's type you can characterize the + * expected exception based on other criterias, too: + * + * <ul> + * <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li> + * <li>The exception's message complies with a Hamcrest matcher: {@link #expectMessage(Matcher)}</li> + * <li>The exception's cause complies with a Hamcrest matcher: {@link #expectCause(Matcher)}</li> + * <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li> + * </ul> + * + * <p> + * You can combine any of the presented expect-methods. The test is + * successful if all specifications are met. + * <pre> @Test + * public void throwsException() { + * thrown.expect(NullPointerException.class); + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + * + * <h3>AssumptionViolatedExceptions</h3> + * <p> + * JUnit uses {@link AssumptionViolatedException}s for indicating that a test + * provides no useful information. (See {@link org.junit.Assume} for more + * information.) You have to call {@code assume} methods before you set + * expectations of the {@code ExpectedException} rule. In this case the rule + * will not handle consume the exceptions and it can be handled by the + * framework. E.g. the following test is ignored by JUnit's default runner. + * + * <pre> @Test + * public void ignoredBecauseOfFailedAssumption() { + * assumeTrue(false); // throws AssumptionViolatedException + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>AssertionErrors</h3> + * + * <p> + * JUnit uses {@link AssertionError}s for indicating that a test is failing. You + * have to call {@code assert} methods before you set expectations of the + * {@code ExpectedException} rule, if they should be handled by the framework. + * E.g. the following test fails because of the {@code assertTrue} statement. + * + * <pre> @Test + * public void throwsUnhandled() { + * assertTrue(false); // throws AssertionError + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>Missing Exceptions</h3> + * <p> + * By default missing exceptions are reported with an error message + * like "Expected test to throw an instance of foo". You can configure a different + * message by means of {@link #reportMissingExceptionWithMessage(String)}. You + * can use a {@code %s} placeholder for the description of the expected + * exception. E.g. "Test doesn't throw %s." will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @since 4.7 */ public class ExpectedException implements TestRule { - /** - * @return a Rule that expects no exception to be thrown - * (identical to behavior without this Rule) - */ - public static ExpectedException none() { - return new ExpectedException(); - } - - private Matcher<Object> fMatcher= null; - - private ExpectedException() { - - } - - public Statement apply(Statement base, - org.junit.runner.Description description) { - return new ExpectedExceptionStatement(base); - } - - /** - * Adds {@code matcher} to the list of requirements for any thrown exception. - */ - // Should be able to remove this suppression in some brave new hamcrest world. - @SuppressWarnings("unchecked") - public void expect(Matcher<?> matcher) { - if (fMatcher == null) - fMatcher= (Matcher<Object>) matcher; - else - fMatcher= both(fMatcher).and(matcher); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should be an instance of {@code type} - */ - public void expect(Class<? extends Throwable> type) { - expect(instanceOf(type)); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should <em>contain</em> string {@code substring} - */ - public void expectMessage(String substring) { - expectMessage(containsString(substring)); - } - - /** - * Adds {@code matcher} to the list of requirements for the message - * returned from any thrown exception. - */ - public void expectMessage(Matcher<String> matcher) { - expect(hasMessage(matcher)); - } - - private class ExpectedExceptionStatement extends Statement { - private final Statement fNext; - - public ExpectedExceptionStatement(Statement base) { - fNext= base; - } - - @Override - public void evaluate() throws Throwable { - try { - fNext.evaluate(); - } catch (Throwable e) { - if (fMatcher == null) - throw e; - Assert.assertThat(e, fMatcher); - return; - } - if (fMatcher != null) - throw new AssertionError("Expected test to throw " - + StringDescription.toString(fMatcher)); - } - } - - private Matcher<Throwable> hasMessage(final Matcher<String> matcher) { - return new TypeSafeMatcher<Throwable>() { - public void describeTo(Description description) { - description.appendText("exception with message "); - description.appendDescriptionOf(matcher); - } - - @Override - public boolean matchesSafely(Throwable item) { - return matcher.matches(item.getMessage()); - } - }; - } + /** + * Returns a {@linkplain TestRule rule} that expects no exception to + * be thrown (identical to behavior without this rule). + */ + public static ExpectedException none() { + return new ExpectedException(); + } + + private final ExpectedExceptionMatcherBuilder matcherBuilder = new ExpectedExceptionMatcherBuilder(); + + private String missingExceptionMessage= "Expected test to throw %s"; + + private ExpectedException() { + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssertionErrors are handled by default since JUnit 4.12. Just + * like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssertionErrors() { + return this; + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssumptionViolatedExceptions are handled by default since + * JUnit 4.12. Just like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssumptionViolatedExceptions() { + return this; + } + + /** + * Specifies the failure message for tests that are expected to throw + * an exception but do not throw any. You can use a {@code %s} placeholder for + * the description of the expected exception. E.g. "Test doesn't throw %s." + * will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @param message exception detail message + * @return the rule itself + */ + public ExpectedException reportMissingExceptionWithMessage(String message) { + missingExceptionMessage = message; + return this; + } + + public Statement apply(Statement base, + org.junit.runner.Description description) { + return new ExpectedExceptionStatement(base); + } + + /** + * Verify that your code throws an exception that is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionThatCompliesWithMatcher() { + * NullPointerException e = new NullPointerException(); + * thrown.expect(is(e)); + * throw e; + * }</pre> + */ + public void expect(Matcher<?> matcher) { + matcherBuilder.add(matcher); + } + + /** + * Verify that your code throws an exception that is an + * instance of specific {@code type}. + * <pre> @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * }</pre> + */ + public void expect(Class<? extends Throwable> type) { + expect(instanceOf(type)); + } + + /** + * Verify that your code throws an exception whose message contains + * a specific text. + * <pre> @Test + * public void throwsExceptionWhoseMessageContainsSpecificText() { + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(String substring) { + expectMessage(containsString(substring)); + } + + /** + * Verify that your code throws an exception whose message is matched + * by a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseMessageCompliesWithMatcher() { + * thrown.expectMessage(startsWith("What")); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(Matcher<String> matcher) { + expect(hasMessage(matcher)); + } + + /** + * Verify that your code throws an exception whose cause is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseCauseCompliesWithMatcher() { + * NullPointerException expectedCause = new NullPointerException(); + * thrown.expectCause(is(expectedCause)); + * throw new IllegalArgumentException("What happened?", cause); + * }</pre> + */ + public void expectCause(Matcher<? extends Throwable> expectedCause) { + expect(hasCause(expectedCause)); + } + + private class ExpectedExceptionStatement extends Statement { + private final Statement next; + + public ExpectedExceptionStatement(Statement base) { + next = base; + } + + @Override + public void evaluate() throws Throwable { + try { + next.evaluate(); + } catch (Throwable e) { + handleException(e); + return; + } + if (isAnyExceptionExpected()) { + failDueToMissingException(); + } + } + } + + private void handleException(Throwable e) throws Throwable { + if (isAnyExceptionExpected()) { + assertThat(e, matcherBuilder.build()); + } else { + throw e; + } + } + + private boolean isAnyExceptionExpected() { + return matcherBuilder.expectsThrowable(); + } + + private void failDueToMissingException() throws AssertionError { + fail(missingExceptionMessage()); + } + + private String missingExceptionMessage() { + String expectation= StringDescription.toString(matcherBuilder.build()); + return format(missingExceptionMessage, expectation); + } } diff --git a/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java new file mode 100644 index 0000000..e7d94c4 --- /dev/null +++ b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java @@ -0,0 +1,46 @@ +package org.junit.rules; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.junit.matchers.JUnitMatchers.isThrowable; + +import java.util.ArrayList; +import java.util.List; + +import org.hamcrest.Matcher; + +/** + * Builds special matcher used by {@link ExpectedException}. + */ +class ExpectedExceptionMatcherBuilder { + + private final List<Matcher<?>> matchers = new ArrayList<Matcher<?>>(); + + void add(Matcher<?> matcher) { + matchers.add(matcher); + } + + boolean expectsThrowable() { + return !matchers.isEmpty(); + } + + Matcher<Throwable> build() { + return isThrowable(allOfTheMatchers()); + } + + private Matcher<Throwable> allOfTheMatchers() { + if (matchers.size() == 1) { + return cast(matchers.get(0)); + } + return allOf(castedMatchers()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private List<Matcher<? super Throwable>> castedMatchers() { + return new ArrayList<Matcher<? super Throwable>>((List) matchers); + } + + @SuppressWarnings("unchecked") + private Matcher<Throwable> cast(Matcher<?> singleMatcher) { + return (Matcher<Throwable>) singleMatcher; + } +} diff --git a/src/main/java/org/junit/rules/ExternalResource.java b/src/main/java/org/junit/rules/ExternalResource.java index 1fe3719..71ca287 100644 --- a/src/main/java/org/junit/rules/ExternalResource.java +++ b/src/main/java/org/junit/rules/ExternalResource.java @@ -7,62 +7,65 @@ import org.junit.runners.model.Statement; * A base class for Rules (like TemporaryFolder) that set up an external * resource before a test (a file, socket, server, database connection, etc.), * and guarantee to tear it down afterward: - * + * * <pre> * public static class UsesExternalResource { - * Server myServer= new Server(); - * - * @Rule - * public ExternalResource resource= new ExternalResource() { - * @Override - * protected void before() throws Throwable { - * myServer.connect(); - * }; - * - * @Override - * protected void after() { - * myServer.disconnect(); - * }; - * }; - * - * @Test - * public void testFoo() { - * new Client().run(myServer); - * } + * Server myServer= new Server(); + * + * @Rule + * public ExternalResource resource= new ExternalResource() { + * @Override + * protected void before() throws Throwable { + * myServer.connect(); + * }; + * + * @Override + * protected void after() { + * myServer.disconnect(); + * }; + * }; + * + * @Test + * public void testFoo() { + * new Client().run(myServer); + * } * } * </pre> + * + * @since 4.7 */ public abstract class ExternalResource implements TestRule { - public Statement apply(Statement base, Description description) { - return statement(base); - } + public Statement apply(Statement base, Description description) { + return statement(base); + } - private Statement statement(final Statement base) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - before(); - try { - base.evaluate(); - } finally { - after(); - } - } - }; - } + private Statement statement(final Statement base) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(); + try { + base.evaluate(); + } finally { + after(); + } + } + }; + } - /** - * Override to set up your specific external resource. - * @throws if setup fails (which will disable {@code after} - */ - protected void before() throws Throwable { - // do nothing - } + /** + * Override to set up your specific external resource. + * + * @throws Throwable if setup fails (which will disable {@code after} + */ + protected void before() throws Throwable { + // do nothing + } - /** - * Override to tear down your specific external resource. - */ - protected void after() { - // do nothing - } + /** + * Override to tear down your specific external resource. + */ + protected void after() { + // do nothing + } } diff --git a/src/main/java/org/junit/rules/MethodRule.java b/src/main/java/org/junit/rules/MethodRule.java index 5167672..823ee78 100644 --- a/src/main/java/org/junit/rules/MethodRule.java +++ b/src/main/java/org/junit/rules/MethodRule.java @@ -12,7 +12,7 @@ import org.junit.runners.model.Statement; * {@link Statement}, which is passed to the next {@link Rule}, if any. For * examples of how this can be useful, see these provided MethodRules, * or write your own: - * + * * <ul> * <li>{@link ErrorCollector}: collect multiple errors in one test method</li> * <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li> @@ -23,18 +23,22 @@ import org.junit.runners.model.Statement; * <li>{@link Timeout}: cause test to fail after a set time</li> * <li>{@link Verifier}: fail test if object state ends up incorrect</li> * </ul> + * + * Note that {@link MethodRule} has been replaced by {@link TestRule}, + * which has the added benefit of supporting class rules. + * + * @since 4.7 */ -@Deprecated public interface MethodRule { - /** - * Modifies the method-running {@link Statement} to implement an additional - * test-running rule. - * - * @param base The {@link Statement} to be modified - * @param method The method to be run - * @param target The object on with the method will be run. - * @return a new statement, which may be the same as {@code base}, - * a wrapper around {@code base}, or a completely new Statement. - */ - Statement apply(Statement base, FrameworkMethod method, Object target); -}
\ No newline at end of file + /** + * Modifies the method-running {@link Statement} to implement an additional + * test-running rule. + * + * @param base The {@link Statement} to be modified + * @param method The method to be run + * @param target The object on which the method will be run. + * @return a new statement, which may be the same as {@code base}, + * a wrapper around {@code base}, or a completely new Statement. + */ + Statement apply(Statement base, FrameworkMethod method, Object target); +} diff --git a/src/main/java/org/junit/rules/RuleChain.java b/src/main/java/org/junit/rules/RuleChain.java index 8af3c05..f43d8f5 100644 --- a/src/main/java/org/junit/rules/RuleChain.java +++ b/src/main/java/org/junit/rules/RuleChain.java @@ -1,6 +1,3 @@ -/** - * - */ package org.junit.rules; import java.util.ArrayList; @@ -14,24 +11,24 @@ import org.junit.runners.model.Statement; * The RuleChain rule allows ordering of TestRules. You create a * {@code RuleChain} with {@link #outerRule(TestRule)} and subsequent calls of * {@link #around(TestRule)}: - * + * * <pre> * public static class UseRuleChain { * @Rule - * public TestRule chain= RuleChain + * public RuleChain chain= RuleChain * .outerRule(new LoggingRule("outer rule") * .around(new LoggingRule("middle rule") * .around(new LoggingRule("inner rule"); - * + * * @Test * public void example() { * assertTrue(true); - * } + * } * } * </pre> - * + * * writes the log - * + * * <pre> * starting outer rule * starting middle rule @@ -40,60 +37,61 @@ import org.junit.runners.model.Statement; * finished middle rule * finished outer rule * </pre> + * + * @since 4.10 */ public class RuleChain implements TestRule { - private static final RuleChain EMPTY_CHAIN= new RuleChain( - Collections.<TestRule> emptyList()); + private static final RuleChain EMPTY_CHAIN = new RuleChain( + Collections.<TestRule>emptyList()); - private List<TestRule> rulesStartingWithInnerMost; + private List<TestRule> rulesStartingWithInnerMost; - /** - * Returns a {@code RuleChain} without a {@link TestRule}. This method may - * be the starting point of a {@code RuleChain}. - * - * @return a {@code RuleChain} without a {@link TestRule}. - */ - public static RuleChain emptyRuleChain() { - return EMPTY_CHAIN; - } + /** + * Returns a {@code RuleChain} without a {@link TestRule}. This method may + * be the starting point of a {@code RuleChain}. + * + * @return a {@code RuleChain} without a {@link TestRule}. + */ + public static RuleChain emptyRuleChain() { + return EMPTY_CHAIN; + } - /** - * Returns a {@code RuleChain} with a single {@link TestRule}. This method - * is the usual starting point of a {@code RuleChain}. - * - * @param outerRule - * the outer rule of the {@code RuleChain}. - * @return a {@code RuleChain} with a single {@link TestRule}. - */ - public static RuleChain outerRule(TestRule outerRule) { - return emptyRuleChain().around(outerRule); - } + /** + * Returns a {@code RuleChain} with a single {@link TestRule}. This method + * is the usual starting point of a {@code RuleChain}. + * + * @param outerRule the outer rule of the {@code RuleChain}. + * @return a {@code RuleChain} with a single {@link TestRule}. + */ + public static RuleChain outerRule(TestRule outerRule) { + return emptyRuleChain().around(outerRule); + } - private RuleChain(List<TestRule> rules) { - this.rulesStartingWithInnerMost= rules; - } + private RuleChain(List<TestRule> rules) { + this.rulesStartingWithInnerMost = rules; + } - /** - * Create a new {@code RuleChain}, which encloses the {@code nextRule} with - * the rules of the current {@code RuleChain}. - * - * @param enclosedRule - * the rule to enclose. - * @return a new {@code RuleChain}. - */ - public RuleChain around(TestRule enclosedRule) { - List<TestRule> rulesOfNewChain= new ArrayList<TestRule>(); - rulesOfNewChain.add(enclosedRule); - rulesOfNewChain.addAll(rulesStartingWithInnerMost); - return new RuleChain(rulesOfNewChain); - } + /** + * Create a new {@code RuleChain}, which encloses the {@code nextRule} with + * the rules of the current {@code RuleChain}. + * + * @param enclosedRule the rule to enclose. + * @return a new {@code RuleChain}. + */ + public RuleChain around(TestRule enclosedRule) { + List<TestRule> rulesOfNewChain = new ArrayList<TestRule>(); + rulesOfNewChain.add(enclosedRule); + rulesOfNewChain.addAll(rulesStartingWithInnerMost); + return new RuleChain(rulesOfNewChain); + } - /** - * {@inheritDoc} - */ - public Statement apply(Statement base, Description description) { - for (TestRule each : rulesStartingWithInnerMost) - base= each.apply(base, description); - return base; - } + /** + * {@inheritDoc} + */ + public Statement apply(Statement base, Description description) { + for (TestRule each : rulesStartingWithInnerMost) { + base = each.apply(base, description); + } + return base; + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/rules/RunRules.java b/src/main/java/org/junit/rules/RunRules.java index d5905b9..131fc1f 100644 --- a/src/main/java/org/junit/rules/RunRules.java +++ b/src/main/java/org/junit/rules/RunRules.java @@ -5,23 +5,26 @@ import org.junit.runners.model.Statement; /** * Runs a collection of rules on a statement. + * + * @since 4.9 */ public class RunRules extends Statement { - private final Statement statement; + private final Statement statement; - public RunRules(Statement base, Iterable<TestRule> rules, Description description) { - statement= applyAll(base, rules, description); - } - - @Override - public void evaluate() throws Throwable { - statement.evaluate(); - } + public RunRules(Statement base, Iterable<TestRule> rules, Description description) { + statement = applyAll(base, rules, description); + } - private static Statement applyAll(Statement result, Iterable<TestRule> rules, - Description description) { - for (TestRule each : rules) - result= each.apply(result, description); - return result; - } + @Override + public void evaluate() throws Throwable { + statement.evaluate(); + } + + private static Statement applyAll(Statement result, Iterable<TestRule> rules, + Description description) { + for (TestRule each : rules) { + result = each.apply(result, description); + } + return result; + } } diff --git a/src/main/java/org/junit/rules/Stopwatch.java b/src/main/java/org/junit/rules/Stopwatch.java new file mode 100644 index 0000000..5d34e7f --- /dev/null +++ b/src/main/java/org/junit/rules/Stopwatch.java @@ -0,0 +1,183 @@ +package org.junit.rules; + +import org.junit.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.concurrent.TimeUnit; + +/** + * The Stopwatch Rule notifies one of its own protected methods of the time spent by a test. + * + * <p>Override them to get the time in nanoseconds. For example, this class will keep logging the + * time spent by each passed, failed, skipped, and finished test: + * + * <pre> + * public static class StopwatchTest { + * private static final Logger logger = Logger.getLogger(""); + * + * private static void logInfo(Description description, String status, long nanos) { + * String testName = description.getMethodName(); + * logger.info(String.format("Test %s %s, spent %d microseconds", + * testName, status, TimeUnit.NANOSECONDS.toMicros(nanos))); + * } + * + * @Rule + * public Stopwatch stopwatch = new Stopwatch() { + * @Override + * protected void succeeded(long nanos, Description description) { + * logInfo(description, "succeeded", nanos); + * } + * + * @Override + * protected void failed(long nanos, Throwable e, Description description) { + * logInfo(description, "failed", nanos); + * } + * + * @Override + * protected void skipped(long nanos, AssumptionViolatedException e, Description description) { + * logInfo(description, "skipped", nanos); + * } + * + * @Override + * protected void finished(long nanos, Description description) { + * logInfo(description, "finished", nanos); + * } + * }; + * + * @Test + * public void succeeds() { + * } + * + * @Test + * public void fails() { + * fail(); + * } + * + * @Test + * public void skips() { + * assumeTrue(false); + * } + * } + * </pre> + * + * An example to assert runtime: + * <pre> + * @Test + * public void performanceTest() throws InterruptedException { + * long delta = 30; + * Thread.sleep(300L); + * assertEquals(300d, stopwatch.runtime(MILLISECONDS), delta); + * Thread.sleep(500L); + * assertEquals(800d, stopwatch.runtime(MILLISECONDS), delta); + * } + * </pre> + * + * @author tibor17 + * @since 4.12 + */ +public abstract class Stopwatch implements TestRule { + private final Clock clock; + private volatile long startNanos; + private volatile long endNanos; + + public Stopwatch() { + this(new Clock()); + } + + Stopwatch(Clock clock) { + this.clock = clock; + } + + /** + * Gets the runtime for the test. + * + * @param unit time unit for returned runtime + * @return runtime measured during the test + */ + public long runtime(TimeUnit unit) { + return unit.convert(getNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Invoked when a test succeeds + */ + protected void succeeded(long nanos, Description description) { + } + + /** + * Invoked when a test fails + */ + protected void failed(long nanos, Throwable e, Description description) { + } + + /** + * Invoked when a test is skipped due to a failed assumption. + */ + protected void skipped(long nanos, AssumptionViolatedException e, Description description) { + } + + /** + * Invoked when a test method finishes (whether passing or failing) + */ + protected void finished(long nanos, Description description) { + } + + private long getNanos() { + if (startNanos == 0) { + throw new IllegalStateException("Test has not started"); + } + long currentEndNanos = endNanos; // volatile read happens here + if (currentEndNanos == 0) { + currentEndNanos = clock.nanoTime(); + } + + return currentEndNanos - startNanos; + } + + private void starting() { + startNanos = clock.nanoTime(); + endNanos = 0; + } + + private void stopping() { + endNanos = clock.nanoTime(); + } + + public final Statement apply(Statement base, Description description) { + return new InternalWatcher().apply(base, description); + } + + private class InternalWatcher extends TestWatcher { + + @Override protected void starting(Description description) { + Stopwatch.this.starting(); + } + + @Override protected void finished(Description description) { + Stopwatch.this.finished(getNanos(), description); + } + + @Override protected void succeeded(Description description) { + Stopwatch.this.stopping(); + Stopwatch.this.succeeded(getNanos(), description); + } + + @Override protected void failed(Throwable e, Description description) { + Stopwatch.this.stopping(); + Stopwatch.this.failed(getNanos(), e, description); + } + + @Override protected void skipped(AssumptionViolatedException e, Description description) { + Stopwatch.this.stopping(); + Stopwatch.this.skipped(getNanos(), e, description); + } + } + + static class Clock { + + public long nanoTime() { + return System.nanoTime(); + } + } +} diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java index a7c82aa..dc75c93 100644 --- a/src/main/java/org/junit/rules/TemporaryFolder.java +++ b/src/main/java/org/junit/rules/TemporaryFolder.java @@ -6,108 +6,165 @@ import java.io.IOException; import org.junit.Rule; /** - * The TemporaryFolder Rule allows creation of files and folders that are - * guaranteed to be deleted when the test method finishes (whether it passes or - * fails): - * + * The TemporaryFolder Rule allows creation of files and folders that should + * be deleted when the test method finishes (whether it passes or + * fails). Whether the deletion is successful or not is not checked by this rule. + * No exception will be thrown in case the deletion fails. + * + * <p>Example of usage: * <pre> * public static class HasTempFolder { - * @Rule - * public TemporaryFolder folder= new TemporaryFolder(); - * - * @Test - * public void testUsingTempFolder() throws IOException { - * File createdFile= folder.newFile("myfile.txt"); - * File createdFolder= folder.newFolder("subfolder"); - * // ... - * } + * @Rule + * public TemporaryFolder folder= new TemporaryFolder(); + * + * @Test + * public void testUsingTempFolder() throws IOException { + * File createdFile= folder.newFile("myfile.txt"); + * File createdFolder= folder.newFolder("subfolder"); + * // ... + * } * } * </pre> + * + * @since 4.7 */ public class TemporaryFolder extends ExternalResource { - private File folder; - - @Override - protected void before() throws Throwable { - create(); - } - - @Override - protected void after() { - delete(); - } - - // testing purposes only - /** - * for testing purposes only. Do not use. - */ - public void create() throws IOException { - folder= newFolder(); - } - - /** - * Returns a new fresh file with the given name under the temporary folder. - */ - public File newFile(String fileName) throws IOException { - File file= new File(getRoot(), fileName); - file.createNewFile(); - return file; - } - - /** - * Returns a new fresh file with a random name under the temporary folder. - */ - public File newFile() throws IOException { - return File.createTempFile("junit", null, folder); - } - - /** - * Returns a new fresh folder with the given name under the temporary folder. - */ - public File newFolder(String... folderNames) { - File file = getRoot(); - for (String folderName : folderNames) { - file = new File(file, folderName); - file.mkdir(); - } - return file; - } - - /** - * Returns a new fresh folder with a random name under the temporary - * folder. - */ - public File newFolder() throws IOException { - File createdFolder= File.createTempFile("junit", "", folder); - createdFolder.delete(); - createdFolder.mkdir(); - return createdFolder; - } - - /** - * @return the location of this temporary folder. - */ - public File getRoot() { - if (folder == null) { - throw new IllegalStateException("the temporary folder has not yet been created"); - } - return folder; - } - - /** - * Delete all files and folders under the temporary folder. - * Usually not called directly, since it is automatically applied - * by the {@link Rule} - */ - public void delete() { - recursiveDelete(folder); - } - - private void recursiveDelete(File file) { - File[] files= file.listFiles(); - if (files != null) - for (File each : files) - recursiveDelete(each); - file.delete(); - } + private final File parentFolder; + private File folder; + + public TemporaryFolder() { + this(null); + } + + public TemporaryFolder(File parentFolder) { + this.parentFolder = parentFolder; + } + + @Override + protected void before() throws Throwable { + create(); + } + + @Override + protected void after() { + delete(); + } + + // testing purposes only + + /** + * for testing purposes only. Do not use. + */ + public void create() throws IOException { + folder = createTemporaryFolderIn(parentFolder); + } + + /** + * Returns a new fresh file with the given name under the temporary folder. + */ + public File newFile(String fileName) throws IOException { + File file = new File(getRoot(), fileName); + if (!file.createNewFile()) { + throw new IOException( + "a file with the name \'" + fileName + "\' already exists in the test folder"); + } + return file; + } + + /** + * Returns a new fresh file with a random name under the temporary folder. + */ + public File newFile() throws IOException { + return File.createTempFile("junit", null, getRoot()); + } + + /** + * Returns a new fresh folder with the given name under the temporary + * folder. + */ + public File newFolder(String folder) throws IOException { + return newFolder(new String[]{folder}); + } + + /** + * Returns a new fresh folder with the given name(s) under the temporary + * folder. + */ + public File newFolder(String... folderNames) throws IOException { + File file = getRoot(); + for (int i = 0; i < folderNames.length; i++) { + String folderName = folderNames[i]; + validateFolderName(folderName); + file = new File(file, folderName); + if (!file.mkdir() && isLastElementInArray(i, folderNames)) { + throw new IOException( + "a folder with the name \'" + folderName + "\' already exists"); + } + } + return file; + } + + /** + * Validates if multiple path components were used while creating a folder. + * + * @param folderName + * Name of the folder being created + */ + private void validateFolderName(String folderName) throws IOException { + File tempFile = new File(folderName); + if (tempFile.getParent() != null) { + String errorMsg = "Folder name cannot consist of multiple path components separated by a file separator." + + " Please use newFolder('MyParentFolder','MyFolder') to create hierarchies of folders"; + throw new IOException(errorMsg); + } + } + + private boolean isLastElementInArray(int index, String[] array) { + return index == array.length - 1; + } + + /** + * Returns a new fresh folder with a random name under the temporary folder. + */ + public File newFolder() throws IOException { + return createTemporaryFolderIn(getRoot()); + } + + private File createTemporaryFolderIn(File parentFolder) throws IOException { + File createdFolder = File.createTempFile("junit", "", parentFolder); + createdFolder.delete(); + createdFolder.mkdir(); + return createdFolder; + } + + /** + * @return the location of this temporary folder. + */ + public File getRoot() { + if (folder == null) { + throw new IllegalStateException( + "the temporary folder has not yet been created"); + } + return folder; + } + + /** + * Delete all files and folders under the temporary folder. Usually not + * called directly, since it is automatically applied by the {@link Rule} + */ + public void delete() { + if (folder != null) { + recursiveDelete(folder); + } + } + + private void recursiveDelete(File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File each : files) { + recursiveDelete(each); + } + } + file.delete(); + } } diff --git a/src/main/java/org/junit/rules/TestName.java b/src/main/java/org/junit/rules/TestName.java index c4ab9ce..bf72602 100644 --- a/src/main/java/org/junit/rules/TestName.java +++ b/src/main/java/org/junit/rules/TestName.java @@ -4,36 +4,38 @@ import org.junit.runner.Description; /** * The TestName Rule makes the current test name available inside test methods: - * + * * <pre> * public class TestNameTest { - * @Rule - * public TestName name= new TestName(); - * - * @Test - * public void testA() { - * assertEquals("testA", name.getMethodName()); - * } - * - * @Test - * public void testB() { - * assertEquals("testB", name.getMethodName()); - * } + * @Rule + * public TestName name= new TestName(); + * + * @Test + * public void testA() { + * assertEquals("testA", name.getMethodName()); + * } + * + * @Test + * public void testB() { + * assertEquals("testB", name.getMethodName()); + * } * } * </pre> + * + * @since 4.7 */ public class TestName extends TestWatcher { - private String fName; + private String name; - @Override - protected void starting(Description d) { - fName= d.getMethodName(); - } + @Override + protected void starting(Description d) { + name = d.getMethodName(); + } - /** - * @return the name of the currently-running test method - */ - public String getMethodName() { - return fName; - } + /** + * @return the name of the currently-running test method + */ + public String getMethodName() { + return name; + } } diff --git a/src/main/java/org/junit/rules/TestRule.java b/src/main/java/org/junit/rules/TestRule.java index b7760c4..53e2f70 100644 --- a/src/main/java/org/junit/rules/TestRule.java +++ b/src/main/java/org/junit/rules/TestRule.java @@ -9,16 +9,16 @@ import org.junit.runners.model.Statement; * a test that would otherwise fail to pass, or it may perform necessary setup or * cleanup for tests, or it may observe test execution to report it elsewhere. * {@link TestRule}s can do everything that could be done previously with - * methods annotated with {@link org.junit.Before}, - * {@link org.junit.After}, {@link org.junit.BeforeClass}, or - * {@link org.junit.AfterClass}, but they are more powerful, and more easily + * methods annotated with {@link org.junit.Before}, + * {@link org.junit.After}, {@link org.junit.BeforeClass}, or + * {@link org.junit.AfterClass}, but they are more powerful, and more easily * shared * between projects and classes. - * + * * The default JUnit test runners for suites and * individual test cases recognize {@link TestRule}s introduced in two different - * ways. {@link org.junit.Rule} annotates method-level - * {@link TestRule}s, and {@link org.junit.ClassRule} + * ways. {@link org.junit.Rule} annotates method-level + * {@link TestRule}s, and {@link org.junit.ClassRule} * annotates class-level {@link TestRule}s. See Javadoc for those annotations * for more information. * @@ -28,7 +28,7 @@ import org.junit.runners.model.Statement; * {@link Statement}, which is passed to the next {@link org.junit.Rule}, if any. For * examples of how this can be useful, see these provided TestRules, * or write your own: - * + * * <ul> * <li>{@link ErrorCollector}: collect multiple errors in one test method</li> * <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li> @@ -39,16 +39,18 @@ import org.junit.runners.model.Statement; * <li>{@link Timeout}: cause test to fail after a set time</li> * <li>{@link Verifier}: fail test if object state ends up incorrect</li> * </ul> + * + * @since 4.9 */ public interface TestRule { - /** - * Modifies the method-running {@link Statement} to implement this - * test-running rule. - * - * @param base The {@link Statement} to be modified - * @param description A {@link Description} of the test implemented in {@code base} - * @return a new statement, which may be the same as {@code base}, - * a wrapper around {@code base}, or a completely new Statement. - */ - Statement apply(Statement base, Description description); + /** + * Modifies the method-running {@link Statement} to implement this + * test-running rule. + * + * @param base The {@link Statement} to be modified + * @param description A {@link Description} of the test implemented in {@code base} + * @return a new statement, which may be the same as {@code base}, + * a wrapper around {@code base}, or a completely new Statement. + */ + Statement apply(Statement base, Description description); } diff --git a/src/main/java/org/junit/rules/TestWatcher.java b/src/main/java/org/junit/rules/TestWatcher.java index 351b449..5492b6b 100644 --- a/src/main/java/org/junit/rules/TestWatcher.java +++ b/src/main/java/org/junit/rules/TestWatcher.java @@ -1,94 +1,166 @@ package org.junit.rules; -import org.junit.internal.AssumptionViolatedException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.AssumptionViolatedException; import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; /** * TestWatcher is a base class for Rules that take note of the testing * action, without modifying it. For example, this class will keep a log of each * passing and failing test: - * + * * <pre> * public static class WatchmanTest { - * private static String watchedLog; - * - * @Rule - * public MethodRule watchman= new TestWatcher() { - * @Override - * protected void failed(Description d) { - * watchedLog+= d + "\n"; - * } - * - * @Override - * protected void succeeded(Description d) { - * watchedLog+= d + " " + "success!\n"; - * } - * }; - * - * @Test - * public void fails() { - * fail(); - * } - * - * @Test - * public void succeeds() { - * } + * private static String watchedLog; + * + * @Rule + * public TestWatcher watchman= new TestWatcher() { + * @Override + * protected void failed(Throwable e, Description description) { + * watchedLog+= description + "\n"; + * } + * + * @Override + * protected void succeeded(Description description) { + * watchedLog+= description + " " + "success!\n"; + * } + * }; + * + * @Test + * public void fails() { + * fail(); + * } + * + * @Test + * public void succeeds() { + * } * } * </pre> + * + * @since 4.9 */ public abstract class TestWatcher implements TestRule { - public Statement apply(final Statement base, final Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - starting(description); - try { - base.evaluate(); - succeeded(description); - } catch (AssumptionViolatedException e) { - throw e; - } catch (Throwable t) { - failed(t, description); - throw t; - } finally { - finished(description); - } - } - }; - } - - /** - * Invoked when a test succeeds - * - * @param description - */ - protected void succeeded(Description description) { - } - - /** - * Invoked when a test fails - * - * @param e - * @param description - */ - protected void failed(Throwable e, Description description) { - } - - /** - * Invoked when a test is about to start - * - * @param description - */ - protected void starting(Description description) { - } - - - /** - * Invoked when a test method finishes (whether passing or failing) - * - * @param description - */ - protected void finished(Description description) { - } + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + List<Throwable> errors = new ArrayList<Throwable>(); + + startingQuietly(description, errors); + try { + base.evaluate(); + succeededQuietly(description, errors); + } catch (@SuppressWarnings("deprecation") org.junit.internal.AssumptionViolatedException e) { + errors.add(e); + skippedQuietly(e, description, errors); + } catch (Throwable e) { + errors.add(e); + failedQuietly(e, description, errors); + } finally { + finishedQuietly(description, errors); + } + + MultipleFailureException.assertEmpty(errors); + } + }; + } + + private void succeededQuietly(Description description, + List<Throwable> errors) { + try { + succeeded(description); + } catch (Throwable e) { + errors.add(e); + } + } + + private void failedQuietly(Throwable e, Description description, + List<Throwable> errors) { + try { + failed(e, description); + } catch (Throwable e1) { + errors.add(e1); + } + } + + @SuppressWarnings("deprecation") + private void skippedQuietly( + org.junit.internal.AssumptionViolatedException e, Description description, + List<Throwable> errors) { + try { + if (e instanceof AssumptionViolatedException) { + skipped((AssumptionViolatedException) e, description); + } else { + skipped(e, description); + } + } catch (Throwable e1) { + errors.add(e1); + } + } + + private void startingQuietly(Description description, + List<Throwable> errors) { + try { + starting(description); + } catch (Throwable e) { + errors.add(e); + } + } + + private void finishedQuietly(Description description, + List<Throwable> errors) { + try { + finished(description); + } catch (Throwable e) { + errors.add(e); + } + } + + /** + * Invoked when a test succeeds + */ + protected void succeeded(Description description) { + } + + /** + * Invoked when a test fails + */ + protected void failed(Throwable e, Description description) { + } + + /** + * Invoked when a test is skipped due to a failed assumption. + */ + @SuppressWarnings("deprecation") + protected void skipped(AssumptionViolatedException e, Description description) { + // For backwards compatibility with JUnit 4.11 and earlier, call the legacy version + org.junit.internal.AssumptionViolatedException asInternalException = e; + skipped(asInternalException, description); + } + + /** + * Invoked when a test is skipped due to a failed assumption. + * + * @deprecated use {@link #skipped(AssumptionViolatedException, Description)} + */ + @Deprecated + protected void skipped( + org.junit.internal.AssumptionViolatedException e, Description description) { + } + + /** + * Invoked when a test is about to start + */ + protected void starting(Description description) { + } + + /** + * Invoked when a test method finishes (whether passing or failing) + */ + protected void finished(Description description) { + } } diff --git a/src/main/java/org/junit/rules/TestWatchman.java b/src/main/java/org/junit/rules/TestWatchman.java index 15daa64..c8d6c71 100644 --- a/src/main/java/org/junit/rules/TestWatchman.java +++ b/src/main/java/org/junit/rules/TestWatchman.java @@ -8,93 +8,84 @@ import org.junit.runners.model.Statement; * TestWatchman is a base class for Rules that take note of the testing * action, without modifying it. For example, this class will keep a log of each * passing and failing test: - * + * * <pre> * public static class WatchmanTest { - * private static String watchedLog; - * - * @Rule - * public MethodRule watchman= new TestWatchman() { - * @Override - * public void failed(Throwable e, FrameworkMethod method) { - * watchedLog+= method.getName() + " " + e.getClass().getSimpleName() - * + "\n"; - * } - * - * @Override - * public void succeeded(FrameworkMethod method) { - * watchedLog+= method.getName() + " " + "success!\n"; - * } - * }; - * - * @Test - * public void fails() { - * fail(); - * } - * - * @Test - * public void succeeds() { - * } + * private static String watchedLog; + * + * @Rule + * public MethodRule watchman= new TestWatchman() { + * @Override + * public void failed(Throwable e, FrameworkMethod method) { + * watchedLog+= method.getName() + " " + e.getClass().getSimpleName() + * + "\n"; + * } + * + * @Override + * public void succeeded(FrameworkMethod method) { + * watchedLog+= method.getName() + " " + "success!\n"; + * } + * }; + * + * @Test + * public void fails() { + * fail(); + * } + * + * @Test + * public void succeeds() { + * } * } * </pre> - * - * @deprecated {@link MethodRule} is deprecated. - * Use {@link TestWatcher} implements {@link TestRule} instead. + * + * @since 4.7 + * @deprecated Use {@link TestWatcher} (which implements {@link TestRule}) instead. */ @Deprecated public class TestWatchman implements MethodRule { - public Statement apply(final Statement base, final FrameworkMethod method, - Object target) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - starting(method); - try { - base.evaluate(); - succeeded(method); - } catch (AssumptionViolatedException e) { - throw e; - } catch (Throwable t) { - failed(t, method); - throw t; - } finally { - finished(method); - } - } - }; - } + public Statement apply(final Statement base, final FrameworkMethod method, + Object target) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + starting(method); + try { + base.evaluate(); + succeeded(method); + } catch (AssumptionViolatedException e) { + throw e; + } catch (Throwable e) { + failed(e, method); + throw e; + } finally { + finished(method); + } + } + }; + } - /** - * Invoked when a test method succeeds - * - * @param method - */ - public void succeeded(FrameworkMethod method) { - } + /** + * Invoked when a test method succeeds + */ + public void succeeded(FrameworkMethod method) { + } - /** - * Invoked when a test method fails - * - * @param e - * @param method - */ - public void failed(Throwable e, FrameworkMethod method) { - } + /** + * Invoked when a test method fails + */ + public void failed(Throwable e, FrameworkMethod method) { + } - /** - * Invoked when a test method is about to start - * - * @param method - */ - public void starting(FrameworkMethod method) { - } + /** + * Invoked when a test method is about to start + */ + public void starting(FrameworkMethod method) { + } - /** - * Invoked when a test method finishes (whether passing or failing) - * - * @param method - */ - public void finished(FrameworkMethod method) { - } + /** + * Invoked when a test method finishes (whether passing or failing) + */ + public void finished(FrameworkMethod method) { + } } diff --git a/src/main/java/org/junit/rules/Timeout.java b/src/main/java/org/junit/rules/Timeout.java index 85ce6d6..45a5bc5 100644 --- a/src/main/java/org/junit/rules/Timeout.java +++ b/src/main/java/org/junit/rules/Timeout.java @@ -1,49 +1,233 @@ -/** - * - */ package org.junit.rules; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.concurrent.TimeUnit; + /** * The Timeout Rule applies the same timeout to all test methods in a class: - * * <pre> - * public static class HasGlobalTimeout { - * public static String log; - * - * @Rule - * public MethodRule globalTimeout= new Timeout(20); - * - * @Test - * public void testInfiniteLoop1() { - * log+= "ran1"; - * for (;;) { - * } - * } - * - * @Test - * public void testInfiniteLoop2() { - * log+= "ran2"; - * for (;;) { - * } - * } + * public static class HasGlobalLongTimeout { + * + * @Rule + * public Timeout globalTimeout= new Timeout(20); + * + * @Test + * public void run1() throws InterruptedException { + * Thread.sleep(100); + * } + * + * @Test + * public void infiniteLoop() { + * while (true) {} + * } * } * </pre> + * <p> + * Each test is run in a new thread. If the specified timeout elapses before + * the test completes, its execution is interrupted via {@link Thread#interrupt()}. + * This happens in interruptable I/O and locks, and methods in {@link Object} + * and {@link Thread} throwing {@link InterruptedException}. + * <p> + * A specified timeout of 0 will be interpreted as not set, however tests will + * still launch from separate threads. This can be useful for disabling timeouts + * in environments where they are dynamically set based on some property. + * + * @since 4.7 */ public class Timeout implements TestRule { - private final int fMillis; - - /** - * @param millis the millisecond timeout - */ - public Timeout(int millis) { - fMillis= millis; - } - - public Statement apply(Statement base, Description description) { - return new FailOnTimeout(base, fMillis); - } -}
\ No newline at end of file + private final long timeout; + private final TimeUnit timeUnit; + private final boolean lookForStuckThread; + + /** + * Returns a new builder for building an instance. + * + * @since 4.12 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Create a {@code Timeout} instance with the timeout specified + * in milliseconds. + * <p> + * This constructor is deprecated. + * <p> + * Instead use {@link #Timeout(long, java.util.concurrent.TimeUnit)}, + * {@link Timeout#millis(long)}, or {@link Timeout#seconds(long)}. + * + * @param millis the maximum time in milliseconds to allow the + * test to run before it should timeout + */ + @Deprecated + public Timeout(int millis) { + this(millis, TimeUnit.MILLISECONDS); + } + + /** + * Create a {@code Timeout} instance with the timeout specified + * at the timeUnit of granularity of the provided {@code TimeUnit}. + * + * @param timeout the maximum time to allow the test to run + * before it should timeout + * @param timeUnit the time unit for the {@code timeout} + * @since 4.12 + */ + public Timeout(long timeout, TimeUnit timeUnit) { + this.timeout = timeout; + this.timeUnit = timeUnit; + lookForStuckThread = false; + } + + /** + * Create a {@code Timeout} instance initialized with values form + * a builder. + * + * @since 4.12 + */ + protected Timeout(Builder builder) { + timeout = builder.getTimeout(); + timeUnit = builder.getTimeUnit(); + lookForStuckThread = builder.getLookingForStuckThread(); + } + + /** + * Creates a {@link Timeout} that will timeout a test after the + * given duration, in milliseconds. + * + * @since 4.12 + */ + public static Timeout millis(long millis) { + return new Timeout(millis, TimeUnit.MILLISECONDS); + } + + /** + * Creates a {@link Timeout} that will timeout a test after the + * given duration, in seconds. + * + * @since 4.12 + */ + public static Timeout seconds(long seconds) { + return new Timeout(seconds, TimeUnit.SECONDS); + } + + /** + * Gets the timeout configured for this rule, in the given units. + * + * @since 4.12 + */ + protected final long getTimeout(TimeUnit unit) { + return unit.convert(timeout, timeUnit); + } + + /** + * Gets whether this {@code Timeout} will look for a stuck thread + * when the test times out. + * + * @since 4.12 + */ + protected final boolean getLookingForStuckThread() { + return lookForStuckThread; + } + + /** + * Creates a {@link Statement} that will run the given + * {@code statement}, and timeout the operation based + * on the values configured in this rule. Subclasses + * can override this method for different behavior. + * + * @since 4.12 + */ + protected Statement createFailOnTimeoutStatement( + Statement statement) throws Exception { + return FailOnTimeout.builder() + .withTimeout(timeout, timeUnit) + .withLookingForStuckThread(lookForStuckThread) + .build(statement); + } + + public Statement apply(Statement base, Description description) { + try { + return createFailOnTimeoutStatement(base); + } catch (final Exception e) { + return new Statement() { + @Override public void evaluate() throws Throwable { + throw new RuntimeException("Invalid parameters for Timeout", e); + } + }; + } + } + + /** + * Builder for {@link Timeout}. + * + * @since 4.12 + */ + public static class Builder { + private boolean lookForStuckThread = false; + private long timeout = 0; + private TimeUnit timeUnit = TimeUnit.SECONDS; + + protected Builder() { + } + + /** + * Specifies the time to wait before timing out the test. + * + * <p>If this is not called, or is called with a + * {@code timeout} of {@code 0}, the returned {@code Timeout} + * rule instance will cause the tests to wait forever to + * complete, however the tests will still launch from a + * separate thread. This can be useful for disabling timeouts + * in environments where they are dynamically set based on + * some property. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code this} for method chaining. + */ + public Builder withTimeout(long timeout, TimeUnit unit) { + this.timeout = timeout; + this.timeUnit = unit; + return this; + } + + protected long getTimeout() { + return timeout; + } + + protected TimeUnit getTimeUnit() { + return timeUnit; + } + + /** + * Specifies whether to look for a stuck thread. If a timeout occurs and this + * feature is enabled, the rule will look for a thread that appears to be stuck + * and dump its backtrace. This feature is experimental. Behavior may change + * after the 4.12 release in response to feedback. + * + * @param enable {@code true} to enable the feature + * @return {@code this} for method chaining. + */ + public Builder withLookingForStuckThread(boolean enable) { + this.lookForStuckThread = enable; + return this; + } + + protected boolean getLookingForStuckThread() { + return lookForStuckThread; + } + + + /** + * Builds a {@link Timeout} instance using the values in this builder., + */ + public Timeout build() { + return new Timeout(this); + } + } +} diff --git a/src/main/java/org/junit/rules/Verifier.java b/src/main/java/org/junit/rules/Verifier.java index be1a55e..7a03b0c 100644 --- a/src/main/java/org/junit/rules/Verifier.java +++ b/src/main/java/org/junit/rules/Verifier.java @@ -7,39 +7,41 @@ import org.junit.runners.model.Statement; * Verifier is a base class for Rules like ErrorCollector, which can turn * otherwise passing test methods into failing tests if a verification check is * failed - * + * * <pre> - * public static class ErrorLogVerifier() { + * public static class ErrorLogVerifier { * private ErrorLog errorLog = new ErrorLog(); - * + * * @Rule - * public MethodRule verifier = new Verifier() { + * public Verifier verifier = new Verifier() { * @Override public void verify() { * assertTrue(errorLog.isEmpty()); * } * } - * + * * @Test public void testThatMightWriteErrorLog() { * // ... * } * } * </pre> + * + * @since 4.7 */ -public class Verifier implements TestRule { - public Statement apply(final Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - base.evaluate(); - verify(); - } - }; - } +public abstract class Verifier implements TestRule { + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + verify(); + } + }; + } - /** - * Override this to add verification logic. Overrides should throw an - * exception to indicate that verification failed. - */ - protected void verify() throws Throwable { - } + /** + * Override this to add verification logic. Overrides should throw an + * exception to indicate that verification failed. + */ + protected void verify() throws Throwable { + } } diff --git a/src/main/java/org/junit/runner/Computer.java b/src/main/java/org/junit/runner/Computer.java index 939f702..8bb4b20 100644 --- a/src/main/java/org/junit/runner/Computer.java +++ b/src/main/java/org/junit/runner/Computer.java @@ -8,33 +8,35 @@ import org.junit.runners.model.RunnerBuilder; * Represents a strategy for computing runners and suites. * WARNING: this class is very likely to undergo serious changes in version 4.8 and * beyond. + * + * @since 4.6 */ public class Computer { - /** - * Returns a new default computer, which runs tests in serial order - */ - public static Computer serial() { - return new Computer(); - } + /** + * Returns a new default computer, which runs tests in serial order + */ + public static Computer serial() { + return new Computer(); + } - /** - * Create a suite for {@code classes}, building Runners with {@code builder}. - * Throws an InitializationError if Runner construction fails - */ - public Runner getSuite(final RunnerBuilder builder, - Class<?>[] classes) throws InitializationError { - return new Suite(new RunnerBuilder() { - @Override - public Runner runnerForClass(Class<?> testClass) throws Throwable { - return getRunner(builder, testClass); - } - }, classes); - } + /** + * Create a suite for {@code classes}, building Runners with {@code builder}. + * Throws an InitializationError if Runner construction fails + */ + public Runner getSuite(final RunnerBuilder builder, + Class<?>[] classes) throws InitializationError { + return new Suite(new RunnerBuilder() { + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + return getRunner(builder, testClass); + } + }, classes); + } - /** - * Create a single-class runner for {@code testClass}, using {@code builder} - */ - protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable { - return builder.runnerForClass(testClass); - } + /** + * Create a single-class runner for {@code testClass}, using {@code builder} + */ + protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable { + return builder.runnerForClass(testClass); + } } diff --git a/src/main/java/org/junit/runner/Describable.java b/src/main/java/org/junit/runner/Describable.java index e59cc01..1514141 100644 --- a/src/main/java/org/junit/runner/Describable.java +++ b/src/main/java/org/junit/runner/Describable.java @@ -3,10 +3,12 @@ package org.junit.runner; /** * Represents an object that can describe itself + * + * @since 4.5 */ public interface Describable { - /** - * @return a {@link Description} showing the tests to be run by the receiver - */ - public abstract Description getDescription(); + /** + * @return a {@link Description} showing the tests to be run by the receiver + */ + public abstract Description getDescription(); }
\ No newline at end of file diff --git a/src/main/java/org/junit/runner/FilterFactories.java b/src/main/java/org/junit/runner/FilterFactories.java new file mode 100644 index 0000000..020d394 --- /dev/null +++ b/src/main/java/org/junit/runner/FilterFactories.java @@ -0,0 +1,82 @@ +package org.junit.runner;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory.FilterNotCreatedException;
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Utility class whose methods create a {@link FilterFactory}.
+ */
+class FilterFactories {
+ /**
+ * Creates a {@link Filter}.
+ *
+ * A filter specification is of the form "package.of.FilterFactory=args-to-filter-factory" or
+ * "package.of.FilterFactory".
+ *
+ * @param request the request that will be filtered
+ * @param filterSpec the filter specification
+ * @throws org.junit.runner.FilterFactory.FilterNotCreatedException
+ */
+ public static Filter createFilterFromFilterSpec(Request request, String filterSpec)
+ throws FilterFactory.FilterNotCreatedException {
+ Description topLevelDescription = request.getRunner().getDescription();
+ String[] tuple;
+
+ if (filterSpec.contains("=")) {
+ tuple = filterSpec.split("=", 2);
+ } else {
+ tuple = new String[]{ filterSpec, "" };
+ }
+
+ return createFilter(tuple[0], new FilterFactoryParams(topLevelDescription, tuple[1]));
+ }
+
+ /**
+ * Creates a {@link Filter}.
+ *
+ * @param filterFactoryFqcn The fully qualified class name of the {@link FilterFactory}
+ * @param params The arguments to the {@link FilterFactory}
+ */
+ public static Filter createFilter(String filterFactoryFqcn, FilterFactoryParams params)
+ throws FilterFactory.FilterNotCreatedException {
+ FilterFactory filterFactory = createFilterFactory(filterFactoryFqcn);
+
+ return filterFactory.createFilter(params);
+ }
+
+ /**
+ * Creates a {@link Filter}.
+ *
+ * @param filterFactoryClass The class of the {@link FilterFactory}
+ * @param params The arguments to the {@link FilterFactory}
+ *
+ */
+ public static Filter createFilter(Class<? extends FilterFactory> filterFactoryClass, FilterFactoryParams params)
+ throws FilterFactory.FilterNotCreatedException {
+ FilterFactory filterFactory = createFilterFactory(filterFactoryClass);
+
+ return filterFactory.createFilter(params);
+ }
+
+ static FilterFactory createFilterFactory(String filterFactoryFqcn) throws FilterNotCreatedException {
+ Class<? extends FilterFactory> filterFactoryClass;
+
+ try {
+ filterFactoryClass = Classes.getClass(filterFactoryFqcn).asSubclass(FilterFactory.class);
+ } catch (Exception e) {
+ throw new FilterNotCreatedException(e);
+ }
+
+ return createFilterFactory(filterFactoryClass);
+ }
+
+ static FilterFactory createFilterFactory(Class<? extends FilterFactory> filterFactoryClass)
+ throws FilterNotCreatedException {
+ try {
+ return filterFactoryClass.getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new FilterNotCreatedException(e);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/FilterFactory.java b/src/main/java/org/junit/runner/FilterFactory.java new file mode 100644 index 0000000..57b4eaa --- /dev/null +++ b/src/main/java/org/junit/runner/FilterFactory.java @@ -0,0 +1,24 @@ +package org.junit.runner;
+
+import org.junit.runner.manipulation.Filter;
+
+/**
+ * Extend this class to create a factory that creates {@link Filter}.
+ */
+public interface FilterFactory {
+ /**
+ * Creates a {@link Filter} given a {@link FilterFactoryParams} argument.
+ *
+ * @param params Parameters needed to create the {@link Filter}
+ */
+ Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException;
+
+ /**
+ * Exception thrown if the {@link Filter} cannot be created.
+ */
+ public static class FilterNotCreatedException extends Exception {
+ public FilterNotCreatedException(Exception exception) {
+ super(exception.getMessage(), exception);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/FilterFactoryParams.java b/src/main/java/org/junit/runner/FilterFactoryParams.java new file mode 100644 index 0000000..1e74ab9 --- /dev/null +++ b/src/main/java/org/junit/runner/FilterFactoryParams.java @@ -0,0 +1,23 @@ +package org.junit.runner;
+
+public final class FilterFactoryParams {
+ private final Description topLevelDescription;
+ private final String args;
+
+ public FilterFactoryParams(Description topLevelDescription, String args) {
+ if (args == null || topLevelDescription == null) {
+ throw new NullPointerException();
+ }
+
+ this.topLevelDescription = topLevelDescription;
+ this.args = args;
+ }
+
+ public String getArgs() {
+ return args;
+ }
+
+ public Description getTopLevelDescription() {
+ return topLevelDescription;
+ }
+}
diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java new file mode 100644 index 0000000..434157c --- /dev/null +++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java @@ -0,0 +1,149 @@ +package org.junit.runner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.internal.Classes;
+import org.junit.runner.FilterFactory.FilterNotCreatedException;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runners.model.InitializationError;
+
+class JUnitCommandLineParseResult {
+ private final List<String> filterSpecs = new ArrayList<String>();
+ private final List<Class<?>> classes = new ArrayList<Class<?>>();
+ private final List<Throwable> parserErrors = new ArrayList<Throwable>();
+
+ /**
+ * Do not use. Testing purposes only.
+ */
+ JUnitCommandLineParseResult() {}
+
+ /**
+ * Returns filter specs parsed from command line.
+ */
+ public List<String> getFilterSpecs() {
+ return Collections.unmodifiableList(filterSpecs);
+ }
+
+ /**
+ * Returns test classes parsed from command line.
+ */
+ public List<Class<?>> getClasses() {
+ return Collections.unmodifiableList(classes);
+ }
+
+ /**
+ * Parses the arguments.
+ *
+ * @param args Arguments
+ */
+ public static JUnitCommandLineParseResult parse(String[] args) {
+ JUnitCommandLineParseResult result = new JUnitCommandLineParseResult();
+
+ result.parseArgs(args);
+
+ return result;
+ }
+
+ private void parseArgs(String[] args) {
+ parseParameters(parseOptions(args));
+ }
+
+ String[] parseOptions(String... args) {
+ for (int i = 0; i != args.length; ++i) {
+ String arg = args[i];
+
+ if (arg.equals("--")) {
+ return copyArray(args, i + 1, args.length);
+ } else if (arg.startsWith("--")) {
+ if (arg.startsWith("--filter=") || arg.equals("--filter")) {
+ String filterSpec;
+ if (arg.equals("--filter")) {
+ ++i;
+
+ if (i < args.length) {
+ filterSpec = args[i];
+ } else {
+ parserErrors.add(new CommandLineParserError(arg + " value not specified"));
+ break;
+ }
+ } else {
+ filterSpec = arg.substring(arg.indexOf('=') + 1);
+ }
+
+ filterSpecs.add(filterSpec);
+ } else {
+ parserErrors.add(new CommandLineParserError("JUnit knows nothing about the " + arg + " option"));
+ }
+ } else {
+ return copyArray(args, i, args.length);
+ }
+ }
+
+ return new String[]{};
+ }
+
+ private String[] copyArray(String[] args, int from, int to) {
+ ArrayList<String> result = new ArrayList<String>();
+
+ for (int j = from; j != to; ++j) {
+ result.add(args[j]);
+ }
+
+ return result.toArray(new String[result.size()]);
+ }
+
+ void parseParameters(String[] args) {
+ for (String arg : args) {
+ try {
+ classes.add(Classes.getClass(arg));
+ } catch (ClassNotFoundException e) {
+ parserErrors.add(new IllegalArgumentException("Could not find class [" + arg + "]", e));
+ }
+ }
+ }
+
+ private Request errorReport(Throwable cause) {
+ return Request.errorReport(JUnitCommandLineParseResult.class, cause);
+ }
+
+ /**
+ * Creates a {@link Request}.
+ *
+ * @param computer {@link Computer} to be used.
+ */
+ public Request createRequest(Computer computer) {
+ if (parserErrors.isEmpty()) {
+ Request request = Request.classes(
+ computer, classes.toArray(new Class<?>[classes.size()]));
+ return applyFilterSpecs(request);
+ } else {
+ return errorReport(new InitializationError(parserErrors));
+ }
+ }
+
+ private Request applyFilterSpecs(Request request) {
+ try {
+ for (String filterSpec : filterSpecs) {
+ Filter filter = FilterFactories.createFilterFromFilterSpec(
+ request, filterSpec);
+ request = request.filterWith(filter);
+ }
+ return request;
+ } catch (FilterNotCreatedException e) {
+ return errorReport(e);
+ }
+ }
+
+ /**
+ * Exception used if there's a problem parsing the command line.
+ */
+ public static class CommandLineParserError extends Exception {
+ private static final long serialVersionUID= 1L;
+
+ public CommandLineParserError(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/JUnitCore.java b/src/main/java/org/junit/runner/JUnitCore.java index 2fcd3b3..c1479e0 100644 --- a/src/main/java/org/junit/runner/JUnitCore.java +++ b/src/main/java/org/junit/runner/JUnitCore.java @@ -1,186 +1,167 @@ package org.junit.runner; -import java.util.ArrayList; -import java.util.List; - import junit.runner.Version; import org.junit.internal.JUnitSystem; import org.junit.internal.RealSystem; import org.junit.internal.TextListener; import org.junit.internal.runners.JUnit38ClassRunner; -import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.runner.notification.RunNotifier; /** - * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests, - * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run + * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests, + * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run * <code>java org.junit.runner.JUnitCore TestClass1 TestClass2 ...</code>. - * For one-shot test runs, use the static method {@link #runClasses(Class[])}. + * For one-shot test runs, use the static method {@link #runClasses(Class[])}. * If you want to add special listeners, * create an instance of {@link org.junit.runner.JUnitCore} first and use it to run the tests. - * + * * @see org.junit.runner.Result * @see org.junit.runner.notification.RunListener * @see org.junit.runner.Request + * @since 4.0 */ public class JUnitCore { - private RunNotifier fNotifier; - - /** - * Create a new <code>JUnitCore</code> to run tests. - */ - public JUnitCore() { - fNotifier= new RunNotifier(); - } - - /** - * Run the tests contained in the classes named in the <code>args</code>. - * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1. - * Write feedback while tests are running and write - * stack traces for all failed tests after the tests all complete. - * @param args names of classes in which to find tests to run - */ - public static void main(String... args) { - runMainAndExit(new RealSystem(), args); - } - - /** - * Do not use. Testing purposes only. - * @param system - */ - public static void runMainAndExit(JUnitSystem system, String... args) { - Result result= new JUnitCore().runMain(system, args); - system.exit(result.wasSuccessful() ? 0 : 1); - } - - /** - * Run the tests contained in <code>classes</code>. Write feedback while the tests - * are running and write stack traces for all failed tests after all tests complete. This is - * similar to {@link #main(String[])}, but intended to be used programmatically. - * @param computer Helps construct Runners from classes - * @param classes Classes in which to find tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public static Result runClasses(Computer computer, Class<?>... classes) { - return new JUnitCore().run(computer, classes); - } - /** - * Run the tests contained in <code>classes</code>. Write feedback while the tests - * are running and write stack traces for all failed tests after all tests complete. This is - * similar to {@link #main(String[])}, but intended to be used programmatically. - * @param classes Classes in which to find tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public static Result runClasses(Class<?>... classes) { - return new JUnitCore().run(defaultComputer(), classes); - } - - /** - * Do not use. Testing purposes only. - * @param system - */ - public Result runMain(JUnitSystem system, String... args) { - system.out().println("JUnit version " + Version.id()); - List<Class<?>> classes= new ArrayList<Class<?>>(); - List<Failure> missingClasses= new ArrayList<Failure>(); - for (String each : args) - try { - classes.add(Class.forName(each)); - } catch (ClassNotFoundException e) { - system.out().println("Could not find class: " + each); - Description description= Description.createSuiteDescription(each); - Failure failure= new Failure(description, e); - missingClasses.add(failure); - } - RunListener listener= new TextListener(system); - addListener(listener); - Result result= run(classes.toArray(new Class[0])); - for (Failure each : missingClasses) - result.getFailures().add(each); - return result; - } - - /** - * @return the version number of this release - */ - public String getVersion() { - return Version.id(); - } - - /** - * Run all the tests in <code>classes</code>. - * @param classes the classes containing tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Class<?>... classes) { - return run(Request.classes(defaultComputer(), classes)); - } - - /** - * Run all the tests in <code>classes</code>. - * @param computer Helps construct Runners from classes - * @param classes the classes containing tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Computer computer, Class<?>... classes) { - return run(Request.classes(computer, classes)); - } - - /** - * Run all the tests contained in <code>request</code>. - * @param request the request describing tests - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(Request request) { - return run(request.getRunner()); - } - - /** - * Run all the tests contained in JUnit 3.8.x <code>test</code>. Here for backward compatibility. - * @param test the old-style test - * @return a {@link Result} describing the details of the test run and the failed tests. - */ - public Result run(junit.framework.Test test) { - return run(new JUnit38ClassRunner(test)); - } - - /** - * Do not use. Testing purposes only. - */ - public Result run(Runner runner) { - Result result= new Result(); - RunListener listener= result.createListener(); - fNotifier.addFirstListener(listener); - try { - fNotifier.fireTestRunStarted(runner.getDescription()); - runner.run(fNotifier); - fNotifier.fireTestRunFinished(result); - } finally { - removeListener(listener); - } - return result; - } - - /** - * Add a listener to be notified as the tests run. - * @param listener the listener to add - * @see org.junit.runner.notification.RunListener - */ - public void addListener(RunListener listener) { - fNotifier.addListener(listener); - } - - /** - * Remove a listener. - * @param listener the listener to remove - */ - public void removeListener(RunListener listener) { - fNotifier.removeListener(listener); - } - - static Computer defaultComputer() { - return new Computer(); - } + private final RunNotifier notifier = new RunNotifier(); + + /** + * Run the tests contained in the classes named in the <code>args</code>. + * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1. + * Write feedback while tests are running and write + * stack traces for all failed tests after the tests all complete. + * + * @param args names of classes in which to find tests to run + */ + public static void main(String... args) { + Result result = new JUnitCore().runMain(new RealSystem(), args); + System.exit(result.wasSuccessful() ? 0 : 1); + } + + /** + * Run the tests contained in <code>classes</code>. Write feedback while the tests + * are running and write stack traces for all failed tests after all tests complete. This is + * similar to {@link #main(String[])}, but intended to be used programmatically. + * + * @param classes Classes in which to find tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public static Result runClasses(Class<?>... classes) { + return runClasses(defaultComputer(), classes); + } + + /** + * Run the tests contained in <code>classes</code>. Write feedback while the tests + * are running and write stack traces for all failed tests after all tests complete. This is + * similar to {@link #main(String[])}, but intended to be used programmatically. + * + * @param computer Helps construct Runners from classes + * @param classes Classes in which to find tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public static Result runClasses(Computer computer, Class<?>... classes) { + return new JUnitCore().run(computer, classes); + } + + /** + * @param system + * @param args from main() + */ + Result runMain(JUnitSystem system, String... args) { + system.out().println("JUnit version " + Version.id()); + + JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args); + + RunListener listener = new TextListener(system); + addListener(listener); + + return run(jUnitCommandLineParseResult.createRequest(defaultComputer())); + } + + /** + * @return the version number of this release + */ + public String getVersion() { + return Version.id(); + } + + /** + * Run all the tests in <code>classes</code>. + * + * @param classes the classes containing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Class<?>... classes) { + return run(defaultComputer(), classes); + } + + /** + * Run all the tests in <code>classes</code>. + * + * @param computer Helps construct Runners from classes + * @param classes the classes containing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Computer computer, Class<?>... classes) { + return run(Request.classes(computer, classes)); + } + + /** + * Run all the tests contained in <code>request</code>. + * + * @param request the request describing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Request request) { + return run(request.getRunner()); + } + + /** + * Run all the tests contained in JUnit 3.8.x <code>test</code>. Here for backward compatibility. + * + * @param test the old-style test + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(junit.framework.Test test) { + return run(new JUnit38ClassRunner(test)); + } + + /** + * Do not use. Testing purposes only. + */ + public Result run(Runner runner) { + Result result = new Result(); + RunListener listener = result.createListener(); + notifier.addFirstListener(listener); + try { + notifier.fireTestRunStarted(runner.getDescription()); + runner.run(notifier); + notifier.fireTestRunFinished(result); + } finally { + removeListener(listener); + } + return result; + } + + /** + * Add a listener to be notified as the tests run. + * + * @param listener the listener to add + * @see org.junit.runner.notification.RunListener + */ + public void addListener(RunListener listener) { + notifier.addListener(listener); + } + + /** + * Remove a listener. + * + * @param listener the listener to remove + */ + public void removeListener(RunListener listener) { + notifier.removeListener(listener); + } + static Computer defaultComputer() { + return new Computer(); + } } diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java index 310b915..79c0f1e 100644 --- a/src/main/java/org/junit/runner/Request.java +++ b/src/main/java/org/junit/runner/Request.java @@ -11,151 +11,160 @@ import org.junit.runner.manipulation.Filter; import org.junit.runners.model.InitializationError; /** - * <p>A <code>Request</code> is an abstract description of tests to be run. Older versions of + * A <code>Request</code> is an abstract description of tests to be run. Older versions of * JUnit did not need such a concept--tests to be run were described either by classes containing * tests or a tree of {@link org.junit.Test}s. However, we want to support filtering and sorting, * so we need a more abstract specification than the tests themselves and a richer - * specification than just the classes.</p> - * - * <p>The flow when JUnit runs tests is that a <code>Request</code> specifies some tests to be run -> - * a {@link org.junit.runner.Runner} is created for each class implied by the <code>Request</code> -> - * the {@link org.junit.runner.Runner} returns a detailed {@link org.junit.runner.Description} - * which is a tree structure of the tests to be run.</p> + * specification than just the classes. + * + * <p>The flow when JUnit runs tests is that a <code>Request</code> specifies some tests to be run -> + * a {@link org.junit.runner.Runner} is created for each class implied by the <code>Request</code> -> + * the {@link org.junit.runner.Runner} returns a detailed {@link org.junit.runner.Description} + * which is a tree structure of the tests to be run. + * + * @since 4.0 */ public abstract class Request { - /** - * Create a <code>Request</code> that, when processed, will run a single test. - * This is done by filtering out all other tests. This method is used to support rerunning - * single tests. - * @param clazz the class of the test - * @param methodName the name of the test - * @return a <code>Request</code> that will cause a single test be run - */ - public static Request method(Class<?> clazz, String methodName) { - Description method= Description.createTestDescription(clazz, methodName); - return Request.aClass(clazz).filterWith(method); - } + /** + * Create a <code>Request</code> that, when processed, will run a single test. + * This is done by filtering out all other tests. This method is used to support rerunning + * single tests. + * + * @param clazz the class of the test + * @param methodName the name of the test + * @return a <code>Request</code> that will cause a single test be run + */ + public static Request method(Class<?> clazz, String methodName) { + Description method = Description.createTestDescription(clazz, methodName); + return Request.aClass(clazz).filterWith(method); + } - /** - * Create a <code>Request</code> that, when processed, will run all the tests - * in a class. The odd name is necessary because <code>class</code> is a reserved word. - * @param clazz the class containing the tests - * @return a <code>Request</code> that will cause all tests in the class to be run - */ - public static Request aClass(Class<?> clazz) { - return new ClassRequest(clazz); - } + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a class. The odd name is necessary because <code>class</code> is a reserved word. + * + * @param clazz the class containing the tests + * @return a <code>Request</code> that will cause all tests in the class to be run + */ + public static Request aClass(Class<?> clazz) { + return new ClassRequest(clazz); + } - /** - * Create a <code>Request</code> that, when processed, will run all the tests - * in a class. If the class has a suite() method, it will be ignored. - * @param clazz the class containing the tests - * @return a <code>Request</code> that will cause all tests in the class to be run - */ - public static Request classWithoutSuiteMethod(Class<?> clazz) { - return new ClassRequest(clazz, false); - } + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a class. If the class has a suite() method, it will be ignored. + * + * @param clazz the class containing the tests + * @return a <code>Request</code> that will cause all tests in the class to be run + */ + public static Request classWithoutSuiteMethod(Class<?> clazz) { + return new ClassRequest(clazz, false); + } - /** - * Create a <code>Request</code> that, when processed, will run all the tests - * in a set of classes. - * @param computer Helps construct Runners from classes - * @param classes the classes containing the tests - * @return a <code>Request</code> that will cause all tests in the classes to be run - */ - public static Request classes(Computer computer, Class<?>... classes) { - try { - AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true); - Runner suite= computer.getSuite(builder, classes); - return runner(suite); - } catch (InitializationError e) { - throw new RuntimeException( - "Bug in saff's brain: Suite constructor, called as above, should always complete"); - } - } + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a set of classes. + * + * @param computer Helps construct Runners from classes + * @param classes the classes containing the tests + * @return a <code>Request</code> that will cause all tests in the classes to be run + */ + public static Request classes(Computer computer, Class<?>... classes) { + try { + AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); + Runner suite = computer.getSuite(builder, classes); + return runner(suite); + } catch (InitializationError e) { + throw new RuntimeException( + "Bug in saff's brain: Suite constructor, called as above, should always complete"); + } + } - /** - * Create a <code>Request</code> that, when processed, will run all the tests - * in a set of classes with the default <code>Computer</code>. - * @param classes the classes containing the tests - * @return a <code>Request</code> that will cause all tests in the classes to be run - */ - public static Request classes(Class<?>... classes) { - return classes(JUnitCore.defaultComputer(), classes); - } - + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a set of classes with the default <code>Computer</code>. + * + * @param classes the classes containing the tests + * @return a <code>Request</code> that will cause all tests in the classes to be run + */ + public static Request classes(Class<?>... classes) { + return classes(JUnitCore.defaultComputer(), classes); + } - /** - * Not used within JUnit. Clients should simply instantiate ErrorReportingRunner themselves - */ - @Deprecated - public static Request errorReport(Class<?> klass, Throwable cause) { - return runner(new ErrorReportingRunner(klass, cause)); - } - /** - * @param runner the runner to return - * @return a <code>Request</code> that will run the given runner when invoked - */ - public static Request runner(final Runner runner) { - return new Request(){ - @Override - public Runner getRunner() { - return runner; - } - }; - } + /** + * Creates a {@link Request} that, when processed, will report an error for the given + * test class with the given cause. + */ + public static Request errorReport(Class<?> klass, Throwable cause) { + return runner(new ErrorReportingRunner(klass, cause)); + } - /** - * Returns a {@link Runner} for this Request - * @return corresponding {@link Runner} for this Request - */ - public abstract Runner getRunner(); + /** + * @param runner the runner to return + * @return a <code>Request</code> that will run the given runner when invoked + */ + public static Request runner(final Runner runner) { + return new Request() { + @Override + public Runner getRunner() { + return runner; + } + }; + } - /** - * Returns a Request that only contains those tests that should run when - * <code>filter</code> is applied - * @param filter The {@link Filter} to apply to this Request - * @return the filtered Request - */ - public Request filterWith(Filter filter) { - return new FilterRequest(this, filter); - } + /** + * Returns a {@link Runner} for this Request + * + * @return corresponding {@link Runner} for this Request + */ + public abstract Runner getRunner(); - /** - * Returns a Request that only runs contains tests whose {@link Description} - * equals <code>desiredDescription</code> - * @param desiredDescription {@link Description} of those tests that should be run - * @return the filtered Request - */ - public Request filterWith(final Description desiredDescription) { - return filterWith(Filter.matchMethodDescription(desiredDescription)); - } + /** + * Returns a Request that only contains those tests that should run when + * <code>filter</code> is applied + * + * @param filter The {@link Filter} to apply to this Request + * @return the filtered Request + */ + public Request filterWith(Filter filter) { + return new FilterRequest(this, filter); + } - /** - * Returns a Request whose Tests can be run in a certain order, defined by - * <code>comparator</code> - * - * For example, here is code to run a test suite in alphabetical order: - * - * <pre> - private static Comparator<Description> forward() { - return new Comparator<Description>() { - public int compare(Description o1, Description o2) { - return o1.getDisplayName().compareTo(o2.getDisplayName()); - } - }; - } - - public static main() { - new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward())); - } - * </pre> - * - * @param comparator definition of the order of the tests in this Request - * @return a Request with ordered Tests - */ - public Request sortWith(Comparator<Description> comparator) { - return new SortingRequest(this, comparator); - } + /** + * Returns a Request that only runs contains tests whose {@link Description} + * equals <code>desiredDescription</code> + * + * @param desiredDescription {@link Description} of those tests that should be run + * @return the filtered Request + */ + public Request filterWith(final Description desiredDescription) { + return filterWith(Filter.matchMethodDescription(desiredDescription)); + } + + /** + * Returns a Request whose Tests can be run in a certain order, defined by + * <code>comparator</code> + * <p> + * For example, here is code to run a test suite in alphabetical order: + * <pre> + * private static Comparator<Description> forward() { + * return new Comparator<Description>() { + * public int compare(Description o1, Description o2) { + * return o1.getDisplayName().compareTo(o2.getDisplayName()); + * } + * }; + * } + * + * public static main() { + * new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward())); + * } + * </pre> + * + * @param comparator definition of the order of the tests in this Request + * @return a Request with ordered Tests + */ + public Request sortWith(Comparator<Description> comparator) { + return new SortingRequest(this, comparator); + } } diff --git a/src/main/java/org/junit/runner/Result.java b/src/main/java/org/junit/runner/Result.java index edfb97c..73ad059 100644 --- a/src/main/java/org/junit/runner/Result.java +++ b/src/main/java/org/junit/runner/Result.java @@ -1,106 +1,196 @@ package org.junit.runner; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; import java.io.Serializable; import java.util.ArrayList; -import java.util.List; import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; /** - * A <code>Result</code> collects and summarizes information from running multiple - * tests. Since tests are expected to run correctly, successful tests are only noted in - * the count of tests that ran. + * A <code>Result</code> collects and summarizes information from running multiple tests. + * All tests are counted -- additional information is collected from tests that fail. + * + * @since 4.0 */ public class Result implements Serializable { - private static final long serialVersionUID = 1L; - private AtomicInteger fCount = new AtomicInteger(); - private AtomicInteger fIgnoreCount= new AtomicInteger(); - private final List<Failure> fFailures= Collections.synchronizedList(new ArrayList<Failure>()); - private long fRunTime= 0; - private long fStartTime; - - /** - * @return the number of tests run - */ - public int getRunCount() { - return fCount.get(); - } - - /** - * @return the number of tests that failed during the run - */ - public int getFailureCount() { - return fFailures.size(); - } - - /** - * @return the number of milliseconds it took to run the entire suite to run - */ - public long getRunTime() { - return fRunTime; - } - - /** - * @return the {@link Failure}s describing tests that failed and the problems they encountered - */ - public List<Failure> getFailures() { - return fFailures; - } - - /** - * @return the number of tests ignored during the run - */ - public int getIgnoreCount() { - return fIgnoreCount.get(); - } - - /** - * @return <code>true</code> if all tests succeeded - */ - public boolean wasSuccessful() { - return getFailureCount() == 0; - } - - private class Listener extends RunListener { - @Override - public void testRunStarted(Description description) throws Exception { - fStartTime= System.currentTimeMillis(); - } - - @Override - public void testRunFinished(Result result) throws Exception { - long endTime= System.currentTimeMillis(); - fRunTime+= endTime - fStartTime; - } - - @Override - public void testFinished(Description description) throws Exception { - fCount.getAndIncrement(); - } - - @Override - public void testFailure(Failure failure) throws Exception { - fFailures.add(failure); - } - - @Override - public void testIgnored(Description description) throws Exception { - fIgnoreCount.getAndIncrement(); - } - - @Override - public void testAssumptionFailure(Failure failure) { - // do nothing: same as passing (for 4.5; may change in 4.6) - } - } - - /** - * Internal use only. - */ - public RunListener createListener() { - return new Listener(); - } + private static final long serialVersionUID = 1L; + private static final ObjectStreamField[] serialPersistentFields = + ObjectStreamClass.lookup(SerializedForm.class).getFields(); + private final AtomicInteger count; + private final AtomicInteger ignoreCount; + private final CopyOnWriteArrayList<Failure> failures; + private final AtomicLong runTime; + private final AtomicLong startTime; + + /** Only set during deserialization process. */ + private SerializedForm serializedForm; + + public Result() { + count = new AtomicInteger(); + ignoreCount = new AtomicInteger(); + failures = new CopyOnWriteArrayList<Failure>(); + runTime = new AtomicLong(); + startTime = new AtomicLong(); + } + + private Result(SerializedForm serializedForm) { + count = serializedForm.fCount; + ignoreCount = serializedForm.fIgnoreCount; + failures = new CopyOnWriteArrayList<Failure>(serializedForm.fFailures); + runTime = new AtomicLong(serializedForm.fRunTime); + startTime = new AtomicLong(serializedForm.fStartTime); + } + + /** + * @return the number of tests run + */ + public int getRunCount() { + return count.get(); + } + + /** + * @return the number of tests that failed during the run + */ + public int getFailureCount() { + return failures.size(); + } + + /** + * @return the number of milliseconds it took to run the entire suite to run + */ + public long getRunTime() { + return runTime.get(); + } + + /** + * @return the {@link Failure}s describing tests that failed and the problems they encountered + */ + public List<Failure> getFailures() { + return failures; + } + + /** + * @return the number of tests ignored during the run + */ + public int getIgnoreCount() { + return ignoreCount.get(); + } + + /** + * @return <code>true</code> if all tests succeeded + */ + public boolean wasSuccessful() { + return getFailureCount() == 0; + } + + private void writeObject(ObjectOutputStream s) throws IOException { + SerializedForm serializedForm = new SerializedForm(this); + serializedForm.serialize(s); + } + + private void readObject(ObjectInputStream s) + throws ClassNotFoundException, IOException { + serializedForm = SerializedForm.deserialize(s); + } + + private Object readResolve() { + return new Result(serializedForm); + } + + @RunListener.ThreadSafe + private class Listener extends RunListener { + @Override + public void testRunStarted(Description description) throws Exception { + startTime.set(System.currentTimeMillis()); + } + + @Override + public void testRunFinished(Result result) throws Exception { + long endTime = System.currentTimeMillis(); + runTime.addAndGet(endTime - startTime.get()); + } + + @Override + public void testFinished(Description description) throws Exception { + count.getAndIncrement(); + } + + @Override + public void testFailure(Failure failure) throws Exception { + failures.add(failure); + } + + @Override + public void testIgnored(Description description) throws Exception { + ignoreCount.getAndIncrement(); + } + + @Override + public void testAssumptionFailure(Failure failure) { + // do nothing: same as passing (for 4.5; may change in 4.6) + } + } + + /** + * Internal use only. + */ + public RunListener createListener() { + return new Listener(); + } + + /** + * Represents the serialized output of {@code Result}. The fields on this + * class match the files that {@code Result} had in JUnit 4.11. + */ + private static class SerializedForm implements Serializable { + private static final long serialVersionUID = 1L; + private final AtomicInteger fCount; + private final AtomicInteger fIgnoreCount; + private final List<Failure> fFailures; + private final long fRunTime; + private final long fStartTime; + + public SerializedForm(Result result) { + fCount = result.count; + fIgnoreCount = result.ignoreCount; + fFailures = Collections.synchronizedList(new ArrayList<Failure>(result.failures)); + fRunTime = result.runTime.longValue(); + fStartTime = result.startTime.longValue(); + } + + @SuppressWarnings("unchecked") + private SerializedForm(ObjectInputStream.GetField fields) throws IOException { + fCount = (AtomicInteger) fields.get("fCount", null); + fIgnoreCount = (AtomicInteger) fields.get("fIgnoreCount", null); + fFailures = (List<Failure>) fields.get("fFailures", null); + fRunTime = fields.get("fRunTime", 0L); + fStartTime = fields.get("fStartTime", 0L); + } + + public void serialize(ObjectOutputStream s) throws IOException { + ObjectOutputStream.PutField fields = s.putFields(); + fields.put("fCount", fCount); + fields.put("fIgnoreCount", fIgnoreCount); + fields.put("fFailures", fFailures); + fields.put("fRunTime", fRunTime); + fields.put("fStartTime", fStartTime); + s.writeFields(); + } + + public static SerializedForm deserialize(ObjectInputStream s) + throws ClassNotFoundException, IOException { + ObjectInputStream.GetField fields = s.readFields(); + return new SerializedForm(fields); + } + } } diff --git a/src/main/java/org/junit/runner/RunWith.java b/src/main/java/org/junit/runner/RunWith.java index 602edf0..3428ee2 100644 --- a/src/main/java/org/junit/runner/RunWith.java +++ b/src/main/java/org/junit/runner/RunWith.java @@ -7,28 +7,30 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * When a class is annotated with <code>@RunWith</code> or extends a class annotated - * with <code>@RunWith</code>, JUnit will invoke the class it references to run the - * tests in that class instead of the runner built into JUnit. We added this feature late - * in development. While it seems powerful we expect the runner API to change as we learn - * how people really use it. Some of the classes that are currently internal will likely + * When a class is annotated with <code>@RunWith</code> or extends a class annotated + * with <code>@RunWith</code>, JUnit will invoke the class it references to run the + * tests in that class instead of the runner built into JUnit. We added this feature late + * in development. While it seems powerful we expect the runner API to change as we learn + * how people really use it. Some of the classes that are currently internal will likely * be refined and become public. - * + * * For example, suites in JUnit 4 are built using RunWith, and a custom runner named Suite: - * + * * <pre> * @RunWith(Suite.class) * @SuiteClasses({ATest.class, BTest.class, CTest.class}) * public class ABCSuite { * } * </pre> + * + * @since 4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface RunWith { - /** - * @return a Runner class (must have a constructor that takes a single Class to run) - */ - Class<? extends Runner> value(); + /** + * @return a Runner class (must have a constructor that takes a single Class to run) + */ + Class<? extends Runner> value(); } diff --git a/src/main/java/org/junit/runner/Runner.java b/src/main/java/org/junit/runner/Runner.java index 39e424f..d728dd8 100644 --- a/src/main/java/org/junit/runner/Runner.java +++ b/src/main/java/org/junit/runner/Runner.java @@ -9,32 +9,35 @@ import org.junit.runner.notification.RunNotifier; * a custom runner, in addition to implementing the abstract methods here you must * also provide a constructor that takes as an argument the {@link Class} containing * the tests. - * <p/> - * The default runner implementation guarantees that the instances of the test case + * + * <p>The default runner implementation guarantees that the instances of the test case * class will be constructed immediately before running the test and that the runner - * will retain no reference to the test case instances, generally making them + * will retain no reference to the test case instances, generally making them * available for garbage collection. - * + * * @see org.junit.runner.Description * @see org.junit.runner.RunWith + * @since 4.0 */ public abstract class Runner implements Describable { - /* (non-Javadoc) - * @see org.junit.runner.Describable#getDescription() - */ - public abstract Description getDescription(); + /* + * (non-Javadoc) + * @see org.junit.runner.Describable#getDescription() + */ + public abstract Description getDescription(); - /** - * Run the tests for this runner. - * @param notifier will be notified of events while tests are being run--tests being - * started, finishing, and failing - */ - public abstract void run(RunNotifier notifier); - - /** - * @return the number of tests to be run by the receiver - */ - public int testCount() { - return getDescription().testCount(); - } -}
\ No newline at end of file + /** + * Run the tests for this runner. + * + * @param notifier will be notified of events while tests are being run--tests being + * started, finishing, and failing + */ + public abstract void run(RunNotifier notifier); + + /** + * @return the number of tests to be run by the receiver + */ + public int testCount() { + return getDescription().testCount(); + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Filter.java b/src/main/java/org/junit/runner/manipulation/Filter.java index c0f31b0..0287351 100644 --- a/src/main/java/org/junit/runner/manipulation/Filter.java +++ b/src/main/java/org/junit/runner/manipulation/Filter.java @@ -7,108 +7,116 @@ import org.junit.runner.Request; * The canonical case of filtering is when you want to run a single test method in a class. Rather * than introduce runner API just for that one case, JUnit provides a general filtering mechanism. * If you want to filter the tests to be run, extend <code>Filter</code> and apply an instance of - * your filter to the {@link org.junit.runner.Request} before running it (see - * {@link org.junit.runner.JUnitCore#run(Request)}. Alternatively, apply a <code>Filter</code> to - * a {@link org.junit.runner.Runner} before running tests (for example, in conjunction with + * your filter to the {@link org.junit.runner.Request} before running it (see + * {@link org.junit.runner.JUnitCore#run(Request)}. Alternatively, apply a <code>Filter</code> to + * a {@link org.junit.runner.Runner} before running tests (for example, in conjunction with * {@link org.junit.runner.RunWith}. + * + * @since 4.0 */ public abstract class Filter { - /** - * A null <code>Filter</code> that passes all tests through. - */ - public static Filter ALL= new Filter() { - @Override - public boolean shouldRun(Description description) { - return true; - } + /** + * A null <code>Filter</code> that passes all tests through. + */ + public static final Filter ALL = new Filter() { + @Override + public boolean shouldRun(Description description) { + return true; + } - @Override - public String describe() { - return "all tests"; - } - - @Override - public void apply(Object child) throws NoTestsRemainException { - // do nothing - } + @Override + public String describe() { + return "all tests"; + } - @Override - public Filter intersect(Filter second) { - return second; - } - }; - - /** - * Returns a {@code Filter} that only runs the single method described by - * {@code desiredDescription} - */ - public static Filter matchMethodDescription(final Description desiredDescription) { - return new Filter() { - @Override - public boolean shouldRun(Description description) { - if (description.isTest()) - return desiredDescription.equals(description); - - // explicitly check if any children want to run - for (Description each : description.getChildren()) - if (shouldRun(each)) - return true; - return false; - } + @Override + public void apply(Object child) throws NoTestsRemainException { + // do nothing + } - @Override - public String describe() { - return String.format("Method %s", desiredDescription.getDisplayName()); - } - }; - } + @Override + public Filter intersect(Filter second) { + return second; + } + }; + /** + * Returns a {@code Filter} that only runs the single method described by + * {@code desiredDescription} + */ + public static Filter matchMethodDescription(final Description desiredDescription) { + return new Filter() { + @Override + public boolean shouldRun(Description description) { + if (description.isTest()) { + return desiredDescription.equals(description); + } - /** - * @param description the description of the test to be run - * @return <code>true</code> if the test should be run - */ - public abstract boolean shouldRun(Description description); + // explicitly check if any children want to run + for (Description each : description.getChildren()) { + if (shouldRun(each)) { + return true; + } + } + return false; + } - /** - * Returns a textual description of this Filter - * @return a textual description of this Filter - */ - public abstract String describe(); + @Override + public String describe() { + return String.format("Method %s", desiredDescription.getDisplayName()); + } + }; + } - /** - * Invoke with a {@link org.junit.runner.Runner} to cause all tests it intends to run - * to first be checked with the filter. Only those that pass the filter will be run. - * @param child the runner to be filtered by the receiver - * @throws NoTestsRemainException if the receiver removes all tests - */ - public void apply(Object child) throws NoTestsRemainException { - if (!(child instanceof Filterable)) - return; - Filterable filterable= (Filterable) child; - filterable.filter(this); - } - /** - * Returns a new Filter that accepts the intersection of the tests accepted - * by this Filter and {@code second} - */ - public Filter intersect(final Filter second) { - if (second == this || second == ALL) { - return this; - } - final Filter first= this; - return new Filter() { - @Override - public boolean shouldRun(Description description) { - return first.shouldRun(description) - && second.shouldRun(description); - } + /** + * @param description the description of the test to be run + * @return <code>true</code> if the test should be run + */ + public abstract boolean shouldRun(Description description); - @Override - public String describe() { - return first.describe() + " and " + second.describe(); - } - }; - } + /** + * Returns a textual description of this Filter + * + * @return a textual description of this Filter + */ + public abstract String describe(); + + /** + * Invoke with a {@link org.junit.runner.Runner} to cause all tests it intends to run + * to first be checked with the filter. Only those that pass the filter will be run. + * + * @param child the runner to be filtered by the receiver + * @throws NoTestsRemainException if the receiver removes all tests + */ + public void apply(Object child) throws NoTestsRemainException { + if (!(child instanceof Filterable)) { + return; + } + Filterable filterable = (Filterable) child; + filterable.filter(this); + } + + /** + * Returns a new Filter that accepts the intersection of the tests accepted + * by this Filter and {@code second} + */ + public Filter intersect(final Filter second) { + if (second == this || second == ALL) { + return this; + } + final Filter first = this; + return new Filter() { + @Override + public boolean shouldRun(Description description) { + return first.shouldRun(description) + && second.shouldRun(description); + } + + @Override + public String describe() { + return first.describe() + " and " + second.describe(); + } + }; + } } diff --git a/src/main/java/org/junit/runner/manipulation/Filterable.java b/src/main/java/org/junit/runner/manipulation/Filterable.java index 782c0f7..d605027 100644 --- a/src/main/java/org/junit/runner/manipulation/Filterable.java +++ b/src/main/java/org/junit/runner/manipulation/Filterable.java @@ -3,14 +3,17 @@ package org.junit.runner.manipulation; /** * Runners that allow filtering should implement this interface. Implement {@link #filter(Filter)} * to remove tests that don't pass the filter. + * + * @since 4.0 */ public interface Filterable { - /** - * Remove tests that don't pass the parameter <code>filter</code>. - * @param filter the {@link Filter} to apply - * @throws NoTestsRemainException if all tests are filtered out - */ - void filter(Filter filter) throws NoTestsRemainException; + /** + * Remove tests that don't pass the parameter <code>filter</code>. + * + * @param filter the {@link Filter} to apply + * @throws NoTestsRemainException if all tests are filtered out + */ + void filter(Filter filter) throws NoTestsRemainException; } diff --git a/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java b/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java index 03fb3bf..21935bd 100644 --- a/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java +++ b/src/main/java/org/junit/runner/manipulation/NoTestsRemainException.java @@ -2,7 +2,9 @@ package org.junit.runner.manipulation; /** * Thrown when a filter removes all tests from a runner. + * + * @since 4.0 */ public class NoTestsRemainException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; } diff --git a/src/main/java/org/junit/runner/manipulation/Sortable.java b/src/main/java/org/junit/runner/manipulation/Sortable.java index fec1d0c..9ac864c 100644 --- a/src/main/java/org/junit/runner/manipulation/Sortable.java +++ b/src/main/java/org/junit/runner/manipulation/Sortable.java @@ -5,13 +5,16 @@ package org.junit.runner.manipulation; * failed first, you can reduce the average time to the first test failing. Test sorting should not be used to * cope with order dependencies between tests. Tests that are isolated from each other are less * expensive to maintain and can be run individually. + * + * @since 4.0 */ public interface Sortable { - /** - * Sorts the tests using <code>sorter</code> - * @param sorter the {@link Sorter} to use for sorting the tests - */ - public void sort(Sorter sorter); + /** + * Sorts the tests using <code>sorter</code> + * + * @param sorter the {@link Sorter} to use for sorting the tests + */ + public void sort(Sorter sorter); } diff --git a/src/main/java/org/junit/runner/manipulation/Sorter.java b/src/main/java/org/junit/runner/manipulation/Sorter.java index 242df14..20192d0 100644 --- a/src/main/java/org/junit/runner/manipulation/Sorter.java +++ b/src/main/java/org/junit/runner/manipulation/Sorter.java @@ -7,40 +7,42 @@ import org.junit.runner.Description; /** * A <code>Sorter</code> orders tests. In general you will not need * to use a <code>Sorter</code> directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. - * - * + * + * @since 4.0 */ public class Sorter implements Comparator<Description> { - /** - * NULL is a <code>Sorter</code> that leaves elements in an undefined order - */ - public static Sorter NULL= new Sorter(new Comparator<Description>() { - public int compare(Description o1, Description o2) { - return 0; - }}); - private final Comparator<Description> fComparator; + /** + * NULL is a <code>Sorter</code> that leaves elements in an undefined order + */ + public static final Sorter NULL = new Sorter(new Comparator<Description>() { + public int compare(Description o1, Description o2) { + return 0; + } + }); - /** - * Creates a <code>Sorter</code> that uses <code>comparator</code> - * to sort tests - * @param comparator the {@link Comparator} to use when sorting tests - */ - public Sorter(Comparator<Description> comparator) { - fComparator= comparator; - } + private final Comparator<Description> comparator; - /** - * Sorts the test in <code>runner</code> using <code>comparator</code> - * @param object - */ - public void apply(Object object) { - if (object instanceof Sortable) { - Sortable sortable = (Sortable) object; - sortable.sort(this); - } - } + /** + * Creates a <code>Sorter</code> that uses <code>comparator</code> + * to sort tests + * + * @param comparator the {@link Comparator} to use when sorting tests + */ + public Sorter(Comparator<Description> comparator) { + this.comparator = comparator; + } - public int compare(Description o1, Description o2) { - return fComparator.compare(o1, o2); - } + /** + * Sorts the test in <code>runner</code> using <code>comparator</code> + */ + public void apply(Object object) { + if (object instanceof Sortable) { + Sortable sortable = (Sortable) object; + sortable.sort(this); + } + } + + public int compare(Description o1, Description o2) { + return comparator.compare(o1, o2); + } } diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java index 501caa5..c03b4c1 100644 --- a/src/main/java/org/junit/runner/notification/Failure.java +++ b/src/main/java/org/junit/runner/notification/Failure.java @@ -12,68 +12,76 @@ import org.junit.runner.Description; * will be of a single test. However, if problems are encountered while constructing the * test (for example, if a {@link org.junit.BeforeClass} method is not static), it may describe * something other than a single test. + * + * @since 4.0 */ public class Failure implements Serializable { - private static final long serialVersionUID = 1L; - private final Description fDescription; - private final Throwable fThrownException; + private static final long serialVersionUID = 1L; - /** - * Constructs a <code>Failure</code> with the given description and exception. - * @param description a {@link org.junit.runner.Description} of the test that failed - * @param thrownException the exception that was thrown while running the test - */ - public Failure(Description description, Throwable thrownException) { - fThrownException = thrownException; - fDescription= description; - } + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final Description fDescription; + private final Throwable fThrownException; - /** - * @return a user-understandable label for the test - */ - public String getTestHeader() { - return fDescription.getDisplayName(); - } + /** + * Constructs a <code>Failure</code> with the given description and exception. + * + * @param description a {@link org.junit.runner.Description} of the test that failed + * @param thrownException the exception that was thrown while running the test + */ + public Failure(Description description, Throwable thrownException) { + this.fThrownException = thrownException; + this.fDescription = description; + } - /** - * @return the raw description of the context of the failure. - */ - public Description getDescription() { - return fDescription; - } + /** + * @return a user-understandable label for the test + */ + public String getTestHeader() { + return fDescription.getDisplayName(); + } - /** - * @return the exception thrown - */ + /** + * @return the raw description of the context of the failure. + */ + public Description getDescription() { + return fDescription; + } - public Throwable getException() { - return fThrownException; - } + /** + * @return the exception thrown + */ - @Override - public String toString() { - StringBuffer buffer= new StringBuffer(); - buffer.append(getTestHeader() + ": "+fThrownException.getMessage()); - return buffer.toString(); - } + public Throwable getException() { + return fThrownException; + } - /** - * Convenience method - * @return the printed form of the exception - */ - public String getTrace() { - StringWriter stringWriter= new StringWriter(); - PrintWriter writer= new PrintWriter(stringWriter); - getException().printStackTrace(writer); - StringBuffer buffer= stringWriter.getBuffer(); - return buffer.toString(); - } + @Override + public String toString() { + return getTestHeader() + ": " + fThrownException.getMessage(); + } - /** - * Convenience method - * @return the message of the thrown exception - */ - public String getMessage() { - return getException().getMessage(); - } + /** + * Convenience method + * + * @return the printed form of the exception + */ + public String getTrace() { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + getException().printStackTrace(writer); + return stringWriter.toString(); + } + + /** + * Convenience method + * + * @return the message of the thrown exception + */ + public String getMessage() { + return getException().getMessage(); + } } diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java index ffe8134..db9d8c1 100644 --- a/src/main/java/org/junit/runner/notification/RunListener.java +++ b/src/main/java/org/junit/runner/notification/RunListener.java @@ -1,15 +1,21 @@ package org.junit.runner.notification; -import org.junit.internal.AssumptionViolatedException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.junit.runner.Description; import org.junit.runner.Result; /** - * <p>If you need to respond to the events during a test run, extend <code>RunListener</code> - * and override the appropriate methods. If a listener throws an exception while processing a - * test event, it will be removed for the remainder of the test run.</p> - * - * <p>For example, suppose you have a <code>Cowbell</code> + * Register an instance of this class with {@link RunNotifier} to be notified + * of events that occur during a test run. All of the methods in this class + * are abstract and have no implementation; override one or more methods to + * receive events. + * <p> + * For example, suppose you have a <code>Cowbell</code> * class that you want to make a noise whenever a test fails. You could write: * <pre> * public class RingingListener extends RunListener { @@ -18,9 +24,8 @@ import org.junit.runner.Result; * } * } * </pre> - * </p> - * - * <p>To invoke your listener, you need to run your tests through <code>JUnitCore</code>. + * <p> + * To invoke your listener, you need to run your tests through <code>JUnitCore</code>. * <pre> * public void main(String... args) { * JUnitCore core= new JUnitCore(); @@ -28,66 +33,108 @@ import org.junit.runner.Result; * core.run(MyTestClass.class); * } * </pre> - * </p> + * <p> + * If a listener throws an exception for a test event, the other listeners will + * have their {@link RunListener#testFailure(Failure)} called with a {@code Description} + * of {@link Description#TEST_MECHANISM} to indicate the failure. + * <p> + * By default, JUnit will synchronize calls to your listener. If your listener + * is thread-safe and you want to allow JUnit to call your listener from + * multiple threads when tests are run in parallel, you can annotate your + * test class with {@link RunListener.ThreadSafe}. + * <p> + * Listener methods will be called from the same thread as is running + * the test, unless otherwise indicated by the method Javadoc + * * @see org.junit.runner.JUnitCore + * @since 4.0 */ public class RunListener { - /** - * Called before any tests have been run. - * @param description describes the tests to be run - */ - public void testRunStarted(Description description) throws Exception { - } - - /** - * Called when all tests have finished - * @param result the summary of the test run, including all the tests that failed - */ - public void testRunFinished(Result result) throws Exception { - } - - /** - * Called when an atomic test is about to be started. - * @param description the description of the test that is about to be run - * (generally a class and method name) - */ - public void testStarted(Description description) throws Exception { - } + /** + * Called before any tests have been run. This may be called on an + * arbitrary thread. + * + * @param description describes the tests to be run + */ + public void testRunStarted(Description description) throws Exception { + } - /** - * Called when an atomic test has finished, whether the test succeeds or fails. - * @param description the description of the test that just ran - */ - public void testFinished(Description description) throws Exception { - } + /** + * Called when all tests have finished. This may be called on an + * arbitrary thread. + * + * @param result the summary of the test run, including all the tests that failed + */ + public void testRunFinished(Result result) throws Exception { + } - /** - * Called when an atomic test fails. - * @param failure describes the test that failed and the exception that was thrown - */ - public void testFailure(Failure failure) throws Exception { - } + /** + * Called when an atomic test is about to be started. + * + * @param description the description of the test that is about to be run + * (generally a class and method name) + */ + public void testStarted(Description description) throws Exception { + } - /** - * Called when an atomic test flags that it assumes a condition that is - * false - * - * @param failure - * describes the test that failed and the - * {@link AssumptionViolatedException} that was thrown - */ - public void testAssumptionFailure(Failure failure) { - } + /** + * Called when an atomic test has finished, whether the test succeeds or fails. + * + * @param description the description of the test that just ran + */ + public void testFinished(Description description) throws Exception { + } - /** - * Called when a test will not be run, generally because a test method is annotated - * with {@link org.junit.Ignore}. - * - * @param description describes the test that will not be run - */ - public void testIgnored(Description description) throws Exception { - } -} + /** + * Called when an atomic test fails, or when a listener throws an exception. + * + * <p>In the case of a failure of an atomic test, this method will be called + * with the same {@code Description} passed to + * {@link #testStarted(Description)}, from the same thread that called + * {@link #testStarted(Description)}. + * + * <p>In the case of a listener throwing an exception, this will be called with + * a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called + * on an arbitrary thread. + * + * @param failure describes the test that failed and the exception that was thrown + */ + public void testFailure(Failure failure) throws Exception { + } + + /** + * Called when an atomic test flags that it assumes a condition that is + * false + * + * @param failure describes the test that failed and the + * {@link org.junit.AssumptionViolatedException} that was thrown + */ + public void testAssumptionFailure(Failure failure) { + } + /** + * Called when a test will not be run, generally because a test method is annotated + * with {@link org.junit.Ignore}. + * + * @param description describes the test that will not be run + */ + public void testIgnored(Description description) throws Exception { + } + + /** + * Indicates a {@code RunListener} that can have its methods called + * concurrently. This implies that the class is thread-safe (i.e. no set of + * listener calls can put the listener into an invalid state, even if those + * listener calls are being made by multiple threads without + * synchronization). + * + * @since 4.12 + */ + @Documented + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface ThreadSafe { + } +} diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java index d0f6c85..6875f76 100644 --- a/src/main/java/org/junit/runner/notification/RunNotifier.java +++ b/src/main/java/org/junit/runner/notification/RunNotifier.java @@ -1,166 +1,214 @@ package org.junit.runner.notification; +import static java.util.Arrays.asList; + import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; -import org.junit.internal.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runner.Result; /** * If you write custom runners, you may need to notify JUnit of your progress running tests. * Do this by invoking the <code>RunNotifier</code> passed to your implementation of - * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to + * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to * move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)} * to a separate class since they should only be called once per run. + * + * @since 4.0 */ public class RunNotifier { - private final List<RunListener> fListeners= - Collections.synchronizedList(new ArrayList<RunListener>()); - private boolean fPleaseStop= false; - - /** Internal use only - */ - public void addListener(RunListener listener) { - fListeners.add(listener); - } - - /** Internal use only - */ - public void removeListener(RunListener listener) { - fListeners.remove(listener); + private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>(); + private volatile boolean pleaseStop = false; + + /** + * Internal use only + */ + public void addListener(RunListener listener) { + if (listener == null) { + throw new NullPointerException("Cannot add a null listener"); + } + listeners.add(wrapIfNotThreadSafe(listener)); + } + + /** + * Internal use only + */ + public void removeListener(RunListener listener) { + if (listener == null) { + throw new NullPointerException("Cannot remove a null listener"); + } + listeners.remove(wrapIfNotThreadSafe(listener)); + } + + /** + * Wraps the given listener with {@link SynchronizedRunListener} if + * it is not annotated with {@link RunListener.ThreadSafe}. + */ + RunListener wrapIfNotThreadSafe(RunListener listener) { + return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ? + listener : new SynchronizedRunListener(listener, this); + } + + + private abstract class SafeNotifier { + private final List<RunListener> currentListeners; + + SafeNotifier() { + this(listeners); + } + + SafeNotifier(List<RunListener> currentListeners) { + this.currentListeners = currentListeners; + } + + void run() { + int capacity = currentListeners.size(); + ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity); + ArrayList<Failure> failures = new ArrayList<Failure>(capacity); + for (RunListener listener : currentListeners) { + try { + notifyListener(listener); + safeListeners.add(listener); + } catch (Exception e) { + failures.add(new Failure(Description.TEST_MECHANISM, e)); + } + } + fireTestFailures(safeListeners, failures); + } + + abstract protected void notifyListener(RunListener each) throws Exception; + } + + /** + * Do not invoke. + */ + public void fireTestRunStarted(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testRunStarted(description); + } + }.run(); + } + + /** + * Do not invoke. + */ + public void fireTestRunFinished(final Result result) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testRunFinished(result); + } + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test is about to start. + * + * @param description the description of the atomic test (generally a class and method name) + * @throws StoppedByUserException thrown if a user has requested that the test run stop + */ + public void fireTestStarted(final Description description) throws StoppedByUserException { + if (pleaseStop) { + throw new StoppedByUserException(); + } + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testStarted(description); + } + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test failed. + * + * @param failure the description of the test that failed and the exception thrown + */ + public void fireTestFailure(Failure failure) { + fireTestFailures(listeners, asList(failure)); + } + + private void fireTestFailures(List<RunListener> listeners, + final List<Failure> failures) { + if (!failures.isEmpty()) { + new SafeNotifier(listeners) { + @Override + protected void notifyListener(RunListener listener) throws Exception { + for (Failure each : failures) { + listener.testFailure(each); + } + } + }.run(); + } + } + + /** + * Invoke to tell listeners that an atomic test flagged that it assumed + * something false. + * + * @param failure the description of the test that failed and the + * {@link org.junit.AssumptionViolatedException} thrown + */ + public void fireTestAssumptionFailed(final Failure failure) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testAssumptionFailure(failure); + } + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test was ignored. + * + * @param description the description of the ignored test + */ + public void fireTestIgnored(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testIgnored(description); + } + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test finished. Always invoke + * this method if you invoke {@link #fireTestStarted(Description)} + * as listeners are likely to expect them to come in pairs. + * + * @param description the description of the test that finished + */ + public void fireTestFinished(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testFinished(description); + } + }.run(); + } + + /** + * Ask that the tests run stop before starting the next test. Phrased politely because + * the test currently running will not be interrupted. It seems a little odd to put this + * functionality here, but the <code>RunNotifier</code> is the only object guaranteed + * to be shared amongst the many runners involved. + */ + public void pleaseStop() { + pleaseStop = true; } - private abstract class SafeNotifier { - void run() { - synchronized (fListeners) { - for (Iterator<RunListener> all= fListeners.iterator(); all.hasNext();) - try { - notifyListener(all.next()); - } catch (Exception e) { - all.remove(); // Remove the offending listener first to avoid an infinite loop - fireTestFailure(new Failure(Description.TEST_MECHANISM, e)); - } - } - } - - abstract protected void notifyListener(RunListener each) throws Exception; - } - - /** - * Do not invoke. - */ - public void fireTestRunStarted(final Description description) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testRunStarted(description); - }; - }.run(); - } - - /** - * Do not invoke. - */ - public void fireTestRunFinished(final Result result) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testRunFinished(result); - }; - }.run(); - } - - /** - * Invoke to tell listeners that an atomic test is about to start. - * @param description the description of the atomic test (generally a class and method name) - * @throws StoppedByUserException thrown if a user has requested that the test run stop - */ - public void fireTestStarted(final Description description) throws StoppedByUserException { - if (fPleaseStop) - throw new StoppedByUserException(); - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testStarted(description); - }; - }.run(); - } - - /** - * Invoke to tell listeners that an atomic test failed. - * @param failure the description of the test that failed and the exception thrown - */ - public void fireTestFailure(final Failure failure) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testFailure(failure); - }; - }.run(); - } - - /** - * Invoke to tell listeners that an atomic test flagged that it assumed - * something false. - * - * @param failure - * the description of the test that failed and the - * {@link AssumptionViolatedException} thrown - */ - public void fireTestAssumptionFailed(final Failure failure) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testAssumptionFailure(failure); - }; - }.run(); - } - - /** - * Invoke to tell listeners that an atomic test was ignored. - * @param description the description of the ignored test - */ - public void fireTestIgnored(final Description description) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testIgnored(description); - } - }.run(); - } - - /** - * Invoke to tell listeners that an atomic test finished. Always invoke - * {@link #fireTestFinished(Description)} if you invoke {@link #fireTestStarted(Description)} - * as listeners are likely to expect them to come in pairs. - * @param description the description of the test that finished - */ - public void fireTestFinished(final Description description) { - new SafeNotifier() { - @Override - protected void notifyListener(RunListener each) throws Exception { - each.testFinished(description); - }; - }.run(); - } - - /** - * Ask that the tests run stop before starting the next test. Phrased politely because - * the test currently running will not be interrupted. It seems a little odd to put this - * functionality here, but the <code>RunNotifier</code> is the only object guaranteed - * to be shared amongst the many runners involved. - */ - public void pleaseStop() { - fPleaseStop= true; - } - - /** - * Internal use only. The Result's listener must be first. - */ - public void addFirstListener(RunListener listener) { - fListeners.add(0, listener); - } -}
\ No newline at end of file + /** + * Internal use only. The Result's listener must be first. + */ + public void addFirstListener(RunListener listener) { + if (listener == null) { + throw new NullPointerException("Cannot add a null listener"); + } + listeners.add(0, wrapIfNotThreadSafe(listener)); + } +} diff --git a/src/main/java/org/junit/runner/notification/StoppedByUserException.java b/src/main/java/org/junit/runner/notification/StoppedByUserException.java index 89be3ba..f5490f7 100644 --- a/src/main/java/org/junit/runner/notification/StoppedByUserException.java +++ b/src/main/java/org/junit/runner/notification/StoppedByUserException.java @@ -1,11 +1,12 @@ package org.junit.runner.notification; /** - * Thrown when a user has requested that the test run stop. Writers of + * Thrown when a user has requested that the test run stop. Writers of * test running GUIs should be prepared to catch a <code>StoppedByUserException</code>. - * + * * @see org.junit.runner.notification.RunNotifier + * @since 4.0 */ public class StoppedByUserException extends RuntimeException { - private static final long serialVersionUID= 1L; + private static final long serialVersionUID = 1L; } diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java new file mode 100644 index 0000000..c53c1ee --- /dev/null +++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java @@ -0,0 +1,103 @@ +package org.junit.runner.notification; + +import org.junit.runner.Description; +import org.junit.runner.Result; + +/** + * Thread-safe decorator for {@link RunListener} implementations that synchronizes + * calls to the delegate. + * + * <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because + * prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier, + * so no two listeners were ever called concurrently. If we instead made the methods here + * sychronized, clients that added multiple listeners that called common code might see + * issues due to the reduced synchronization. + * + * @author Tibor Digana (tibor17) + * @author Kevin Cooney (kcooney) + * @since 4.12 + * + * @see RunNotifier + */ +@RunListener.ThreadSafe +final class SynchronizedRunListener extends RunListener { + private final RunListener listener; + private final Object monitor; + + SynchronizedRunListener(RunListener listener, Object monitor) { + this.listener = listener; + this.monitor = monitor; + } + + @Override + public void testRunStarted(Description description) throws Exception { + synchronized (monitor) { + listener.testRunStarted(description); + } + } + + @Override + public void testRunFinished(Result result) throws Exception { + synchronized (monitor) { + listener.testRunFinished(result); + } + } + + @Override + public void testStarted(Description description) throws Exception { + synchronized (monitor) { + listener.testStarted(description); + } + } + + @Override + public void testFinished(Description description) throws Exception { + synchronized (monitor) { + listener.testFinished(description); + } + } + + @Override + public void testFailure(Failure failure) throws Exception { + synchronized (monitor) { + listener.testFailure(failure); + } + } + + @Override + public void testAssumptionFailure(Failure failure) { + synchronized (monitor) { + listener.testAssumptionFailure(failure); + } + } + + @Override + public void testIgnored(Description description) throws Exception { + synchronized (monitor) { + listener.testIgnored(description); + } + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SynchronizedRunListener)) { + return false; + } + SynchronizedRunListener that = (SynchronizedRunListener) other; + + return listener.equals(that.listener); + } + + @Override + public String toString() { + return listener.toString() + " (with synchronization wrapper)"; + } +} diff --git a/src/main/java/org/junit/runners/AllTests.java b/src/main/java/org/junit/runners/AllTests.java index 50c02db..416c99d 100644 --- a/src/main/java/org/junit/runners/AllTests.java +++ b/src/main/java/org/junit/runners/AllTests.java @@ -2,7 +2,8 @@ package org.junit.runners; import org.junit.internal.runners.SuiteMethod; -/** Runner for use with JUnit 3.8.x-style AllTests classes +/** + * Runner for use with JUnit 3.8.x-style AllTests classes * (those that only implement a static <code>suite()</code> * method). For example: * <pre> @@ -13,12 +14,14 @@ import org.junit.internal.runners.SuiteMethod; * } * } * </pre> + * + * @since 4.0 */ public class AllTests extends SuiteMethod { - /** - * Only called reflectively. Do not use programmatically. - */ - public AllTests(Class<?> klass) throws Throwable { - super(klass); - } + /** + * Only called reflectively. Do not use programmatically. + */ + public AllTests(Class<?> klass) throws Throwable { + super(klass); + } } diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java index 397da19..4d06199 100644 --- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java +++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java @@ -1,8 +1,11 @@ package org.junit.runners; -import static org.junit.internal.runners.rules.RuleFieldValidator.RULE_VALIDATOR; +import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR; +import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; @@ -17,6 +20,7 @@ import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.internal.runners.statements.InvokeMethod; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.MethodRule; import org.junit.rules.RunRules; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -31,377 +35,403 @@ import org.junit.runners.model.Statement; * annotations in the org.junit package. Many users will never notice this * class: it is now the default test class runner, but it should have exactly * the same behavior as the old test class runner ({@code JUnit4ClassRunner}). - * + * <p> * BlockJUnit4ClassRunner has advantages for writers of custom JUnit runners * that are slight changes to the default behavior, however: - * + * * <ul> * <li>It has a much simpler implementation based on {@link Statement}s, * allowing new operations to be inserted into the appropriate point in the * execution flow. - * + * * <li>It is published, and extension and reuse are encouraged, whereas {@code * JUnit4ClassRunner} was in an internal package, and is now deprecated. * </ul> + * <p> + * In turn, in 2009 we introduced {@link Rule}s. In many cases where extending + * BlockJUnit4ClassRunner was necessary to add new behavior, {@link Rule}s can + * be used, which makes the extension more reusable and composable. + * + * @since 4.5 */ public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { - /** - * Creates a BlockJUnit4ClassRunner to run {@code klass} - * - * @throws InitializationError - * if the test class is malformed. - */ - public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError { - super(klass); - } - - // - // Implementation of ParentRunner - // - - @Override - protected void runChild(final FrameworkMethod method, RunNotifier notifier) { - Description description= describeChild(method); - if (method.getAnnotation(Ignore.class) != null) { - notifier.fireTestIgnored(description); - } else { - runLeaf(methodBlock(method), description, notifier); - } - } - - @Override - protected Description describeChild(FrameworkMethod method) { - return Description.createTestDescription(getTestClass().getJavaClass(), - testName(method), method.getAnnotations()); - } - - @Override - protected List<FrameworkMethod> getChildren() { - return computeTestMethods(); - } - - // - // Override in subclasses - // - - /** - * Returns the methods that run tests. Default implementation returns all - * methods annotated with {@code @Test} on this class and superclasses that - * are not overridden. - */ - protected List<FrameworkMethod> computeTestMethods() { - return getTestClass().getAnnotatedMethods(Test.class); - } - - @Override - protected void collectInitializationErrors(List<Throwable> errors) { - super.collectInitializationErrors(errors); - - validateNoNonStaticInnerClass(errors); - validateConstructor(errors); - validateInstanceMethods(errors); - validateFields(errors); - } - - protected void validateNoNonStaticInnerClass(List<Throwable> errors) { - if (getTestClass().isANonStaticInnerClass()) { - String gripe= "The inner class " + getTestClass().getName() - + " is not static."; - errors.add(new Exception(gripe)); - } - } - - /** - * Adds to {@code errors} if the test class has more than one constructor, - * or if the constructor takes parameters. Override if a subclass requires - * different validation rules. - */ - protected void validateConstructor(List<Throwable> errors) { - validateOnlyOneConstructor(errors); - validateZeroArgConstructor(errors); - } - - /** - * Adds to {@code errors} if the test class has more than one constructor - * (do not override) - */ - protected void validateOnlyOneConstructor(List<Throwable> errors) { - if (!hasOneConstructor()) { - String gripe= "Test class should have exactly one public constructor"; - errors.add(new Exception(gripe)); - } - } - - /** - * Adds to {@code errors} if the test class's single constructor takes - * parameters (do not override) - */ - protected void validateZeroArgConstructor(List<Throwable> errors) { - if (!getTestClass().isANonStaticInnerClass() - && hasOneConstructor() - && (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) { - String gripe= "Test class should have exactly one public zero-argument constructor"; - errors.add(new Exception(gripe)); - } - } - - private boolean hasOneConstructor() { - return getTestClass().getJavaClass().getConstructors().length == 1; - } - - /** - * Adds to {@code errors} for each method annotated with {@code @Test}, - * {@code @Before}, or {@code @After} that is not a public, void instance - * method with no arguments. - * - * @deprecated unused API, will go away in future version - */ - @Deprecated - protected void validateInstanceMethods(List<Throwable> errors) { - validatePublicVoidNoArgMethods(After.class, false, errors); - validatePublicVoidNoArgMethods(Before.class, false, errors); - validateTestMethods(errors); - - if (computeTestMethods().size() == 0) - errors.add(new Exception("No runnable methods")); - } - - protected void validateFields(List<Throwable> errors) { - RULE_VALIDATOR.validate(getTestClass(), errors); - } - - /** - * Adds to {@code errors} for each method annotated with {@code @Test}that - * is not a public, void instance method with no arguments. - */ - protected void validateTestMethods(List<Throwable> errors) { - validatePublicVoidNoArgMethods(Test.class, false, errors); - } - - /** - * Returns a new fixture for running a test. Default implementation executes - * the test class's no-argument constructor (validation should have ensured - * one exists). - */ - protected Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance(); - } - - /** - * Returns the name that describes {@code method} for {@link Description}s. - * Default implementation is the method's name - */ - protected String testName(FrameworkMethod method) { - return method.getName(); - } - - /** - * Returns a Statement that, when executed, either returns normally if - * {@code method} passes, or throws an exception if {@code method} fails. - * - * Here is an outline of the default implementation: - * - * <ul> - * <li>Invoke {@code method} on the result of {@code createTest()}, and - * throw any exceptions thrown by either operation. - * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code - * expecting} attribute, return normally only if the previous step threw an - * exception of the correct type, and throw an exception otherwise. - * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code - * timeout} attribute, throw an exception if the previous step takes more - * than the specified number of milliseconds. - * <li>ALWAYS run all non-overridden {@code @Before} methods on this class - * and superclasses before any of the previous steps; if any throws an - * Exception, stop execution and pass the exception on. - * <li>ALWAYS run all non-overridden {@code @After} methods on this class - * and superclasses after any of the previous steps; all After methods are - * always executed: exceptions thrown by previous steps are combined, if - * necessary, with exceptions from After methods into a - * {@link MultipleFailureException}. - * <li>ALWAYS allow {@code @Rule} fields to modify the execution of the - * above steps. A {@code Rule} may prevent all execution of the above steps, - * or add additional behavior before and after, or modify thrown exceptions. - * For more information, see {@link TestRule} - * </ul> - * - * This can be overridden in subclasses, either by overriding this method, - * or the implementations creating each sub-statement. - */ - protected Statement methodBlock(FrameworkMethod method) { - Object test; - try { - test= new ReflectiveCallable() { - @Override - protected Object runReflectiveCall() throws Throwable { - return createTest(); - } - }.run(); - } catch (Throwable e) { - return new Fail(e); - } - - Statement statement= methodInvoker(method, test); - statement= possiblyExpectingExceptions(method, test, statement); - statement= withPotentialTimeout(method, test, statement); - statement= withBefores(method, test, statement); - statement= withAfters(method, test, statement); - statement= withRules(method, test, statement); - return statement; - } - - // - // Statement builders - // - - /** - * Returns a {@link Statement} that invokes {@code method} on {@code test} - */ - protected Statement methodInvoker(FrameworkMethod method, Object test) { - return new InvokeMethod(method, test); - } - - /** - * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation - * has the {@code expecting} attribute, return normally only if {@code next} - * throws an exception of the correct type, and throw an exception - * otherwise. - * - * @deprecated Will be private soon: use Rules instead - */ - @Deprecated - protected Statement possiblyExpectingExceptions(FrameworkMethod method, - Object test, Statement next) { - Test annotation= method.getAnnotation(Test.class); - return expectsException(annotation) ? new ExpectException(next, - getExpectedException(annotation)) : next; - } - - /** - * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation - * has the {@code timeout} attribute, throw an exception if {@code next} - * takes more than the specified number of milliseconds. - * - * @deprecated Will be private soon: use Rules instead - */ - @Deprecated - protected Statement withPotentialTimeout(FrameworkMethod method, - Object test, Statement next) { - long timeout= getTimeout(method.getAnnotation(Test.class)); - return timeout > 0 ? new FailOnTimeout(next, timeout) : next; - } - - /** - * Returns a {@link Statement}: run all non-overridden {@code @Before} - * methods on this class and superclasses before running {@code next}; if - * any throws an Exception, stop execution and pass the exception on. - * - * @deprecated Will be private soon: use Rules instead - */ - @Deprecated - protected Statement withBefores(FrameworkMethod method, Object target, - Statement statement) { - List<FrameworkMethod> befores= getTestClass().getAnnotatedMethods( - Before.class); - return befores.isEmpty() ? statement : new RunBefores(statement, - befores, target); - } - - /** - * Returns a {@link Statement}: run all non-overridden {@code @After} - * methods on this class and superclasses before running {@code next}; all - * After methods are always executed: exceptions thrown by previous steps - * are combined, if necessary, with exceptions from After methods into a - * {@link MultipleFailureException}. - * - * @deprecated Will be private soon: use Rules instead - */ - @Deprecated - protected Statement withAfters(FrameworkMethod method, Object target, - Statement statement) { - List<FrameworkMethod> afters= getTestClass().getAnnotatedMethods( - After.class); - return afters.isEmpty() ? statement : new RunAfters(statement, afters, - target); - } - - private Statement withRules(FrameworkMethod method, Object target, - Statement statement) { - Statement result= statement; - result= withMethodRules(method, target, result); - result= withTestRules(method, target, result); - return result; - } - - @SuppressWarnings("deprecation") - private Statement withMethodRules(FrameworkMethod method, Object target, - Statement result) { - List<TestRule> testRules= getTestRules(target); - for (org.junit.rules.MethodRule each : getMethodRules(target)) - if (! testRules.contains(each)) - result= each.apply(result, method, target); - return result; - } - - @SuppressWarnings("deprecation") - private List<org.junit.rules.MethodRule> getMethodRules(Object target) { - return rules(target); - } - - /** - * @param target - * the test case instance - * @return a list of MethodRules that should be applied when executing this - * test - * @deprecated {@link org.junit.rules.MethodRule} is a deprecated interface. Port to - * {@link TestRule} and - * {@link BlockJUnit4ClassRunner#getTestRules(Object)} - */ - @Deprecated - protected List<org.junit.rules.MethodRule> rules(Object target) { - return getTestClass().getAnnotatedFieldValues(target, Rule.class, - org.junit.rules.MethodRule.class); - } - - /** - * Returns a {@link Statement}: apply all non-static {@link Value} fields - * annotated with {@link Rule}. - * - * @param statement The base statement - * @return a RunRules statement if any class-level {@link Rule}s are - * found, or the base statement - */ - private Statement withTestRules(FrameworkMethod method, Object target, - Statement statement) { - List<TestRule> testRules= getTestRules(target); - return testRules.isEmpty() ? statement : - new RunRules(statement, testRules, describeChild(method)); - } - - /** - * @param target - * the test case instance - * @return a list of TestRules that should be applied when executing this - * test - */ - protected List<TestRule> getTestRules(Object target) { - return getTestClass().getAnnotatedFieldValues(target, - Rule.class, TestRule.class); - } - - private Class<? extends Throwable> getExpectedException(Test annotation) { - if (annotation == null || annotation.expected() == None.class) - return null; - else - return annotation.expected(); - } - - private boolean expectsException(Test annotation) { - return getExpectedException(annotation) != null; - } - - private long getTimeout(Test annotation) { - if (annotation == null) - return 0; - return annotation.timeout(); - } + private final ConcurrentHashMap<FrameworkMethod, Description> methodDescriptions = new ConcurrentHashMap<FrameworkMethod, Description>(); + /** + * Creates a BlockJUnit4ClassRunner to run {@code klass} + * + * @throws InitializationError if the test class is malformed. + */ + public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError { + super(klass); + } + + // + // Implementation of ParentRunner + // + + @Override + protected void runChild(final FrameworkMethod method, RunNotifier notifier) { + Description description = describeChild(method); + if (isIgnored(method)) { + notifier.fireTestIgnored(description); + } else { + runLeaf(methodBlock(method), description, notifier); + } + } + + /** + * Evaluates whether {@link FrameworkMethod}s are ignored based on the + * {@link Ignore} annotation. + */ + @Override + protected boolean isIgnored(FrameworkMethod child) { + return child.getAnnotation(Ignore.class) != null; + } + + @Override + protected Description describeChild(FrameworkMethod method) { + Description description = methodDescriptions.get(method); + + if (description == null) { + description = Description.createTestDescription(getTestClass().getJavaClass(), + testName(method), method.getAnnotations()); + methodDescriptions.putIfAbsent(method, description); + } + + return description; + } + + @Override + protected List<FrameworkMethod> getChildren() { + return computeTestMethods(); + } + + // + // Override in subclasses + // + + /** + * Returns the methods that run tests. Default implementation returns all + * methods annotated with {@code @Test} on this class and superclasses that + * are not overridden. + */ + protected List<FrameworkMethod> computeTestMethods() { + return getTestClass().getAnnotatedMethods(Test.class); + } + + @Override + protected void collectInitializationErrors(List<Throwable> errors) { + super.collectInitializationErrors(errors); + + validateNoNonStaticInnerClass(errors); + validateConstructor(errors); + validateInstanceMethods(errors); + validateFields(errors); + validateMethods(errors); + } + + protected void validateNoNonStaticInnerClass(List<Throwable> errors) { + if (getTestClass().isANonStaticInnerClass()) { + String gripe = "The inner class " + getTestClass().getName() + + " is not static."; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class has more than one constructor, + * or if the constructor takes parameters. Override if a subclass requires + * different validation rules. + */ + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + validateZeroArgConstructor(errors); + } + + /** + * Adds to {@code errors} if the test class has more than one constructor + * (do not override) + */ + protected void validateOnlyOneConstructor(List<Throwable> errors) { + if (!hasOneConstructor()) { + String gripe = "Test class should have exactly one public constructor"; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class's single constructor takes + * parameters (do not override) + */ + protected void validateZeroArgConstructor(List<Throwable> errors) { + if (!getTestClass().isANonStaticInnerClass() + && hasOneConstructor() + && (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) { + String gripe = "Test class should have exactly one public zero-argument constructor"; + errors.add(new Exception(gripe)); + } + } + + private boolean hasOneConstructor() { + return getTestClass().getJavaClass().getConstructors().length == 1; + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}, + * {@code @Before}, or {@code @After} that is not a public, void instance + * method with no arguments. + */ + @Deprecated + protected void validateInstanceMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(After.class, false, errors); + validatePublicVoidNoArgMethods(Before.class, false, errors); + validateTestMethods(errors); + + if (computeTestMethods().size() == 0) { + errors.add(new Exception("No runnable methods")); + } + } + + protected void validateFields(List<Throwable> errors) { + RULE_VALIDATOR.validate(getTestClass(), errors); + } + + private void validateMethods(List<Throwable> errors) { + RULE_METHOD_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}that + * is not a public, void instance method with no arguments. + */ + protected void validateTestMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(Test.class, false, errors); + } + + /** + * Returns a new fixture for running a test. Default implementation executes + * the test class's no-argument constructor (validation should have ensured + * one exists). + */ + protected Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(); + } + + /** + * Returns the name that describes {@code method} for {@link Description}s. + * Default implementation is the method's name + */ + protected String testName(FrameworkMethod method) { + return method.getName(); + } + + /** + * Returns a Statement that, when executed, either returns normally if + * {@code method} passes, or throws an exception if {@code method} fails. + * + * Here is an outline of the default implementation: + * + * <ul> + * <li>Invoke {@code method} on the result of {@code createTest()}, and + * throw any exceptions thrown by either operation. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * expecting} attribute, return normally only if the previous step threw an + * exception of the correct type, and throw an exception otherwise. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * timeout} attribute, throw an exception if the previous step takes more + * than the specified number of milliseconds. + * <li>ALWAYS run all non-overridden {@code @Before} methods on this class + * and superclasses before any of the previous steps; if any throws an + * Exception, stop execution and pass the exception on. + * <li>ALWAYS run all non-overridden {@code @After} methods on this class + * and superclasses after any of the previous steps; all After methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + * <li>ALWAYS allow {@code @Rule} fields to modify the execution of the + * above steps. A {@code Rule} may prevent all execution of the above steps, + * or add additional behavior before and after, or modify thrown exceptions. + * For more information, see {@link TestRule} + * </ul> + * + * This can be overridden in subclasses, either by overriding this method, + * or the implementations creating each sub-statement. + */ + protected Statement methodBlock(FrameworkMethod method) { + Object test; + try { + test = new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return createTest(); + } + }.run(); + } catch (Throwable e) { + return new Fail(e); + } + + Statement statement = methodInvoker(method, test); + statement = possiblyExpectingExceptions(method, test, statement); + statement = withPotentialTimeout(method, test, statement); + statement = withBefores(method, test, statement); + statement = withAfters(method, test, statement); + statement = withRules(method, test, statement); + return statement; + } + + // + // Statement builders + // + + /** + * Returns a {@link Statement} that invokes {@code method} on {@code test} + */ + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return new InvokeMethod(method, test); + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code expecting} attribute, return normally only if {@code next} + * throws an exception of the correct type, and throw an exception + * otherwise. + */ + protected Statement possiblyExpectingExceptions(FrameworkMethod method, + Object test, Statement next) { + Test annotation = method.getAnnotation(Test.class); + return expectsException(annotation) ? new ExpectException(next, + getExpectedException(annotation)) : next; + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code timeout} attribute, throw an exception if {@code next} + * takes more than the specified number of milliseconds. + */ + @Deprecated + protected Statement withPotentialTimeout(FrameworkMethod method, + Object test, Statement next) { + long timeout = getTimeout(method.getAnnotation(Test.class)); + if (timeout <= 0) { + return next; + } + return FailOnTimeout.builder() + .withTimeout(timeout, TimeUnit.MILLISECONDS) + .build(next); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @Before} + * methods on this class and superclasses before running {@code next}; if + * any throws an Exception, stop execution and pass the exception on. + */ + protected Statement withBefores(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods( + Before.class); + return befores.isEmpty() ? statement : new RunBefores(statement, + befores, target); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @After} + * methods on this class and superclasses before running {@code next}; all + * After methods are always executed: exceptions thrown by previous steps + * are combined, if necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + */ + protected Statement withAfters(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods( + After.class); + return afters.isEmpty() ? statement : new RunAfters(statement, afters, + target); + } + + private Statement withRules(FrameworkMethod method, Object target, + Statement statement) { + List<TestRule> testRules = getTestRules(target); + Statement result = statement; + result = withMethodRules(method, testRules, target, result); + result = withTestRules(method, testRules, result); + + return result; + } + + private Statement withMethodRules(FrameworkMethod method, List<TestRule> testRules, + Object target, Statement result) { + for (org.junit.rules.MethodRule each : getMethodRules(target)) { + if (!testRules.contains(each)) { + result = each.apply(result, method, target); + } + } + return result; + } + + private List<org.junit.rules.MethodRule> getMethodRules(Object target) { + return rules(target); + } + + /** + * @param target the test case instance + * @return a list of MethodRules that should be applied when executing this + * test + */ + protected List<MethodRule> rules(Object target) { + List<MethodRule> rules = getTestClass().getAnnotatedMethodValues(target, + Rule.class, MethodRule.class); + + rules.addAll(getTestClass().getAnnotatedFieldValues(target, + Rule.class, MethodRule.class)); + + return rules; + } + + /** + * Returns a {@link Statement}: apply all non-static fields + * annotated with {@link Rule}. + * + * @param statement The base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withTestRules(FrameworkMethod method, List<TestRule> testRules, + Statement statement) { + return testRules.isEmpty() ? statement : + new RunRules(statement, testRules, describeChild(method)); + } + + /** + * @param target the test case instance + * @return a list of TestRules that should be applied when executing this + * test + */ + protected List<TestRule> getTestRules(Object target) { + List<TestRule> result = getTestClass().getAnnotatedMethodValues(target, + Rule.class, TestRule.class); + + result.addAll(getTestClass().getAnnotatedFieldValues(target, + Rule.class, TestRule.class)); + + return result; + } + + private Class<? extends Throwable> getExpectedException(Test annotation) { + if (annotation == null || annotation.expected() == None.class) { + return null; + } else { + return annotation.expected(); + } + } + + private boolean expectsException(Test annotation) { + return getExpectedException(annotation) != null; + } + + private long getTimeout(Test annotation) { + if (annotation == null) { + return 0; + } + return annotation.timeout(); + } } diff --git a/src/main/java/org/junit/runners/JUnit4.java b/src/main/java/org/junit/runners/JUnit4.java index 1e1f347..6ba28c2 100644 --- a/src/main/java/org/junit/runners/JUnit4.java +++ b/src/main/java/org/junit/runners/JUnit4.java @@ -11,12 +11,14 @@ import org.junit.runners.model.InitializationError; * This is the only way this class should be used--any extension that * depends on the implementation details of this class is likely to break * in future versions. + * + * @since 4.5 */ public final class JUnit4 extends BlockJUnit4ClassRunner { - /** - * Constructs a new instance of the default runner - */ - public JUnit4(Class<?> klass) throws InitializationError { - super(klass); - } + /** + * Constructs a new instance of the default runner + */ + public JUnit4(Class<?> klass) throws InitializationError { + super(klass); + } } diff --git a/src/main/java/org/junit/runners/MethodSorters.java b/src/main/java/org/junit/runners/MethodSorters.java new file mode 100644 index 0000000..5821892 --- /dev/null +++ b/src/main/java/org/junit/runners/MethodSorters.java @@ -0,0 +1,41 @@ +package org.junit.runners; + +import java.lang.reflect.Method; +import java.util.Comparator; + +import org.junit.internal.MethodSorter; + +/** + * Sort the methods into a specified execution order. + * Defines common {@link MethodSorter} implementations. + * + * @since 4.11 + */ +public enum MethodSorters { + /** + * Sorts the test methods by the method name, in lexicographic order, + * with {@link Method#toString()} used as a tiebreaker + */ + NAME_ASCENDING(MethodSorter.NAME_ASCENDING), + + /** + * Leaves the test methods in the order returned by the JVM. + * Note that the order from the JVM may vary from run to run + */ + JVM(null), + + /** + * Sorts the test methods in a deterministic, but not predictable, order + */ + DEFAULT(MethodSorter.DEFAULT); + + private final Comparator<Method> comparator; + + private MethodSorters(Comparator<Method> comparator) { + this.comparator = comparator; + } + + public Comparator<Method> getComparator() { + return comparator; + } +} diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java index 3ebfead..829c8f0 100644 --- a/src/main/java/org/junit/runners/Parameterized.java +++ b/src/main/java/org/junit/runners/Parameterized.java @@ -1,167 +1,351 @@ package org.junit.runners; -import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.reflect.Modifier; +import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; +import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; +import org.junit.runners.parameterized.ParametersRunnerFactory; +import org.junit.runners.parameterized.TestWithParameters; /** - * <p> * The custom runner <code>Parameterized</code> implements parameterized tests. * When running a parameterized test class, instances are created for the * cross-product of the test methods and the test data elements. - * </p> - * + * <p> * For example, to test a Fibonacci function, write: - * * <pre> * @RunWith(Parameterized.class) * public class FibonacciTest { - * @Parameters - * public static List<Object[]> data() { - * return Arrays.asList(new Object[][] { - * { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } - * }); - * } - * - * private int fInput; - * - * private int fExpected; - * - * public FibonacciTest(int input, int expected) { - * fInput= input; - * fExpected= expected; - * } - * - * @Test - * public void test() { - * assertEquals(fExpected, Fibonacci.compute(fInput)); - * } + * @Parameters(name= "{index}: fib[{0}]={1}") + * public static Iterable<Object[]> data() { + * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, + * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); + * } + * + * private int fInput; + * + * private int fExpected; + * + * public FibonacciTest(int input, int expected) { + * fInput= input; + * fExpected= expected; + * } + * + * @Test + * public void test() { + * assertEquals(fExpected, Fibonacci.compute(fInput)); + * } * } * </pre> - * * <p> * Each instance of <code>FibonacciTest</code> will be constructed using the * two-argument constructor and the data values in the * <code>@Parameters</code> method. - * </p> + * <p> + * In order that you can easily identify the individual tests, you may provide a + * name for the <code>@Parameters</code> annotation. This name is allowed + * to contain placeholders, which are replaced at runtime. The placeholders are + * <dl> + * <dt>{index}</dt> + * <dd>the current parameter index</dd> + * <dt>{0}</dt> + * <dd>the first parameter value</dd> + * <dt>{1}</dt> + * <dd>the second parameter value</dd> + * <dt>...</dt> + * <dd>...</dd> + * </dl> + * <p> + * In the example given above, the <code>Parameterized</code> runner creates + * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter, + * then the current parameter index is used as name. + * <p> + * You can also write: + * <pre> + * @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; + * + * @Parameter(1) + * public int fExpected; + * + * @Test + * public void test() { + * assertEquals(fExpected, Fibonacci.compute(fInput)); + * } + * } + * </pre> + * <p> + * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor + * and fields annotated by <code>@Parameter</code> will be initialized + * with the data values in the <code>@Parameters</code> method. + * + * <p> + * The parameters can be provided as an array, too: + * + * <pre> + * @Parameters + * public static Object[][] data() { + * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, + * { 5, 5 }, { 6, 8 } }; + * } + * </pre> + * + * <h3>Tests with single parameter</h3> + * <p> + * If your test needs a single parameter only, you don't have to wrap it with an + * array. Instead you can provide an <code>Iterable</code> or an array of + * objects. + * <pre> + * @Parameters + * public static Iterable<? extends Object> data() { + * return Arrays.asList("first test", "second test"); + * } + * </pre> + * <p> + * or + * <pre> + * @Parameters + * public static Object[] data() { + * return new Object[] { "first test", "second test" }; + * } + * </pre> + * + * <h3>Create different runners</h3> + * <p> + * By default the {@code Parameterized} runner creates a slightly modified + * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an + * own {@code Parameterized} runner that creates another runner for each set of + * parameters. Therefore you have to build a {@link ParametersRunnerFactory} + * that creates a runner for each {@link TestWithParameters}. ( + * {@code TestWithParameters} are bundling the parameters and the test name.) + * The factory must have a public zero-arg constructor. + * + * <pre> + * public class YourRunnerFactory implements ParameterizedRunnerFactory { + * public Runner createRunnerForTestWithParameters(TestWithParameters test) + * throws InitializationError { + * return YourRunner(test); + * } + * } + * </pre> + * <p> + * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized} + * runner that it should use your factory. + * + * <pre> + * @RunWith(Parameterized.class) + * @UseParametersRunnerFactory(YourRunnerFactory.class) + * public class YourTest { + * ... + * } + * </pre> + * + * @since 4.0 */ public class Parameterized extends Suite { - /** - * Annotation for a method which provides parameters to be injected into the - * test class constructor by <code>Parameterized</code> - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public static @interface Parameters { - } - - private class TestClassRunnerForParameters extends - BlockJUnit4ClassRunner { - private final int fParameterSetNumber; - - private final List<Object[]> fParameterList; - - TestClassRunnerForParameters(Class<?> type, - List<Object[]> parameterList, int i) throws InitializationError { - super(type); - fParameterList= parameterList; - fParameterSetNumber= i; - } - - @Override - public Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance( - computeParams()); - } - - private Object[] computeParams() throws Exception { - try { - return fParameterList.get(fParameterSetNumber); - } catch (ClassCastException e) { - throw new Exception(String.format( - "%s.%s() must return a Collection of arrays.", - getTestClass().getName(), getParametersMethod( - getTestClass()).getName())); - } - } - - @Override - protected String getName() { - return String.format("[%s]", fParameterSetNumber); - } - - @Override - protected String testName(final FrameworkMethod method) { - return String.format("%s[%s]", method.getName(), - fParameterSetNumber); - } - - @Override - protected void validateConstructor(List<Throwable> errors) { - validateOnlyOneConstructor(errors); - } - - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); - } - - @Override - protected Annotation[] getRunnerAnnotations() { - return new Annotation[0]; - } - } - - private final ArrayList<Runner> runners= new ArrayList<Runner>(); - - /** - * Only called reflectively. Do not use programmatically. - */ - public Parameterized(Class<?> klass) throws Throwable { - super(klass, Collections.<Runner>emptyList()); - List<Object[]> parametersList= getParametersList(getTestClass()); - for (int i= 0; i < parametersList.size(); i++) - runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(), - parametersList, i)); - } - - @Override - protected List<Runner> getChildren() { - return runners; - } - - @SuppressWarnings("unchecked") - private List<Object[]> getParametersList(TestClass klass) - throws Throwable { - return (List<Object[]>) getParametersMethod(klass).invokeExplosively( - null); - } - - private FrameworkMethod getParametersMethod(TestClass testClass) - throws Exception { - List<FrameworkMethod> methods= testClass - .getAnnotatedMethods(Parameters.class); - for (FrameworkMethod each : methods) { - int modifiers= each.getMethod().getModifiers(); - if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) - return each; - } - - throw new Exception("No public static parameters method on class " - + testClass.getName()); - } + /** + * Annotation for a method which provides parameters to be injected into the + * test class constructor by <code>Parameterized</code>. The method has to + * be public and static. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface Parameters { + /** + * Optional pattern to derive the test's name from the parameters. Use + * numbers in braces to refer to the parameters or the additional data + * as follows: + * <pre> + * {index} - the current parameter index + * {0} - the first parameter value + * {1} - the second parameter value + * etc... + * </pre> + * <p> + * Default value is "{index}" for compatibility with previous JUnit + * versions. + * + * @return {@link MessageFormat} pattern string, except the index + * placeholder. + * @see MessageFormat + */ + String name() default "{index}"; + } + + /** + * Annotation for fields of the test class which will be initialized by the + * method annotated by <code>Parameters</code>. + * By using directly this annotation, the test class constructor isn't needed. + * Index range must start at 0. + * Default value is 0. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public static @interface Parameter { + /** + * Method that returns the index of the parameter in the array + * returned by the method annotated by <code>Parameters</code>. + * Index range must start at 0. + * Default value is 0. + * + * @return the index of the parameter. + */ + int value() default 0; + } + + /** + * Add this annotation to your test class if you want to generate a special + * runner. You have to specify a {@link ParametersRunnerFactory} class that + * creates such runners. The factory must have a public zero-arg + * constructor. + */ + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @Target(ElementType.TYPE) + public @interface UseParametersRunnerFactory { + /** + * @return a {@link ParametersRunnerFactory} class (must have a default + * constructor) + */ + Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class; + } + + private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); + + private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList(); + + private final List<Runner> runners; + + /** + * Only called reflectively. Do not use programmatically. + */ + public Parameterized(Class<?> klass) throws Throwable { + super(klass, NO_RUNNERS); + ParametersRunnerFactory runnerFactory = getParametersRunnerFactory( + klass); + Parameters parameters = getParametersMethod().getAnnotation( + Parameters.class); + runners = Collections.unmodifiableList(createRunnersForParameters( + allParameters(), parameters.name(), runnerFactory)); + } + + private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass) + throws InstantiationException, IllegalAccessException { + UseParametersRunnerFactory annotation = klass + .getAnnotation(UseParametersRunnerFactory.class); + if (annotation == null) { + return DEFAULT_FACTORY; + } else { + Class<? extends ParametersRunnerFactory> factoryClass = annotation + .value(); + return factoryClass.newInstance(); + } + } + + @Override + protected List<Runner> getChildren() { + return runners; + } + + private TestWithParameters createTestWithNotNormalizedParameters( + String pattern, int index, Object parametersOrSingleParameter) { + Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter + : new Object[] { parametersOrSingleParameter }; + return createTestWithParameters(getTestClass(), pattern, index, + parameters); + } + + @SuppressWarnings("unchecked") + private Iterable<Object> allParameters() throws Throwable { + Object parameters = getParametersMethod().invokeExplosively(null); + if (parameters instanceof Iterable) { + return (Iterable<Object>) parameters; + } else if (parameters instanceof Object[]) { + return Arrays.asList((Object[]) parameters); + } else { + throw parametersMethodReturnedWrongType(); + } + } + + private FrameworkMethod getParametersMethod() throws Exception { + List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods( + Parameters.class); + for (FrameworkMethod each : methods) { + if (each.isStatic() && each.isPublic()) { + return each; + } + } + + throw new Exception("No public static parameters method on class " + + getTestClass().getName()); + } + + private List<Runner> createRunnersForParameters( + Iterable<Object> allParameters, String namePattern, + ParametersRunnerFactory runnerFactory) + throws InitializationError, + Exception { + try { + List<TestWithParameters> tests = createTestsForParameters( + allParameters, namePattern); + List<Runner> runners = new ArrayList<Runner>(); + for (TestWithParameters test : tests) { + runners.add(runnerFactory + .createRunnerForTestWithParameters(test)); + } + return runners; + } catch (ClassCastException e) { + throw parametersMethodReturnedWrongType(); + } + } + + private List<TestWithParameters> createTestsForParameters( + Iterable<Object> allParameters, String namePattern) + throws Exception { + int i = 0; + List<TestWithParameters> children = new ArrayList<TestWithParameters>(); + for (Object parametersOfSingleTest : allParameters) { + children.add(createTestWithNotNormalizedParameters(namePattern, + i++, parametersOfSingleTest)); + } + return children; + } + + private Exception parametersMethodReturnedWrongType() throws Exception { + String className = getTestClass().getName(); + String methodName = getParametersMethod().getName(); + String message = MessageFormat.format( + "{0}.{1}() must return an Iterable of arrays.", + className, methodName); + return new Exception(message); + } + private static TestWithParameters createTestWithParameters( + TestClass testClass, String pattern, int index, Object[] parameters) { + String finalPattern = pattern.replaceAll("\\{index\\}", + Integer.toString(index)); + String name = MessageFormat.format(finalPattern, parameters); + return new TestWithParameters("[" + name + "]", testClass, + Arrays.asList(parameters)); + } } diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java index a41aad3..92641bf 100644..100755 --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -1,10 +1,13 @@ package org.junit.runners; -import static org.junit.internal.runners.rules.RuleFieldValidator.CLASS_RULE_VALIDATOR; +import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_METHOD_VALIDATOR; +import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_VALIDATOR; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -13,6 +16,7 @@ import java.util.List; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; @@ -31,10 +35,12 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; -import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; +import org.junit.validator.AnnotationsValidator; +import org.junit.validator.PublicClassValidator; +import org.junit.validator.TestClassValidator; /** * Provides most of the functionality specific to a Runner that implements a @@ -43,336 +49,404 @@ import org.junit.runners.model.TestClass; * {@link Method} . For {@link Suite}, {@code T} is {@link Class}.) Subclasses * must implement finding the children of the node, describing each child, and * running each child. ParentRunner will filter and sort children, handle - * {@code @BeforeClass} and {@code @AfterClass} methods, + * {@code @BeforeClass} and {@code @AfterClass} methods, * handle annotated {@link ClassRule}s, create a composite * {@link Description}, and run children sequentially. + * + * @since 4.5 */ public abstract class ParentRunner<T> extends Runner implements Filterable, - Sortable { - private final TestClass fTestClass; - - private Sorter fSorter= Sorter.NULL; - - private List<T> fFilteredChildren= null; - - private RunnerScheduler fScheduler= new RunnerScheduler() { - public void schedule(Runnable childStatement) { - childStatement.run(); - } - - public void finished() { - // do nothing - } - }; - - /** - * Constructs a new {@code ParentRunner} that will run {@code @TestClass} - * @throws InitializationError - */ - protected ParentRunner(Class<?> testClass) throws InitializationError { - fTestClass= new TestClass(testClass); - validate(); - } - - // - // Must be overridden - // - - /** - * Returns a list of objects that define the children of this Runner. - */ - protected abstract List<T> getChildren(); - - /** - * Returns a {@link Description} for {@code child}, which can be assumed to - * be an element of the list returned by {@link ParentRunner#getChildren()} - */ - protected abstract Description describeChild(T child); - - /** - * Runs the test corresponding to {@code child}, which can be assumed to be - * an element of the list returned by {@link ParentRunner#getChildren()}. - * Subclasses are responsible for making sure that relevant test events are - * reported through {@code notifier} - */ - protected abstract void runChild(T child, RunNotifier notifier); - - // - // May be overridden - // - - /** - * Adds to {@code errors} a throwable for each problem noted with the test class (available from {@link #getTestClass()}). - * Default implementation adds an error for each method annotated with - * {@code @BeforeClass} or {@code @AfterClass} that is not - * {@code public static void} with no arguments. - */ - protected void collectInitializationErrors(List<Throwable> errors) { - validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); - validatePublicVoidNoArgMethods(AfterClass.class, true, errors); - validateClassRules(errors); - } - - /** - * Adds to {@code errors} if any method in this class is annotated with - * {@code annotation}, but: - * <ul> - * <li>is not public, or - * <li>takes parameters, or - * <li>returns something other than void, or - * <li>is static (given {@code isStatic is false}), or - * <li>is not static (given {@code isStatic is true}). - */ - protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, - boolean isStatic, List<Throwable> errors) { - List<FrameworkMethod> methods= getTestClass().getAnnotatedMethods(annotation); - - for (FrameworkMethod eachTestMethod : methods) - eachTestMethod.validatePublicVoidNoArg(isStatic, errors); - } - - private void validateClassRules(List<Throwable> errors) { - CLASS_RULE_VALIDATOR.validate(getTestClass(), errors); - } - - /** - * Constructs a {@code Statement} to run all of the tests in the test class. Override to add pre-/post-processing. - * Here is an outline of the implementation: - * <ul> - * <li>Call {@link #runChild(Object, RunNotifier)} on each object returned by {@link #getChildren()} (subject to any imposed filter and sort).</li> - * <li>ALWAYS run all non-overridden {@code @BeforeClass} methods on this class - * and superclasses before the previous step; if any throws an - * Exception, stop execution and pass the exception on. - * <li>ALWAYS run all non-overridden {@code @AfterClass} methods on this class - * and superclasses before any of the previous steps; all AfterClass methods are - * always executed: exceptions thrown by previous steps are combined, if - * necessary, with exceptions from AfterClass methods into a - * {@link MultipleFailureException}. - * </ul> - * @param notifier - * @return {@code Statement} - */ - protected Statement classBlock(final RunNotifier notifier) { - Statement statement= childrenInvoker(notifier); - statement= withBeforeClasses(statement); - statement= withAfterClasses(statement); - statement= withClassRules(statement); - return statement; - } - - /** - * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class - * and superclasses before executing {@code statement}; if any throws an - * Exception, stop execution and pass the exception on. - */ - protected Statement withBeforeClasses(Statement statement) { - List<FrameworkMethod> befores= fTestClass - .getAnnotatedMethods(BeforeClass.class); - return befores.isEmpty() ? statement : - new RunBefores(statement, befores, null); - } - - /** - * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class - * and superclasses before executing {@code statement}; all AfterClass methods are - * always executed: exceptions thrown by previous steps are combined, if - * necessary, with exceptions from AfterClass methods into a - * {@link MultipleFailureException}. - */ - protected Statement withAfterClasses(Statement statement) { - List<FrameworkMethod> afters= fTestClass - .getAnnotatedMethods(AfterClass.class); - return afters.isEmpty() ? statement : - new RunAfters(statement, afters, null); - } - - /** - * Returns a {@link Statement}: apply all - * static fields assignable to {@link TestRule} - * annotated with {@link ClassRule}. - * - * @param statement - * the base statement - * @return a RunRules statement if any class-level {@link Rule}s are - * found, or the base statement - */ - private Statement withClassRules(Statement statement) { - List<TestRule> classRules= classRules(); - return classRules.isEmpty() ? statement : - new RunRules(statement, classRules, getDescription()); - } - - /** - * @return the {@code ClassRule}s that can transform the block that runs - * each method in the tested class. - */ - protected List<TestRule> classRules() { - return fTestClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class); - } - - /** - * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} - * on each object returned by {@link #getChildren()} (subject to any imposed - * filter and sort) - */ - protected Statement childrenInvoker(final RunNotifier notifier) { - return new Statement() { - @Override - public void evaluate() { - runChildren(notifier); - } - }; - } - - private void runChildren(final RunNotifier notifier) { - for (final T each : getFilteredChildren()) - fScheduler.schedule(new Runnable() { - public void run() { - ParentRunner.this.runChild(each, notifier); - } - }); - fScheduler.finished(); - } - - /** - * Returns a name used to describe this Runner - */ - protected String getName() { - return fTestClass.getName(); - } - - // - // Available for subclasses - // - - /** - * Returns a {@link TestClass} object wrapping the class to be executed. - */ - public final TestClass getTestClass() { - return fTestClass; - } - - /** - * Runs a {@link Statement} that represents a leaf (aka atomic) test. - */ - protected final void runLeaf(Statement statement, Description description, - RunNotifier notifier) { - EachTestNotifier eachNotifier= new EachTestNotifier(notifier, description); - eachNotifier.fireTestStarted(); - try { - statement.evaluate(); - } catch (AssumptionViolatedException e) { - eachNotifier.addFailedAssumption(e); - } catch (Throwable e) { - eachNotifier.addFailure(e); - } finally { - eachNotifier.fireTestFinished(); - } - } - - /** - * @return the annotations that should be attached to this runner's - * description. - */ - protected Annotation[] getRunnerAnnotations() { - return fTestClass.getAnnotations(); - } - - // - // Implementation of Runner - // - - @Override - public Description getDescription() { - Description description= Description.createSuiteDescription(getName(), - getRunnerAnnotations()); - for (T child : getFilteredChildren()) - description.addChild(describeChild(child)); - return description; - } - - @Override - public void run(final RunNotifier notifier) { - EachTestNotifier testNotifier= new EachTestNotifier(notifier, - getDescription()); - try { - Statement statement= classBlock(notifier); - statement.evaluate(); - } catch (AssumptionViolatedException e) { - testNotifier.fireTestIgnored(); - } catch (StoppedByUserException e) { - throw e; - } catch (Throwable e) { - testNotifier.addFailure(e); - } - } - - // - // Implementation of Filterable and Sortable - // - - public void filter(Filter filter) throws NoTestsRemainException { - for (Iterator<T> iter = getFilteredChildren().iterator(); iter.hasNext(); ) { - T each = iter.next(); - if (shouldRun(filter, each)) - try { - filter.apply(each); - } catch (NoTestsRemainException e) { - iter.remove(); - } - else - iter.remove(); - } - if (getFilteredChildren().isEmpty()) { - throw new NoTestsRemainException(); - } - } - - public void sort(Sorter sorter) { - fSorter= sorter; - for (T each : getFilteredChildren()) - sortChild(each); - Collections.sort(getFilteredChildren(), comparator()); - } - - // - // Private implementation - // - - private void validate() throws InitializationError { - List<Throwable> errors= new ArrayList<Throwable>(); - collectInitializationErrors(errors); - if (!errors.isEmpty()) - throw new InitializationError(errors); - } - - private List<T> getFilteredChildren() { - if (fFilteredChildren == null) - fFilteredChildren = new ArrayList<T>(getChildren()); - return fFilteredChildren; - } - - private void sortChild(T child) { - fSorter.apply(child); - } - - private boolean shouldRun(Filter filter, T each) { - return filter.shouldRun(describeChild(each)); - } - - private Comparator<? super T> comparator() { - return new Comparator<T>() { - public int compare(T o1, T o2) { - return fSorter.compare(describeChild(o1), describeChild(o2)); - } - }; - } - - /** - * Sets a scheduler that determines the order and parallelization - * of children. Highly experimental feature that may change. - */ - public void setScheduler(RunnerScheduler scheduler) { - this.fScheduler = scheduler; - } + Sortable { + private static final List<TestClassValidator> VALIDATORS = Arrays.asList( + new AnnotationsValidator(), new PublicClassValidator()); + + private final Object childrenLock = new Object(); + private final TestClass testClass; + + // Guarded by childrenLock + private volatile Collection<T> filteredChildren = null; + + private volatile RunnerScheduler scheduler = new RunnerScheduler() { + public void schedule(Runnable childStatement) { + childStatement.run(); + } + + public void finished() { + // do nothing + } + }; + + /** + * Constructs a new {@code ParentRunner} that will run {@code @TestClass} + */ + protected ParentRunner(Class<?> testClass) throws InitializationError { + this.testClass = createTestClass(testClass); + validate(); + } + + protected TestClass createTestClass(Class<?> testClass) { + return new TestClass(testClass); + } + + // + // Must be overridden + // + + /** + * Returns a list of objects that define the children of this Runner. + */ + protected abstract List<T> getChildren(); + + /** + * Returns a {@link Description} for {@code child}, which can be assumed to + * be an element of the list returned by {@link ParentRunner#getChildren()} + */ + protected abstract Description describeChild(T child); + + /** + * Runs the test corresponding to {@code child}, which can be assumed to be + * an element of the list returned by {@link ParentRunner#getChildren()}. + * Subclasses are responsible for making sure that relevant test events are + * reported through {@code notifier} + */ + protected abstract void runChild(T child, RunNotifier notifier); + + // + // May be overridden + // + + /** + * Adds to {@code errors} a throwable for each problem noted with the test class (available from {@link #getTestClass()}). + * Default implementation adds an error for each method annotated with + * {@code @BeforeClass} or {@code @AfterClass} that is not + * {@code public static void} with no arguments. + */ + protected void collectInitializationErrors(List<Throwable> errors) { + validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); + validatePublicVoidNoArgMethods(AfterClass.class, true, errors); + validateClassRules(errors); + applyValidators(errors); + } + + private void applyValidators(List<Throwable> errors) { + if (getTestClass().getJavaClass() != null) { + for (TestClassValidator each : VALIDATORS) { + errors.addAll(each.validateTestClass(getTestClass())); + } + } + } + + /** + * Adds to {@code errors} if any method in this class is annotated with + * {@code annotation}, but: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + * </ul> + */ + protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, + boolean isStatic, List<Throwable> errors) { + List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation); + + for (FrameworkMethod eachTestMethod : methods) { + eachTestMethod.validatePublicVoidNoArg(isStatic, errors); + } + } + + private void validateClassRules(List<Throwable> errors) { + CLASS_RULE_VALIDATOR.validate(getTestClass(), errors); + CLASS_RULE_METHOD_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Constructs a {@code Statement} to run all of the tests in the test class. + * Override to add pre-/post-processing. Here is an outline of the + * implementation: + * <ol> + * <li>Determine the children to be run using {@link #getChildren()} + * (subject to any imposed filter and sort).</li> + * <li>If there are any children remaining after filtering and ignoring, + * construct a statement that will: + * <ol> + * <li>Apply all {@code ClassRule}s on the test-class and superclasses.</li> + * <li>Run all non-overridden {@code @BeforeClass} methods on the test-class + * and superclasses; if any throws an Exception, stop execution and pass the + * exception on.</li> + * <li>Run all remaining tests on the test-class.</li> + * <li>Run all non-overridden {@code @AfterClass} methods on the test-class + * and superclasses: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link org.junit.runners.model.MultipleFailureException}.</li> + * </ol> + * </li> + * </ol> + * + * @return {@code Statement} + */ + protected Statement classBlock(final RunNotifier notifier) { + Statement statement = childrenInvoker(notifier); + if (!areAllChildrenIgnored()) { + statement = withBeforeClasses(statement); + statement = withAfterClasses(statement); + statement = withClassRules(statement); + } + return statement; + } + + private boolean areAllChildrenIgnored() { + for (T child : getFilteredChildren()) { + if (!isIgnored(child)) { + return false; + } + } + return true; + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class + * and superclasses before executing {@code statement}; if any throws an + * Exception, stop execution and pass the exception on. + */ + protected Statement withBeforeClasses(Statement statement) { + List<FrameworkMethod> befores = testClass + .getAnnotatedMethods(BeforeClass.class); + return befores.isEmpty() ? statement : + new RunBefores(statement, befores, null); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class + * and superclasses before executing {@code statement}; all AfterClass methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link org.junit.runners.model.MultipleFailureException}. + */ + protected Statement withAfterClasses(Statement statement) { + List<FrameworkMethod> afters = testClass + .getAnnotatedMethods(AfterClass.class); + return afters.isEmpty() ? statement : + new RunAfters(statement, afters, null); + } + + /** + * Returns a {@link Statement}: apply all + * static fields assignable to {@link TestRule} + * annotated with {@link ClassRule}. + * + * @param statement the base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withClassRules(Statement statement) { + List<TestRule> classRules = classRules(); + return classRules.isEmpty() ? statement : + new RunRules(statement, classRules, getDescription()); + } + + /** + * @return the {@code ClassRule}s that can transform the block that runs + * each method in the tested class. + */ + protected List<TestRule> classRules() { + List<TestRule> result = testClass.getAnnotatedMethodValues(null, ClassRule.class, TestRule.class); + result.addAll(testClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class)); + return result; + } + + /** + * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} + * on each object returned by {@link #getChildren()} (subject to any imposed + * filter and sort) + */ + protected Statement childrenInvoker(final RunNotifier notifier) { + return new Statement() { + @Override + public void evaluate() { + runChildren(notifier); + } + }; + } + + /** + * Evaluates whether a child is ignored. The default implementation always + * returns <code>false</code>. + * + * <p>{@link BlockJUnit4ClassRunner}, for example, overrides this method to + * filter tests based on the {@link Ignore} annotation. + */ + protected boolean isIgnored(T child) { + return false; + } + + private void runChildren(final RunNotifier notifier) { + final RunnerScheduler currentScheduler = scheduler; + try { + for (final T each : getFilteredChildren()) { + currentScheduler.schedule(new Runnable() { + public void run() { + ParentRunner.this.runChild(each, notifier); + } + }); + } + } finally { + currentScheduler.finished(); + } + } + + /** + * Returns a name used to describe this Runner + */ + protected String getName() { + return testClass.getName(); + } + + // + // Available for subclasses + // + + /** + * Returns a {@link TestClass} object wrapping the class to be executed. + */ + public final TestClass getTestClass() { + return testClass; + } + + /** + * Runs a {@link Statement} that represents a leaf (aka atomic) test. + */ + protected final void runLeaf(Statement statement, Description description, + RunNotifier notifier) { + EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description); + eachNotifier.fireTestStarted(); + try { + statement.evaluate(); + } catch (AssumptionViolatedException e) { + eachNotifier.addFailedAssumption(e); + } catch (Throwable e) { + eachNotifier.addFailure(e); + } finally { + eachNotifier.fireTestFinished(); + } + } + + /** + * @return the annotations that should be attached to this runner's + * description. + */ + protected Annotation[] getRunnerAnnotations() { + return testClass.getAnnotations(); + } + + // + // Implementation of Runner + // + + @Override + public Description getDescription() { + Description description = Description.createSuiteDescription(getName(), + getRunnerAnnotations()); + for (T child : getFilteredChildren()) { + description.addChild(describeChild(child)); + } + return description; + } + + @Override + public void run(final RunNotifier notifier) { + EachTestNotifier testNotifier = new EachTestNotifier(notifier, + getDescription()); + try { + Statement statement = classBlock(notifier); + statement.evaluate(); + } catch (AssumptionViolatedException e) { + testNotifier.addFailedAssumption(e); + } catch (StoppedByUserException e) { + throw e; + } catch (Throwable e) { + testNotifier.addFailure(e); + } + } + + // + // Implementation of Filterable and Sortable + // + + public void filter(Filter filter) throws NoTestsRemainException { + synchronized (childrenLock) { + List<T> children = new ArrayList<T>(getFilteredChildren()); + for (Iterator<T> iter = children.iterator(); iter.hasNext(); ) { + T each = iter.next(); + if (shouldRun(filter, each)) { + try { + filter.apply(each); + } catch (NoTestsRemainException e) { + iter.remove(); + } + } else { + iter.remove(); + } + } + filteredChildren = Collections.unmodifiableCollection(children); + if (filteredChildren.isEmpty()) { + throw new NoTestsRemainException(); + } + } + } + + public void sort(Sorter sorter) { + synchronized (childrenLock) { + for (T each : getFilteredChildren()) { + sorter.apply(each); + } + List<T> sortedChildren = new ArrayList<T>(getFilteredChildren()); + Collections.sort(sortedChildren, comparator(sorter)); + filteredChildren = Collections.unmodifiableCollection(sortedChildren); + } + } + + // + // Private implementation + // + + private void validate() throws InitializationError { + List<Throwable> errors = new ArrayList<Throwable>(); + collectInitializationErrors(errors); + if (!errors.isEmpty()) { + throw new InitializationError(errors); + } + } + + private Collection<T> getFilteredChildren() { + if (filteredChildren == null) { + synchronized (childrenLock) { + if (filteredChildren == null) { + filteredChildren = Collections.unmodifiableCollection(getChildren()); + } + } + } + return filteredChildren; + } + + private boolean shouldRun(Filter filter, T each) { + return filter.shouldRun(describeChild(each)); + } + + private Comparator<? super T> comparator(final Sorter sorter) { + return new Comparator<T>() { + public int compare(T o1, T o2) { + return sorter.compare(describeChild(o1), describeChild(o2)); + } + }; + } + + /** + * Sets a scheduler that determines the order and parallelization + * of children. Highly experimental feature that may change. + */ + public void setScheduler(RunnerScheduler scheduler) { + this.scheduler = scheduler; + } } diff --git a/src/main/java/org/junit/runners/Suite.java b/src/main/java/org/junit/runners/Suite.java index 1b3bb48..b37179f 100644 --- a/src/main/java/org/junit/runners/Suite.java +++ b/src/main/java/org/junit/runners/Suite.java @@ -5,6 +5,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Collections; import java.util.List; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; @@ -20,111 +21,110 @@ import org.junit.runners.model.RunnerBuilder; * static {@link junit.framework.Test} <code>suite()</code> method. To use it, annotate a class * with <code>@RunWith(Suite.class)</code> and <code>@SuiteClasses({TestClass1.class, ...})</code>. * When you run this class, it will run all the tests in all the suite classes. + * + * @since 4.0 */ public class Suite extends ParentRunner<Runner> { - /** - * Returns an empty suite. - */ - public static Runner emptySuite() { - try { - return new Suite((Class<?>)null, new Class<?>[0]); - } catch (InitializationError e) { - throw new RuntimeException("This shouldn't be possible"); - } - } - - /** - * The <code>SuiteClasses</code> annotation specifies the classes to be run when a class - * annotated with <code>@RunWith(Suite.class)</code> is run. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - @Inherited - public @interface SuiteClasses { - /** - * @return the classes to be run - */ - public Class<?>[] value(); - } - - private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError { - SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class); - if (annotation == null) - throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName())); - return annotation.value(); - } + /** + * Returns an empty suite. + */ + public static Runner emptySuite() { + try { + return new Suite((Class<?>) null, new Class<?>[0]); + } catch (InitializationError e) { + throw new RuntimeException("This shouldn't be possible"); + } + } - private final List<Runner> fRunners; + /** + * The <code>SuiteClasses</code> annotation specifies the classes to be run when a class + * annotated with <code>@RunWith(Suite.class)</code> is run. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Inherited + public @interface SuiteClasses { + /** + * @return the classes to be run + */ + public Class<?>[] value(); + } - /** - * Called reflectively on classes annotated with <code>@RunWith(Suite.class)</code> - * - * @param klass the root class - * @param builder builds runners for classes in the suite - * @throws InitializationError - */ - public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError { - this(builder, klass, getAnnotatedClasses(klass)); - } + private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError { + SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class); + if (annotation == null) { + throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName())); + } + return annotation.value(); + } - /** - * Call this when there is no single root class (for example, multiple class names - * passed on the command line to {@link org.junit.runner.JUnitCore} - * - * @param builder builds runners for classes in the suite - * @param classes the classes in the suite - * @throws InitializationError - */ - public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError { - this(null, builder.runners(null, classes)); - } - - /** - * Call this when the default builder is good enough. Left in for compatibility with JUnit 4.4. - * @param klass the root of the suite - * @param suiteClasses the classes in the suite - * @throws InitializationError - */ - protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { - this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses); - } - - /** - * Called by this class and subclasses once the classes making up the suite have been determined - * - * @param builder builds runners for classes in the suite - * @param klass the root of the suite - * @param suiteClasses the classes in the suite - * @throws InitializationError - */ - protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { - this(klass, builder.runners(klass, suiteClasses)); - } - - /** - * Called by this class and subclasses once the runners making up the suite have been determined - * - * @param klass root of the suite - * @param runners for each class in the suite, a {@link Runner} - * @throws InitializationError - */ - protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError { - super(klass); - fRunners = runners; - } - - @Override - protected List<Runner> getChildren() { - return fRunners; - } - - @Override - protected Description describeChild(Runner child) { - return child.getDescription(); - } + private final List<Runner> runners; - @Override - protected void runChild(Runner runner, final RunNotifier notifier) { - runner.run(notifier); - } + /** + * Called reflectively on classes annotated with <code>@RunWith(Suite.class)</code> + * + * @param klass the root class + * @param builder builds runners for classes in the suite + */ + public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError { + this(builder, klass, getAnnotatedClasses(klass)); + } + + /** + * Call this when there is no single root class (for example, multiple class names + * passed on the command line to {@link org.junit.runner.JUnitCore} + * + * @param builder builds runners for classes in the suite + * @param classes the classes in the suite + */ + public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError { + this(null, builder.runners(null, classes)); + } + + /** + * Call this when the default builder is good enough. Left in for compatibility with JUnit 4.4. + * + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + */ + protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses); + } + + /** + * Called by this class and subclasses once the classes making up the suite have been determined + * + * @param builder builds runners for classes in the suite + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + */ + protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(klass, builder.runners(klass, suiteClasses)); + } + + /** + * Called by this class and subclasses once the runners making up the suite have been determined + * + * @param klass root of the suite + * @param runners for each class in the suite, a {@link Runner} + */ + protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError { + super(klass); + this.runners = Collections.unmodifiableList(runners); + } + + @Override + protected List<Runner> getChildren() { + return runners; + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + protected void runChild(Runner runner, final RunNotifier notifier) { + runner.run(notifier); + } } diff --git a/src/main/java/org/junit/runners/model/Annotatable.java b/src/main/java/org/junit/runners/model/Annotatable.java new file mode 100644 index 0000000..8eff6fd --- /dev/null +++ b/src/main/java/org/junit/runners/model/Annotatable.java @@ -0,0 +1,20 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; + +/** + * A model element that may have annotations. + * + * @since 4.12 + */ +public interface Annotatable { + /** + * Returns the model elements' annotations. + */ + Annotation[] getAnnotations(); + + /** + * Returns the annotation on the model element of the given type, or @code{null} + */ + <T extends Annotation> T getAnnotation(Class<T> annotationType); +} diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java index 4a4d4a4..945e389 100644 --- a/src/main/java/org/junit/runners/model/FrameworkField.java +++ b/src/main/java/org/junit/runners/model/FrameworkField.java @@ -2,64 +2,79 @@ package org.junit.runners.model; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import org.junit.runners.BlockJUnit4ClassRunner; /** * Represents a field on a test class (currently used only for Rules in * {@link BlockJUnit4ClassRunner}, but custom runners can make other uses) + * + * @since 4.7 */ public class FrameworkField extends FrameworkMember<FrameworkField> { - private final Field fField; + private final Field field; - FrameworkField(Field field) { - fField= field; - } + FrameworkField(Field field) { + if (field == null) { + throw new NullPointerException( + "FrameworkField cannot be created without an underlying field."); + } + this.field = field; + } - public String getName() { - return getField().getName(); - } + @Override + public String getName() { + return getField().getName(); + } - @Override - public Annotation[] getAnnotations() { - return fField.getAnnotations(); - } + public Annotation[] getAnnotations() { + return field.getAnnotations(); + } - public boolean isPublic() { - int modifiers= fField.getModifiers(); - return Modifier.isPublic(modifiers); - } + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + return field.getAnnotation(annotationType); + } - @Override - public boolean isShadowedBy(FrameworkField otherMember) { - return otherMember.getName().equals(getName()); - } + @Override + public boolean isShadowedBy(FrameworkField otherMember) { + return otherMember.getName().equals(getName()); + } - public boolean isStatic() { - int modifiers= fField.getModifiers(); - return Modifier.isStatic(modifiers); - } + @Override + protected int getModifiers() { + return field.getModifiers(); + } - /** - * @return the underlying java Field - */ - public Field getField() { - return fField; - } + /** + * @return the underlying java Field + */ + public Field getField() { + return field; + } - /** - * @return the underlying Java Field type - * @see java.lang.reflect.Field#getType() - */ - public Class<?> getType() { - return fField.getType(); - } + /** + * @return the underlying Java Field type + * @see java.lang.reflect.Field#getType() + */ + @Override + public Class<?> getType() { + return field.getType(); + } + + @Override + public Class<?> getDeclaringClass() { + return field.getDeclaringClass(); + } - /** - * Attempts to retrieve the value of this field on {@code target} - */ - public Object get(Object target) throws IllegalArgumentException, IllegalAccessException { - return fField.get(target); - } + /** + * Attempts to retrieve the value of this field on {@code target} + */ + public Object get(Object target) throws IllegalArgumentException, IllegalAccessException { + return field.get(target); + } + + @Override + public String toString() { + return field.toString(); + } } diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java index 9cccd4b..724f096 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMember.java +++ b/src/main/java/org/junit/runners/model/FrameworkMember.java @@ -1,20 +1,45 @@ package org.junit.runners.model; -import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; import java.util.List; -abstract class FrameworkMember<T extends FrameworkMember<T>> { - /** - * Returns the annotations on this method - */ - abstract Annotation[] getAnnotations(); +/** + * Parent class for {@link FrameworkField} and {@link FrameworkMethod} + * + * @since 4.7 + */ +public abstract class FrameworkMember<T extends FrameworkMember<T>> implements + Annotatable { + abstract boolean isShadowedBy(T otherMember); - abstract boolean isShadowedBy(T otherMember); + boolean isShadowedBy(List<T> members) { + for (T each : members) { + if (isShadowedBy(each)) { + return true; + } + } + return false; + } - boolean isShadowedBy(List<T> members) { - for (T each : members) - if (isShadowedBy(each)) - return true; - return false; - } + protected abstract int getModifiers(); + + /** + * Returns true if this member is static, false if not. + */ + public boolean isStatic() { + return Modifier.isStatic(getModifiers()); + } + + /** + * Returns true if this member is public, false if not. + */ + public boolean isPublic() { + return Modifier.isPublic(getModifiers()); + } + + public abstract String getName(); + + public abstract Class<?> getType(); + + public abstract Class<?> getDeclaringClass(); } diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java index 81c8963..3580052 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMethod.java +++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java @@ -1,9 +1,8 @@ -package org.junit.runners.model; +package org.junit.runners.model; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.List; @@ -12,145 +11,192 @@ import org.junit.internal.runners.model.ReflectiveCallable; /** * Represents a method on a test class to be invoked at the appropriate point in * test execution. These methods are usually marked with an annotation (such as - * {@code @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass}, + * {@code @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass}, * {@code @AfterClass}, etc.) + * + * @since 4.5 */ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { - final Method fMethod; - - /** - * Returns a new {@code FrameworkMethod} for {@code method} - */ - public FrameworkMethod(Method method) { - fMethod= method; - } - - /** - * Returns the underlying Java method - */ - public Method getMethod() { - return fMethod; - } - - /** - * Returns the result of invoking this method on {@code target} with - * parameters {@code params}. {@link InvocationTargetException}s thrown are - * unwrapped, and their causes rethrown. - */ - public Object invokeExplosively(final Object target, final Object... params) - throws Throwable { - return new ReflectiveCallable() { - @Override - protected Object runReflectiveCall() throws Throwable { - return fMethod.invoke(target, params); - } - }.run(); - } - - /** - * Returns the method's name - */ - public String getName() { - return fMethod.getName(); - } - - /** - * Adds to {@code errors} if this method: - * <ul> - * <li>is not public, or - * <li>takes parameters, or - * <li>returns something other than void, or - * <li>is static (given {@code isStatic is false}), or - * <li>is not static (given {@code isStatic is true}). - */ - public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) { - validatePublicVoid(isStatic, errors); - if (fMethod.getParameterTypes().length != 0) - errors.add(new Exception("Method " + fMethod.getName() + " should have no parameters")); - } - - - /** - * Adds to {@code errors} if this method: - * <ul> - * <li>is not public, or - * <li>returns something other than void, or - * <li>is static (given {@code isStatic is false}), or - * <li>is not static (given {@code isStatic is true}). - */ - public void validatePublicVoid(boolean isStatic, List<Throwable> errors) { - if (Modifier.isStatic(fMethod.getModifiers()) != isStatic) { - String state= isStatic ? "should" : "should not"; - errors.add(new Exception("Method " + fMethod.getName() + "() " + state + " be static")); - } - if (!Modifier.isPublic(fMethod.getDeclaringClass().getModifiers())) - errors.add(new Exception("Class " + fMethod.getDeclaringClass().getName() + " should be public")); - if (!Modifier.isPublic(fMethod.getModifiers())) - errors.add(new Exception("Method " + fMethod.getName() + "() should be public")); - if (fMethod.getReturnType() != Void.TYPE) - errors.add(new Exception("Method " + fMethod.getName() + "() should be void")); - } - - public void validateNoTypeParametersOnArgs(List<Throwable> errors) { - new NoGenericTypeParametersValidator(fMethod).validate(errors); - } - - @Override - public boolean isShadowedBy(FrameworkMethod other) { - if (!other.getName().equals(getName())) - return false; - if (other.getParameterTypes().length != getParameterTypes().length) - return false; - for (int i= 0; i < other.getParameterTypes().length; i++) - if (!other.getParameterTypes()[i].equals(getParameterTypes()[i])) - return false; - return true; - } - - @Override - public boolean equals(Object obj) { - if (!FrameworkMethod.class.isInstance(obj)) - return false; - return ((FrameworkMethod) obj).fMethod.equals(fMethod); - } - - @Override - public int hashCode() { - return fMethod.hashCode(); - } - - /** - * Returns true iff this is a no-arg method that returns a value assignable - * to {@code type} - * - * @deprecated This is used only by the Theories runner, and does not - * use all the generic type info that it ought to. It will be replaced - * with a forthcoming ParameterSignature#canAcceptResultOf(FrameworkMethod) - * once Theories moves to junit-contrib. - */ - @Deprecated - public boolean producesType(Type type) { - return getParameterTypes().length == 0 && type instanceof Class<?> - && ((Class<?>) type).isAssignableFrom(fMethod.getReturnType()); - } - - private Class<?>[] getParameterTypes() { - return fMethod.getParameterTypes(); - } - - /** - * Returns the annotations on this method - */ - @Override - public Annotation[] getAnnotations() { - return fMethod.getAnnotations(); - } - - /** - * Returns the annotation of type {@code annotationType} on this method, if - * one exists. - */ - public <T extends Annotation> T getAnnotation(Class<T> annotationType) { - return fMethod.getAnnotation(annotationType); - } + private final Method method; + + /** + * Returns a new {@code FrameworkMethod} for {@code method} + */ + public FrameworkMethod(Method method) { + if (method == null) { + throw new NullPointerException( + "FrameworkMethod cannot be created without an underlying method."); + } + this.method = method; + } + + /** + * Returns the underlying Java method + */ + public Method getMethod() { + return method; + } + + /** + * Returns the result of invoking this method on {@code target} with + * parameters {@code params}. {@link InvocationTargetException}s thrown are + * unwrapped, and their causes rethrown. + */ + public Object invokeExplosively(final Object target, final Object... params) + throws Throwable { + return new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return method.invoke(target, params); + } + }.run(); + } + + /** + * Returns the method's name + */ + @Override + public String getName() { + return method.getName(); + } + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + * </ul> + */ + public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) { + validatePublicVoid(isStatic, errors); + if (method.getParameterTypes().length != 0) { + errors.add(new Exception("Method " + method.getName() + " should have no parameters")); + } + } + + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + * </ul> + */ + public void validatePublicVoid(boolean isStatic, List<Throwable> errors) { + if (isStatic() != isStatic) { + String state = isStatic ? "should" : "should not"; + errors.add(new Exception("Method " + method.getName() + "() " + state + " be static")); + } + if (!isPublic()) { + errors.add(new Exception("Method " + method.getName() + "() should be public")); + } + if (method.getReturnType() != Void.TYPE) { + errors.add(new Exception("Method " + method.getName() + "() should be void")); + } + } + + @Override + protected int getModifiers() { + return method.getModifiers(); + } + + /** + * Returns the return type of the method + */ + public Class<?> getReturnType() { + return method.getReturnType(); + } + + /** + * Returns the return type of the method + */ + @Override + public Class<?> getType() { + return getReturnType(); + } + + /** + * Returns the class where the method is actually declared + */ + @Override + public Class<?> getDeclaringClass() { + return method.getDeclaringClass(); + } + + public void validateNoTypeParametersOnArgs(List<Throwable> errors) { + new NoGenericTypeParametersValidator(method).validate(errors); + } + + @Override + public boolean isShadowedBy(FrameworkMethod other) { + if (!other.getName().equals(getName())) { + return false; + } + if (other.getParameterTypes().length != getParameterTypes().length) { + return false; + } + for (int i = 0; i < other.getParameterTypes().length; i++) { + if (!other.getParameterTypes()[i].equals(getParameterTypes()[i])) { + return false; + } + } + return true; + } + + @Override + public boolean equals(Object obj) { + if (!FrameworkMethod.class.isInstance(obj)) { + return false; + } + return ((FrameworkMethod) obj).method.equals(method); + } + + @Override + public int hashCode() { + return method.hashCode(); + } + + /** + * Returns true if this is a no-arg method that returns a value assignable + * to {@code type} + * + * @deprecated This is used only by the Theories runner, and does not + * use all the generic type info that it ought to. It will be replaced + * with a forthcoming ParameterSignature#canAcceptResultOf(FrameworkMethod) + * once Theories moves to junit-contrib. + */ + @Deprecated + public boolean producesType(Type type) { + return getParameterTypes().length == 0 && type instanceof Class<?> + && ((Class<?>) type).isAssignableFrom(method.getReturnType()); + } + + private Class<?>[] getParameterTypes() { + return method.getParameterTypes(); + } + + /** + * Returns the annotations on this method + */ + public Annotation[] getAnnotations() { + return method.getAnnotations(); + } + + /** + * Returns the annotation of type {@code annotationType} on this method, if + * one exists. + */ + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + return method.getAnnotation(annotationType); + } + + @Override + public String toString() { + return method.toString(); + } } diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java index 4de9ea7..841b565 100644 --- a/src/main/java/org/junit/runners/model/InitializationError.java +++ b/src/main/java/org/junit/runners/model/InitializationError.java @@ -5,35 +5,43 @@ import java.util.List; /** * Represents one or more problems encountered while initializing a Runner + * + * @since 4.5 */ public class InitializationError extends Exception { - private static final long serialVersionUID= 1L; - private final List<Throwable> fErrors; + private static final long serialVersionUID = 1L; - /** - * Construct a new {@code InitializationError} with one or more - * errors {@code errors} as causes - */ - public InitializationError(List<Throwable> errors) { - fErrors= errors; - } - - public InitializationError(Throwable error) { - this(Arrays.asList(error)); - } - - /** - * Construct a new {@code InitializationError} with one cause - * with message {@code string} - */ - public InitializationError(String string) { - this(new Exception(string)); - } + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final List<Throwable> fErrors; - /** - * Returns one or more Throwables that led to this initialization error. - */ - public List<Throwable> getCauses() { - return fErrors; - } + /** + * Construct a new {@code InitializationError} with one or more + * errors {@code errors} as causes + */ + public InitializationError(List<Throwable> errors) { + this.fErrors = errors; + } + + public InitializationError(Throwable error) { + this(Arrays.asList(error)); + } + + /** + * Construct a new {@code InitializationError} with one cause + * with message {@code string} + */ + public InitializationError(String string) { + this(new Exception(string)); + } + + /** + * Returns one or more Throwables that led to this initialization error. + */ + public List<Throwable> getCauses() { + return fErrors; + } } diff --git a/src/main/java/org/junit/runners/model/MultipleFailureException.java b/src/main/java/org/junit/runners/model/MultipleFailureException.java index 6d70ca0..325c645 100644 --- a/src/main/java/org/junit/runners/model/MultipleFailureException.java +++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java @@ -1,60 +1,69 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - package org.junit.runners.model; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.internal.Throwables; + /** * Collects multiple {@code Throwable}s into one exception. + * + * @since 4.9 */ public class MultipleFailureException extends Exception { - private static final long serialVersionUID= 1L; - - private final List<Throwable> fErrors; - - public MultipleFailureException(List<Throwable> errors) { - fErrors= new ArrayList<Throwable>(errors); - } - - public List<Throwable> getFailures() { - return Collections.unmodifiableList(fErrors); - } - - @Override - public String getMessage() { - StringBuilder sb = new StringBuilder( - String.format("There were %d errors:", fErrors.size())); - for (Throwable e : fErrors) { - sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); - } - return sb.toString(); - } - - /** - * Asserts that a list of throwables is empty. If it isn't empty, - * will throw {@link MultipleFailureException} (if there are - * multiple throwables in the list) or the first element in the list - * (if there is only one element). - * - * @param errors list to check - * @throws Throwable if the list is not empty - */ - @SuppressWarnings("deprecation") - public static void assertEmpty(List<Throwable> errors) throws Throwable { - if (errors.isEmpty()) - return; - if (errors.size() == 1) - throw errors.get(0); - - /* - * Many places in the code are documented to throw - * org.junit.internal.runners.model.MultipleFailureException. - * That class now extends this one, so we throw the internal - * exception in case developers have tests that catch - * MultipleFailureException. - */ - throw new org.junit.internal.runners.model.MultipleFailureException(errors); - } + private static final long serialVersionUID = 1L; + + /* + * We have to use the f prefix until the next major release to ensure + * serialization compatibility. + * See https://github.com/junit-team/junit/issues/976 + */ + private final List<Throwable> fErrors; + + public MultipleFailureException(List<Throwable> errors) { + this.fErrors = new ArrayList<Throwable>(errors); + } + + public List<Throwable> getFailures() { + return Collections.unmodifiableList(fErrors); + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder( + String.format("There were %d errors:", fErrors.size())); + for (Throwable e : fErrors) { + sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); + } + return sb.toString(); + } + + /** + * Asserts that a list of throwables is empty. If it isn't empty, + * will throw {@link MultipleFailureException} (if there are + * multiple throwables in the list) or the first element in the list + * (if there is only one element). + * + * @param errors list to check + * @throws Exception or Error if the list is not empty + */ + @SuppressWarnings("deprecation") + public static void assertEmpty(List<Throwable> errors) throws Exception { + if (errors.isEmpty()) { + return; + } + if (errors.size() == 1) { + throw Throwables.rethrowAsException(errors.get(0)); + } + + /* + * Many places in the code are documented to throw + * org.junit.internal.runners.model.MultipleFailureException. + * That class now extends this one, so we throw the internal + * exception in case developers have tests that catch + * MultipleFailureException. + */ + throw new org.junit.internal.runners.model.MultipleFailureException(errors); + } } diff --git a/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java b/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java index 77662b8..386b7ff 100644 --- a/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java +++ b/src/main/java/org/junit/runners/model/NoGenericTypeParametersValidator.java @@ -9,45 +9,50 @@ import java.lang.reflect.WildcardType; import java.util.List; class NoGenericTypeParametersValidator { - private final Method fMethod; - - NoGenericTypeParametersValidator(Method method) { - this.fMethod = method; - } - - void validate(List<Throwable> errors) { - for (Type each : fMethod.getGenericParameterTypes()) - validateNoTypeParameterOnType(each, errors); - } - - private void validateNoTypeParameterOnType(Type type, List<Throwable> errors) { - if (type instanceof TypeVariable<?>) { - errors.add(new Exception("Method " + fMethod.getName() - + "() contains unresolved type variable " + type)); - } else if (type instanceof ParameterizedType) - validateNoTypeParameterOnParameterizedType((ParameterizedType) type, errors); - else if (type instanceof WildcardType) - validateNoTypeParameterOnWildcardType((WildcardType) type, errors); - else if (type instanceof GenericArrayType) - validateNoTypeParameterOnGenericArrayType((GenericArrayType) type, errors); - } - - private void validateNoTypeParameterOnParameterizedType(ParameterizedType parameterized, - List<Throwable> errors) { - for (Type each : parameterized.getActualTypeArguments()) - validateNoTypeParameterOnType(each, errors); - } - - private void validateNoTypeParameterOnWildcardType(WildcardType wildcard, - List<Throwable> errors) { - for (Type each : wildcard.getUpperBounds()) - validateNoTypeParameterOnType(each, errors); - for (Type each : wildcard.getLowerBounds()) - validateNoTypeParameterOnType(each, errors); - } - - private void validateNoTypeParameterOnGenericArrayType( - GenericArrayType arrayType, List<Throwable> errors) { - validateNoTypeParameterOnType(arrayType.getGenericComponentType(), errors); - } + private final Method method; + + NoGenericTypeParametersValidator(Method method) { + this.method = method; + } + + void validate(List<Throwable> errors) { + for (Type each : method.getGenericParameterTypes()) { + validateNoTypeParameterOnType(each, errors); + } + } + + private void validateNoTypeParameterOnType(Type type, List<Throwable> errors) { + if (type instanceof TypeVariable<?>) { + errors.add(new Exception("Method " + method.getName() + + "() contains unresolved type variable " + type)); + } else if (type instanceof ParameterizedType) { + validateNoTypeParameterOnParameterizedType((ParameterizedType) type, errors); + } else if (type instanceof WildcardType) { + validateNoTypeParameterOnWildcardType((WildcardType) type, errors); + } else if (type instanceof GenericArrayType) { + validateNoTypeParameterOnGenericArrayType((GenericArrayType) type, errors); + } + } + + private void validateNoTypeParameterOnParameterizedType(ParameterizedType parameterized, + List<Throwable> errors) { + for (Type each : parameterized.getActualTypeArguments()) { + validateNoTypeParameterOnType(each, errors); + } + } + + private void validateNoTypeParameterOnWildcardType(WildcardType wildcard, + List<Throwable> errors) { + for (Type each : wildcard.getUpperBounds()) { + validateNoTypeParameterOnType(each, errors); + } + for (Type each : wildcard.getLowerBounds()) { + validateNoTypeParameterOnType(each, errors); + } + } + + private void validateNoTypeParameterOnGenericArrayType( + GenericArrayType arrayType, List<Throwable> errors) { + validateNoTypeParameterOnType(arrayType.getGenericComponentType(), errors); + } }
\ No newline at end of file diff --git a/src/main/java/org/junit/runners/model/RunnerBuilder.java b/src/main/java/org/junit/runners/model/RunnerBuilder.java index 3a334be..7d3eee3 100644 --- a/src/main/java/org/junit/runners/model/RunnerBuilder.java +++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java @@ -9,21 +9,21 @@ import org.junit.internal.runners.ErrorReportingRunner; import org.junit.runner.Runner; /** - * A RunnerBuilder is a strategy for constructing runners for classes. - * + * A RunnerBuilder is a strategy for constructing runners for classes. + * * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking - * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself. + * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself. * For example, * imagine a custom runner that builds suites based on a list of classes in a text file: - * + * * <pre> * \@RunWith(TextFileSuite.class) * \@SuiteSpecFile("mysuite.txt") * class MySuite {} * </pre> - * + * * The implementation of TextFileSuite might include: - * + * * <pre> * public TextFileSuite(Class testClass, RunnerBuilder builder) { * // ... @@ -32,73 +32,77 @@ import org.junit.runner.Runner; * // ... * } * </pre> - * + * * @see org.junit.runners.Suite + * @since 4.5 */ public abstract class RunnerBuilder { - private final Set<Class<?>> parents= new HashSet<Class<?>>(); + private final Set<Class<?>> parents = new HashSet<Class<?>>(); - /** - * Override to calculate the correct runner for a test class at runtime. - * - * @param testClass class to be run - * @return a Runner - * @throws Throwable if a runner cannot be constructed - */ - public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; + /** + * Override to calculate the correct runner for a test class at runtime. + * + * @param testClass class to be run + * @return a Runner + * @throws Throwable if a runner cannot be constructed + */ + public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; - /** - * Always returns a runner, even if it is just one that prints an error instead of running tests. - * @param testClass class to be run - * @return a Runner - */ - public Runner safeRunnerForClass(Class<?> testClass) { - try { - return runnerForClass(testClass); - } catch (Throwable e) { - return new ErrorReportingRunner(testClass, e); - } - } + /** + * Always returns a runner, even if it is just one that prints an error instead of running tests. + * + * @param testClass class to be run + * @return a Runner + */ + public Runner safeRunnerForClass(Class<?> testClass) { + try { + return runnerForClass(testClass); + } catch (Throwable e) { + return new ErrorReportingRunner(testClass, e); + } + } - Class<?> addParent(Class<?> parent) throws InitializationError { - if (!parents.add(parent)) - throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); - return parent; - } + Class<?> addParent(Class<?> parent) throws InitializationError { + if (!parents.add(parent)) { + throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); + } + return parent; + } - void removeParent(Class<?> klass) { - parents.remove(klass); - } + void removeParent(Class<?> klass) { + parents.remove(klass); + } - /** - * Constructs and returns a list of Runners, one for each child class in - * {@code children}. Care is taken to avoid infinite recursion: - * this builder will throw an exception if it is requested for another - * runner for {@code parent} before this call completes. - */ - public List<Runner> runners(Class<?> parent, Class<?>[] children) - throws InitializationError { - addParent(parent); + /** + * Constructs and returns a list of Runners, one for each child class in + * {@code children}. Care is taken to avoid infinite recursion: + * this builder will throw an exception if it is requested for another + * runner for {@code parent} before this call completes. + */ + public List<Runner> runners(Class<?> parent, Class<?>[] children) + throws InitializationError { + addParent(parent); - try { - return runners(children); - } finally { - removeParent(parent); - } - } - - public List<Runner> runners(Class<?> parent, List<Class<?>> children) - throws InitializationError { - return runners(parent, children.toArray(new Class<?>[0])); - } - - private List<Runner> runners(Class<?>[] children) { - ArrayList<Runner> runners= new ArrayList<Runner>(); - for (Class<?> each : children) { - Runner childRunner= safeRunnerForClass(each); - if (childRunner != null) - runners.add(childRunner); - } - return runners; - } + try { + return runners(children); + } finally { + removeParent(parent); + } + } + + public List<Runner> runners(Class<?> parent, List<Class<?>> children) + throws InitializationError { + return runners(parent, children.toArray(new Class<?>[0])); + } + + private List<Runner> runners(Class<?>[] children) { + ArrayList<Runner> runners = new ArrayList<Runner>(); + for (Class<?> each : children) { + Runner childRunner = safeRunnerForClass(each); + if (childRunner != null) { + runners.add(childRunner); + } + } + return runners; + } } diff --git a/src/main/java/org/junit/runners/model/RunnerScheduler.java b/src/main/java/org/junit/runners/model/RunnerScheduler.java index fbc25a4..db43308 100644 --- a/src/main/java/org/junit/runners/model/RunnerScheduler.java +++ b/src/main/java/org/junit/runners/model/RunnerScheduler.java @@ -3,19 +3,21 @@ package org.junit.runners.model; /** * Represents a strategy for scheduling when individual test methods * should be run (in serial or parallel) - * + * * WARNING: still experimental, may go away. + * + * @since 4.7 */ public interface RunnerScheduler { - /** - * Schedule a child statement to run - */ - void schedule(Runnable childStatement); - - /** - * Override to implement any behavior that must occur - * after all children have been scheduled (for example, - * waiting for them all to finish) - */ - void finished(); + /** + * Schedule a child statement to run + */ + void schedule(Runnable childStatement); + + /** + * Override to implement any behavior that must occur + * after all children have been scheduled (for example, + * waiting for them all to finish) + */ + void finished(); } diff --git a/src/main/java/org/junit/runners/model/Statement.java b/src/main/java/org/junit/runners/model/Statement.java index a7c5478..fa53fa1 100644 --- a/src/main/java/org/junit/runners/model/Statement.java +++ b/src/main/java/org/junit/runners/model/Statement.java @@ -1,16 +1,15 @@ -/** - * - */ package org.junit.runners.model; /** * Represents one or more actions to be taken at runtime in the course * of running a JUnit test suite. + * + * @since 4.5 */ public abstract class Statement { - /** - * Run the action, throwing a {@code Throwable} if anything goes wrong. - */ - public abstract void evaluate() throws Throwable; + /** + * Run the action, throwing a {@code Throwable} if anything goes wrong. + */ + public abstract void evaluate() throws Throwable; }
\ No newline at end of file diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java index 362a13a..c8a544d 100644..100755 --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -1,159 +1,313 @@ package org.junit.runners.model; import static java.lang.reflect.Modifier.isStatic; +import static org.junit.internal.MethodSorter.NAME_ASCENDING; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.internal.MethodSorter; /** * Wraps a class to be run, providing method validation and annotation searching + * + * @since 4.5 */ -public class TestClass { - private final Class<?> fClass; - - private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<Class<?>, List<FrameworkMethod>>(); - - private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<Class<?>, List<FrameworkField>>(); - - /** - * Creates a {@code TestClass} wrapping {@code klass}. Each time this - * constructor executes, the class is scanned for annotations, which can be - * an expensive process (we hope in future JDK's it will not be.) Therefore, - * try to share instances of {@code TestClass} where possible. - */ - public TestClass(Class<?> klass) { - fClass= klass; - if (klass != null && klass.getConstructors().length > 1) - throw new IllegalArgumentException( - "Test class can only have one constructor"); - - for (Class<?> eachClass : getSuperClasses(fClass)) { - for (Method eachMethod : eachClass.getDeclaredMethods()) - addToAnnotationLists(new FrameworkMethod(eachMethod), - fMethodsForAnnotations); - for (Field eachField : eachClass.getDeclaredFields()) - addToAnnotationLists(new FrameworkField(eachField), - fFieldsForAnnotations); - } - } - - private <T extends FrameworkMember<T>> void addToAnnotationLists(T member, - Map<Class<?>, List<T>> map) { - for (Annotation each : member.getAnnotations()) { - Class<? extends Annotation> type= each.annotationType(); - List<T> members= getAnnotatedMembers(map, type); - if (member.isShadowedBy(members)) - return; - if (runsTopToBottom(type)) - members.add(0, member); - else - members.add(member); - } - } - - /** - * Returns, efficiently, all the non-overridden methods in this class and - * its superclasses that are annotated with {@code annotationClass}. - */ - public List<FrameworkMethod> getAnnotatedMethods( - Class<? extends Annotation> annotationClass) { - return getAnnotatedMembers(fMethodsForAnnotations, annotationClass); - } - - /** - * Returns, efficiently, all the non-overridden fields in this class and its - * superclasses that are annotated with {@code annotationClass}. - */ - public List<FrameworkField> getAnnotatedFields( - Class<? extends Annotation> annotationClass) { - return getAnnotatedMembers(fFieldsForAnnotations, annotationClass); - } - - private <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map, - Class<? extends Annotation> type) { - if (!map.containsKey(type)) - map.put(type, new ArrayList<T>()); - return map.get(type); - } - - private boolean runsTopToBottom(Class<? extends Annotation> annotation) { - return annotation.equals(Before.class) - || annotation.equals(BeforeClass.class); - } - - private List<Class<?>> getSuperClasses(Class<?> testClass) { - ArrayList<Class<?>> results= new ArrayList<Class<?>>(); - Class<?> current= testClass; - while (current != null) { - results.add(current); - current= current.getSuperclass(); - } - return results; - } - - /** - * Returns the underlying Java class. - */ - public Class<?> getJavaClass() { - return fClass; - } - - /** - * Returns the class's name. - */ - public String getName() { - if (fClass == null) - return "null"; - return fClass.getName(); - } - - /** - * Returns the only public constructor in the class, or throws an {@code - * AssertionError} if there are more or less than one. - */ - - public Constructor<?> getOnlyConstructor() { - Constructor<?>[] constructors= fClass.getConstructors(); - Assert.assertEquals(1, constructors.length); - return constructors[0]; - } - - /** - * Returns the annotations on this class - */ - public Annotation[] getAnnotations() { - if (fClass == null) - return new Annotation[0]; - return fClass.getAnnotations(); - } - - public <T> List<T> getAnnotatedFieldValues(Object test, - Class<? extends Annotation> annotationClass, Class<T> valueClass) { - List<T> results= new ArrayList<T>(); - for (FrameworkField each : getAnnotatedFields(annotationClass)) { - try { - Object fieldValue= each.get(test); - if (valueClass.isInstance(fieldValue)) - results.add(valueClass.cast(fieldValue)); - } catch (IllegalAccessException e) { - throw new RuntimeException( - "How did getFields return a field we couldn't access?", e); - } - } - return results; - } - - public boolean isANonStaticInnerClass() { - return fClass.isMemberClass() && !isStatic(fClass.getModifiers()); - } +public class TestClass implements Annotatable { + private static final FieldComparator FIELD_COMPARATOR = new FieldComparator(); + private static final MethodComparator METHOD_COMPARATOR = new MethodComparator(); + + private final Class<?> clazz; + private final Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations; + private final Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations; + + /** + * Creates a {@code TestClass} wrapping {@code clazz}. Each time this + * constructor executes, the class is scanned for annotations, which can be + * an expensive process (we hope in future JDK's it will not be.) Therefore, + * try to share instances of {@code TestClass} where possible. + */ + public TestClass(Class<?> clazz) { + this.clazz = clazz; + if (clazz != null && clazz.getConstructors().length > 1) { + throw new IllegalArgumentException( + "Test class can only have one constructor"); + } + + Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations = + new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>(); + Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations = + new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>(); + + scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations); + + this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations); + this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations); + } + + protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) { + for (Class<?> eachClass : getSuperClasses(clazz)) { + for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) { + addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations); + } + // ensuring fields are sorted to make sure that entries are inserted + // and read from fieldForAnnotations in a deterministic order + for (Field eachField : getSortedDeclaredFields(eachClass)) { + addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations); + } + } + } + + private static Field[] getSortedDeclaredFields(Class<?> clazz) { + Field[] declaredFields = clazz.getDeclaredFields(); + Arrays.sort(declaredFields, FIELD_COMPARATOR); + return declaredFields; + } + + protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member, + Map<Class<? extends Annotation>, List<T>> map) { + for (Annotation each : member.getAnnotations()) { + Class<? extends Annotation> type = each.annotationType(); + List<T> members = getAnnotatedMembers(map, type, true); + if (member.isShadowedBy(members)) { + return; + } + if (runsTopToBottom(type)) { + members.add(0, member); + } else { + members.add(member); + } + } + } + + private static <T extends FrameworkMember<T>> Map<Class<? extends Annotation>, List<T>> + makeDeeplyUnmodifiable(Map<Class<? extends Annotation>, List<T>> source) { + LinkedHashMap<Class<? extends Annotation>, List<T>> copy = + new LinkedHashMap<Class<? extends Annotation>, List<T>>(); + for (Map.Entry<Class<? extends Annotation>, List<T>> entry : source.entrySet()) { + copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); + } + return Collections.unmodifiableMap(copy); + } + + /** + * Returns, efficiently, all the non-overridden methods in this class and + * its superclasses that are annotated}. + * + * @since 4.12 + */ + public List<FrameworkMethod> getAnnotatedMethods() { + List<FrameworkMethod> methods = collectValues(methodsForAnnotations); + Collections.sort(methods, METHOD_COMPARATOR); + return methods; + } + + /** + * Returns, efficiently, all the non-overridden methods in this class and + * its superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkMethod> getAnnotatedMethods( + Class<? extends Annotation> annotationClass) { + return Collections.unmodifiableList(getAnnotatedMembers(methodsForAnnotations, annotationClass, false)); + } + + /** + * Returns, efficiently, all the non-overridden fields in this class and its + * superclasses that are annotated. + * + * @since 4.12 + */ + public List<FrameworkField> getAnnotatedFields() { + return collectValues(fieldsForAnnotations); + } + + /** + * Returns, efficiently, all the non-overridden fields in this class and its + * superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkField> getAnnotatedFields( + Class<? extends Annotation> annotationClass) { + return Collections.unmodifiableList(getAnnotatedMembers(fieldsForAnnotations, annotationClass, false)); + } + + private <T> List<T> collectValues(Map<?, List<T>> map) { + Set<T> values = new LinkedHashSet<T>(); + for (List<T> additionalValues : map.values()) { + values.addAll(additionalValues); + } + return new ArrayList<T>(values); + } + + private static <T> List<T> getAnnotatedMembers(Map<Class<? extends Annotation>, List<T>> map, + Class<? extends Annotation> type, boolean fillIfAbsent) { + if (!map.containsKey(type) && fillIfAbsent) { + map.put(type, new ArrayList<T>()); + } + List<T> members = map.get(type); + return members == null ? Collections.<T>emptyList() : members; + } + + private static boolean runsTopToBottom(Class<? extends Annotation> annotation) { + return annotation.equals(Before.class) + || annotation.equals(BeforeClass.class); + } + + private static List<Class<?>> getSuperClasses(Class<?> testClass) { + ArrayList<Class<?>> results = new ArrayList<Class<?>>(); + Class<?> current = testClass; + while (current != null) { + results.add(current); + current = current.getSuperclass(); + } + return results; + } + + /** + * Returns the underlying Java class. + */ + public Class<?> getJavaClass() { + return clazz; + } + + /** + * Returns the class's name. + */ + public String getName() { + if (clazz == null) { + return "null"; + } + return clazz.getName(); + } + + /** + * Returns the only public constructor in the class, or throws an {@code + * AssertionError} if there are more or less than one. + */ + + public Constructor<?> getOnlyConstructor() { + Constructor<?>[] constructors = clazz.getConstructors(); + Assert.assertEquals(1, constructors.length); + return constructors[0]; + } + + /** + * Returns the annotations on this class + */ + public Annotation[] getAnnotations() { + if (clazz == null) { + return new Annotation[0]; + } + return clazz.getAnnotations(); + } + + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + if (clazz == null) { + return null; + } + return clazz.getAnnotation(annotationType); + } + + public <T> List<T> getAnnotatedFieldValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass) { + List<T> results = new ArrayList<T>(); + for (FrameworkField each : getAnnotatedFields(annotationClass)) { + try { + Object fieldValue = each.get(test); + if (valueClass.isInstance(fieldValue)) { + results.add(valueClass.cast(fieldValue)); + } + } catch (IllegalAccessException e) { + throw new RuntimeException( + "How did getFields return a field we couldn't access?", e); + } + } + return results; + } + + public <T> List<T> getAnnotatedMethodValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass) { + List<T> results = new ArrayList<T>(); + for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) { + try { + /* + * A method annotated with @Rule may return a @TestRule or a @MethodRule, + * we cannot call the method to check whether the return type matches our + * expectation i.e. subclass of valueClass. If we do that then the method + * will be invoked twice and we do not want to do that. So we first check + * whether return type matches our expectation and only then call the method + * to fetch the MethodRule + */ + if (valueClass.isAssignableFrom(each.getReturnType())) { + Object fieldValue = each.invokeExplosively(test); + results.add(valueClass.cast(fieldValue)); + } + } catch (Throwable e) { + throw new RuntimeException( + "Exception in " + each.getName(), e); + } + } + return results; + } + + public boolean isPublic() { + return Modifier.isPublic(clazz.getModifiers()); + } + + public boolean isANonStaticInnerClass() { + return clazz.isMemberClass() && !isStatic(clazz.getModifiers()); + } + + @Override + public int hashCode() { + return (clazz == null) ? 0 : clazz.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TestClass other = (TestClass) obj; + return clazz == other.clazz; + } + + /** + * Compares two fields by its name. + */ + private static class FieldComparator implements Comparator<Field> { + public int compare(Field left, Field right) { + return left.getName().compareTo(right.getName()); + } + } + + /** + * Compares two methods by its name. + */ + private static class MethodComparator implements + Comparator<FrameworkMethod> { + public int compare(FrameworkMethod left, FrameworkMethod right) { + return NAME_ASCENDING.compare(left.getMethod(), right.getMethod()); + } + } } diff --git a/src/main/java/org/junit/runners/model/TestTimedOutException.java b/src/main/java/org/junit/runners/model/TestTimedOutException.java new file mode 100644 index 0000000..60e1a8a --- /dev/null +++ b/src/main/java/org/junit/runners/model/TestTimedOutException.java @@ -0,0 +1,44 @@ +package org.junit.runners.model; + +import java.util.concurrent.TimeUnit; + +/** + * Exception thrown when a test fails on timeout. + * + * @since 4.12 + * + */ +public class TestTimedOutException extends Exception { + + private static final long serialVersionUID = 31935685163547539L; + + private final TimeUnit timeUnit; + private final long timeout; + + /** + * Creates exception with a standard message "test timed out after [timeout] [timeUnit]" + * + * @param timeout the amount of time passed before the test was interrupted + * @param timeUnit the time unit for the timeout value + */ + public TestTimedOutException(long timeout, TimeUnit timeUnit) { + super(String.format("test timed out after %d %s", + timeout, timeUnit.name().toLowerCase())); + this.timeUnit = timeUnit; + this.timeout = timeout; + } + + /** + * Gets the time passed before the test was interrupted + */ + public long getTimeout() { + return timeout; + } + + /** + * Gets the time unit for the timeout value + */ + public TimeUnit getTimeUnit() { + return timeUnit; + } +} diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java new file mode 100644 index 0000000..1c49f84 --- /dev/null +++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParameters.java @@ -0,0 +1,143 @@ +package org.junit.runners.parameterized; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.List; + +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +/** + * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be + * injected via constructor or into annotated fields. + */ +public class BlockJUnit4ClassRunnerWithParameters extends + BlockJUnit4ClassRunner { + private final Object[] parameters; + + private final String name; + + public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) + throws InitializationError { + super(test.getTestClass().getJavaClass()); + parameters = test.getParameters().toArray( + new Object[test.getParameters().size()]); + name = test.getName(); + } + + @Override + public Object createTest() throws Exception { + if (fieldsAreAnnotated()) { + return createTestUsingFieldInjection(); + } else { + return createTestUsingConstructorInjection(); + } + } + + private Object createTestUsingConstructorInjection() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(parameters); + } + + private Object createTestUsingFieldInjection() throws Exception { + List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); + if (annotatedFieldsByParameter.size() != parameters.length) { + throw new Exception( + "Wrong number of parameters and @Parameter fields." + + " @Parameter fields counted: " + + annotatedFieldsByParameter.size() + + ", available parameters: " + parameters.length + + "."); + } + Object testClassInstance = getTestClass().getJavaClass().newInstance(); + for (FrameworkField each : annotatedFieldsByParameter) { + Field field = each.getField(); + Parameter annotation = field.getAnnotation(Parameter.class); + int index = annotation.value(); + try { + field.set(testClassInstance, parameters[index]); + } catch (IllegalArgumentException iare) { + throw new Exception(getTestClass().getName() + + ": Trying to set " + field.getName() + + " with the value " + parameters[index] + + " that is not the right type (" + + parameters[index].getClass().getSimpleName() + + " instead of " + field.getType().getSimpleName() + + ").", iare); + } + } + return testClassInstance; + } + + @Override + protected String getName() { + return name; + } + + @Override + protected String testName(FrameworkMethod method) { + return method.getName() + getName(); + } + + @Override + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + if (fieldsAreAnnotated()) { + validateZeroArgConstructor(errors); + } + } + + @Override + protected void validateFields(List<Throwable> errors) { + super.validateFields(errors); + if (fieldsAreAnnotated()) { + List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); + int[] usedIndices = new int[annotatedFieldsByParameter.size()]; + for (FrameworkField each : annotatedFieldsByParameter) { + int index = each.getField().getAnnotation(Parameter.class) + .value(); + if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { + errors.add(new Exception("Invalid @Parameter value: " + + index + ". @Parameter fields counted: " + + annotatedFieldsByParameter.size() + + ". Please use an index between 0 and " + + (annotatedFieldsByParameter.size() - 1) + ".")); + } else { + usedIndices[index]++; + } + } + for (int index = 0; index < usedIndices.length; index++) { + int numberOfUse = usedIndices[index]; + if (numberOfUse == 0) { + errors.add(new Exception("@Parameter(" + index + + ") is never used.")); + } else if (numberOfUse > 1) { + errors.add(new Exception("@Parameter(" + index + + ") is used more than once (" + numberOfUse + ").")); + } + } + } + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } + + @Override + protected Annotation[] getRunnerAnnotations() { + return new Annotation[0]; + } + + private List<FrameworkField> getAnnotatedFieldsByParameter() { + return getTestClass().getAnnotatedFields(Parameter.class); + } + + private boolean fieldsAreAnnotated() { + return !getAnnotatedFieldsByParameter().isEmpty(); + } +} diff --git a/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java new file mode 100644 index 0000000..ae49ef4 --- /dev/null +++ b/src/main/java/org/junit/runners/parameterized/BlockJUnit4ClassRunnerWithParametersFactory.java @@ -0,0 +1,18 @@ +package org.junit.runners.parameterized; + +import org.junit.runner.Runner; +import org.junit.runners.model.InitializationError; + +/** + * A {@link ParametersRunnerFactory} that creates + * {@link BlockJUnit4ClassRunnerWithParameters}. + * + * @since 4.12 + */ +public class BlockJUnit4ClassRunnerWithParametersFactory implements + ParametersRunnerFactory { + public Runner createRunnerForTestWithParameters(TestWithParameters test) + throws InitializationError { + return new BlockJUnit4ClassRunnerWithParameters(test); + } +} diff --git a/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java new file mode 100644 index 0000000..16ea1f3 --- /dev/null +++ b/src/main/java/org/junit/runners/parameterized/ParametersRunnerFactory.java @@ -0,0 +1,21 @@ +package org.junit.runners.parameterized; + +import org.junit.runner.Runner; +import org.junit.runners.model.InitializationError; + +/** + * A {@code ParameterizedRunnerFactory} creates a runner for a single + * {@link TestWithParameters}. + * + * @since 4.12 + */ +public interface ParametersRunnerFactory { + /** + * Returns a runner for the specified {@link TestWithParameters}. + * + * @throws InitializationError + * if the runner could not be created. + */ + Runner createRunnerForTestWithParameters(TestWithParameters test) + throws InitializationError; +} diff --git a/src/main/java/org/junit/runners/parameterized/TestWithParameters.java b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java new file mode 100644 index 0000000..1b86644 --- /dev/null +++ b/src/main/java/org/junit/runners/parameterized/TestWithParameters.java @@ -0,0 +1,82 @@ +package org.junit.runners.parameterized; + +import static java.util.Collections.unmodifiableList; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.runners.model.TestClass; + +/** + * A {@code TestWithParameters} keeps the data together that are needed for + * creating a runner for a single data set of a parameterized test. It has a + * name, the test class and a list of parameters. + * + * @since 4.12 + */ +public class TestWithParameters { + private final String name; + + private final TestClass testClass; + + private final List<Object> parameters; + + public TestWithParameters(String name, TestClass testClass, + List<Object> parameters) { + notNull(name, "The name is missing."); + notNull(testClass, "The test class is missing."); + notNull(parameters, "The parameters are missing."); + this.name = name; + this.testClass = testClass; + this.parameters = unmodifiableList(new ArrayList<Object>(parameters)); + } + + public String getName() { + return name; + } + + public TestClass getTestClass() { + return testClass; + } + + public List<Object> getParameters() { + return parameters; + } + + @Override + public int hashCode() { + int prime = 14747; + int result = prime + name.hashCode(); + result = prime * result + testClass.hashCode(); + return prime * result + parameters.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TestWithParameters other = (TestWithParameters) obj; + return name.equals(other.name) + && parameters.equals(other.parameters) + && testClass.equals(other.testClass); + } + + @Override + public String toString() { + return testClass.getName() + " '" + name + "' with parameters " + + parameters; + } + + private static void notNull(Object value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + } +} diff --git a/src/main/java/org/junit/validator/AnnotationValidator.java b/src/main/java/org/junit/validator/AnnotationValidator.java new file mode 100644 index 0000000..8a53adf --- /dev/null +++ b/src/main/java/org/junit/validator/AnnotationValidator.java @@ -0,0 +1,60 @@ +package org.junit.validator; + +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +import static java.util.Collections.emptyList; + +import java.util.List; + +/** + * Validates annotations on classes and methods. To be validated, + * an annotation should be annotated with {@link ValidateWith} + * + * Instances of this class are shared by multiple test runners, so they should + * be immutable and thread-safe. + * + * @since 4.12 + */ +public abstract class AnnotationValidator { + + private static final List<Exception> NO_VALIDATION_ERRORS = emptyList(); + + /** + * Validates annotation on the given class. + * + * @param testClass that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List<Exception> validateAnnotatedClass(TestClass testClass) { + return NO_VALIDATION_ERRORS; + } + + /** + * Validates annotation on the given field. + * + * @param field that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List<Exception> validateAnnotatedField(FrameworkField field) { + return NO_VALIDATION_ERRORS; + + } + + /** + * Validates annotation on the given method. + * + * @param method that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List<Exception> validateAnnotatedMethod(FrameworkMethod method) { + return NO_VALIDATION_ERRORS; + } +} diff --git a/src/main/java/org/junit/validator/AnnotationValidatorFactory.java b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java new file mode 100644 index 0000000..7309fdd --- /dev/null +++ b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java @@ -0,0 +1,42 @@ +package org.junit.validator; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Creates instances of Annotation Validators. + * + * @since 4.12 + */ +public class AnnotationValidatorFactory { + private static final ConcurrentHashMap<ValidateWith, AnnotationValidator> VALIDATORS_FOR_ANNOTATION_TYPES = + new ConcurrentHashMap<ValidateWith, AnnotationValidator>(); + + /** + * Creates the AnnotationValidator specified by the value in + * {@link org.junit.validator.ValidateWith}. Instances are + * cached. + * + * @return An instance of the AnnotationValidator. + * + * @since 4.12 + */ + public AnnotationValidator createAnnotationValidator(ValidateWith validateWithAnnotation) { + AnnotationValidator validator = VALIDATORS_FOR_ANNOTATION_TYPES.get(validateWithAnnotation); + if (validator != null) { + return validator; + } + + Class<? extends AnnotationValidator> clazz = validateWithAnnotation.value(); + if (clazz == null) { + throw new IllegalArgumentException("Can't create validator, value is null in annotation " + validateWithAnnotation.getClass().getName()); + } + try { + AnnotationValidator annotationValidator = clazz.newInstance(); + VALIDATORS_FOR_ANNOTATION_TYPES.putIfAbsent(validateWithAnnotation, annotationValidator); + return VALIDATORS_FOR_ANNOTATION_TYPES.get(validateWithAnnotation); + } catch (Exception e) { + throw new RuntimeException("Exception received when creating AnnotationValidator class " + clazz.getName(), e); + } + } + +} diff --git a/src/main/java/org/junit/validator/AnnotationsValidator.java b/src/main/java/org/junit/validator/AnnotationsValidator.java new file mode 100644 index 0000000..30f54a6 --- /dev/null +++ b/src/main/java/org/junit/validator/AnnotationsValidator.java @@ -0,0 +1,120 @@ +package org.junit.validator; + +import static java.util.Collections.singletonList; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.runners.model.Annotatable; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +/** + * An {@code AnnotationsValidator} validates all annotations of a test class, + * including its annotated fields and methods. + * + * @since 4.12 + */ +public final class AnnotationsValidator implements TestClassValidator { + private static final List<AnnotatableValidator<?>> VALIDATORS = Arrays.<AnnotatableValidator<?>>asList( + new ClassValidator(), new MethodValidator(), new FieldValidator()); + + /** + * Validate all annotations of the specified test class that are be + * annotated with {@link ValidateWith}. + * + * @param testClass + * the {@link TestClass} that is validated. + * @return the errors found by the validator. + */ + public List<Exception> validateTestClass(TestClass testClass) { + List<Exception> validationErrors= new ArrayList<Exception>(); + for (AnnotatableValidator<?> validator : VALIDATORS) { + List<Exception> additionalErrors= validator + .validateTestClass(testClass); + validationErrors.addAll(additionalErrors); + } + return validationErrors; + } + + private static abstract class AnnotatableValidator<T extends Annotatable> { + private static final AnnotationValidatorFactory ANNOTATION_VALIDATOR_FACTORY = new AnnotationValidatorFactory(); + + abstract Iterable<T> getAnnotatablesForTestClass(TestClass testClass); + + abstract List<Exception> validateAnnotatable( + AnnotationValidator validator, T annotatable); + + public List<Exception> validateTestClass(TestClass testClass) { + List<Exception> validationErrors= new ArrayList<Exception>(); + for (T annotatable : getAnnotatablesForTestClass(testClass)) { + List<Exception> additionalErrors= validateAnnotatable(annotatable); + validationErrors.addAll(additionalErrors); + } + return validationErrors; + } + + private List<Exception> validateAnnotatable(T annotatable) { + List<Exception> validationErrors= new ArrayList<Exception>(); + for (Annotation annotation : annotatable.getAnnotations()) { + Class<? extends Annotation> annotationType = annotation + .annotationType(); + ValidateWith validateWith = annotationType + .getAnnotation(ValidateWith.class); + if (validateWith != null) { + AnnotationValidator annotationValidator = ANNOTATION_VALIDATOR_FACTORY + .createAnnotationValidator(validateWith); + List<Exception> errors= validateAnnotatable( + annotationValidator, annotatable); + validationErrors.addAll(errors); + } + } + return validationErrors; + } + } + + private static class ClassValidator extends AnnotatableValidator<TestClass> { + @Override + Iterable<TestClass> getAnnotatablesForTestClass(TestClass testClass) { + return singletonList(testClass); + } + + @Override + List<Exception> validateAnnotatable( + AnnotationValidator validator, TestClass testClass) { + return validator.validateAnnotatedClass(testClass); + } + } + + private static class MethodValidator extends + AnnotatableValidator<FrameworkMethod> { + @Override + Iterable<FrameworkMethod> getAnnotatablesForTestClass( + TestClass testClass) { + return testClass.getAnnotatedMethods(); + } + + @Override + List<Exception> validateAnnotatable( + AnnotationValidator validator, FrameworkMethod method) { + return validator.validateAnnotatedMethod(method); + } + } + + private static class FieldValidator extends + AnnotatableValidator<FrameworkField> { + @Override + Iterable<FrameworkField> getAnnotatablesForTestClass(TestClass testClass) { + return testClass.getAnnotatedFields(); + } + + @Override + List<Exception> validateAnnotatable( + AnnotationValidator validator, FrameworkField field) { + return validator.validateAnnotatedField(field); + } + }; +} diff --git a/src/main/java/org/junit/validator/PublicClassValidator.java b/src/main/java/org/junit/validator/PublicClassValidator.java new file mode 100644 index 0000000..fe3f185 --- /dev/null +++ b/src/main/java/org/junit/validator/PublicClassValidator.java @@ -0,0 +1,33 @@ +package org.junit.validator; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.List; + +import org.junit.runners.model.TestClass; + +/** + * Validates that a {@link TestClass} is public. + * + * @since 4.12 + */ +public class PublicClassValidator implements TestClassValidator { + private static final List<Exception> NO_VALIDATION_ERRORS = emptyList(); + + /** + * Validate that the specified {@link TestClass} is public. + * + * @param testClass the {@link TestClass} that is validated. + * @return an empty list if the class is public or a list with a single + * exception otherwise. + */ + public List<Exception> validateTestClass(TestClass testClass) { + if (testClass.isPublic()) { + return NO_VALIDATION_ERRORS; + } else { + return singletonList(new Exception("The class " + + testClass.getName() + " is not public.")); + } + } +} diff --git a/src/main/java/org/junit/validator/TestClassValidator.java b/src/main/java/org/junit/validator/TestClassValidator.java new file mode 100644 index 0000000..43cb787 --- /dev/null +++ b/src/main/java/org/junit/validator/TestClassValidator.java @@ -0,0 +1,21 @@ +package org.junit.validator; + +import java.util.List; + +import org.junit.runners.model.TestClass; + +/** + * Validates a single facet of a test class. + * + * @since 4.12 + */ +public interface TestClassValidator { + /** + * Validate a single facet of a test class. + * + * @param testClass + * the {@link TestClass} that is validated. + * @return the validation errors found by the validator. + */ + public List<Exception> validateTestClass(TestClass testClass); +} diff --git a/src/main/java/org/junit/validator/ValidateWith.java b/src/main/java/org/junit/validator/ValidateWith.java new file mode 100644 index 0000000..03d7906 --- /dev/null +++ b/src/main/java/org/junit/validator/ValidateWith.java @@ -0,0 +1,19 @@ +package org.junit.validator; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Allows for an {@link AnnotationValidator} to be attached to an annotation. + * + * <p>When attached to an annotation, the validator will be instantiated and invoked + * by the {@link org.junit.runners.ParentRunner}.</p> + * + * @since 4.12 + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ValidateWith { + Class<? extends AnnotationValidator> value(); +} |