diff options
Diffstat (limited to 'src/main/java/org/junit/rules')
-rw-r--r-- | src/main/java/org/junit/rules/DisableOnDebug.java | 127 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/ErrorCollector.java | 113 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/ExpectedException.java | 382 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java | 46 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/ExternalResource.java | 101 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/MethodRule.java | 32 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/RuleChain.java | 112 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/RunRules.java | 33 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/Stopwatch.java | 183 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/TemporaryFolder.java | 253 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/TestName.java | 50 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/TestRule.java | 36 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/TestWatcher.java | 228 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/TestWatchman.java | 145 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/Timeout.java | 256 | ||||
-rw-r--r-- | src/main/java/org/junit/rules/Verifier.java | 44 |
16 files changed, 1474 insertions, 667 deletions
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 { + } } |