diff --git a/src/main/java/org/junit/internal/requests/GlobalRuleRequest.java b/src/main/java/org/junit/internal/requests/GlobalRuleRequest.java
new file mode 100644
index 000000000000..10aa1f0f318e
--- /dev/null
+++ b/src/main/java/org/junit/internal/requests/GlobalRuleRequest.java
@@ -0,0 +1,37 @@
+package org.junit.internal.requests;
+
+import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.runner.Request;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.GlobalRuleRunner;
+
+/**
+ * A {@link Request} that adds global rules to the {@link Runner}.
+ */
+public final class GlobalRuleRequest extends Request {
+ private final Request request;
+ private final GlobalRuleRunner ruleRunner;
+
+ /**
+ * Creates a Request with global rules
+ *
+ * @param request a {@link Request} describing your Tests
+ * @param ruleRunner {@link GlobalRuleRunner} to apply to the Tests described in
+ * request
+ */
+ public GlobalRuleRequest(Request request, GlobalRuleRunner ruleRunner) {
+ this.request = request;
+ this.ruleRunner = ruleRunner;
+ }
+
+ @Override
+ public Runner getRunner() {
+ try {
+ Runner runner = request.getRunner();
+ ruleRunner.apply(runner);
+ return runner;
+ } catch (Exception e) {
+ return new ErrorReportingRunner(GlobalRuleRunner.class, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
index 338340789ee8..2b5f26e23591 100644
--- a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
+++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
@@ -5,14 +5,17 @@
import java.util.List;
import org.junit.internal.Classes;
+import org.junit.rules.TestRule;
import org.junit.runner.FilterFactory.FilterNotCreatedException;
import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.GlobalRuleRunner;
import org.junit.runners.model.InitializationError;
class JUnitCommandLineParseResult {
private final List filterSpecs = new ArrayList();
private final List> classes = new ArrayList>();
private final List parserErrors = new ArrayList();
+ private final List> globalRules = new ArrayList>();
/**
* Do not use. Testing purposes only.
@@ -33,6 +36,13 @@ public List> getClasses() {
return Collections.unmodifiableList(classes);
}
+ /**
+ * Returns global rules classes parsed from command line.
+ */
+ public List> getGlobalRules() {
+ return Collections.unmodifiableList(globalRules);
+ }
+
/**
* Parses the arguments.
*
@@ -73,6 +83,27 @@ String[] parseOptions(String... args) {
}
filterSpecs.add(filterSpec);
+ } else if (arg.equals("--global-rule")) {
+ ++i;
+
+ if (i < args.length) {
+ try {
+ Class> clazz = Classes.getClass(args[i]);
+
+ if (TestRule.class.isAssignableFrom(clazz)) {
+ globalRules.add(clazz);
+ } else {
+ parserErrors.add(new CommandLineParserError(args[i] + " does not implement TestRule"));
+ break;
+ }
+ } catch (ClassNotFoundException e) {
+ parserErrors.add(new CommandLineParserError(args[i] + " is not a valid value for global rule"));
+ break;
+ }
+ } else {
+ parserErrors.add(new CommandLineParserError(arg + " value not specified"));
+ break;
+ }
} else {
parserErrors.add(new CommandLineParserError("JUnit knows nothing about the " + arg + " option"));
}
@@ -115,12 +146,20 @@ public Request createRequest(Computer computer) {
if (parserErrors.isEmpty()) {
Request request = Request.classes(
computer, classes.toArray(new Class>[classes.size()]));
- return applyFilterSpecs(request);
+ return applyFilterSpecs(applyGlobalRules(request));
} else {
return errorReport(new InitializationError(parserErrors));
}
}
+ private Request applyGlobalRules(Request request) {
+ if (!globalRules.isEmpty()) {
+ return request.withGlobalRules(new GlobalRuleRunner(globalRules));
+ } else {
+ return request;
+ }
+ }
+
private Request applyFilterSpecs(Request request) {
try {
for (String filterSpec : filterSpecs) {
diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java
index 264489217f74..474e8a814fb3 100644
--- a/src/main/java/org/junit/runner/Request.java
+++ b/src/main/java/org/junit/runner/Request.java
@@ -5,9 +5,12 @@
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.internal.requests.ClassRequest;
import org.junit.internal.requests.FilterRequest;
+import org.junit.internal.requests.GlobalRuleRequest;
import org.junit.internal.requests.SortingRequest;
import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.rules.TestRule;
import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.GlobalRuleRunner;
import org.junit.runners.model.InitializationError;
/**
@@ -169,4 +172,15 @@ public Request filterWith(Description desiredDescription) {
public Request sortWith(Comparator comparator) {
return new SortingRequest(this, comparator);
}
+
+ /**
+ * Returns a Request with global {@link TestRule}s that are applied
+ * to every test being run with this Request
+ *
+ * @param ruleRunner The {@link GlobalRuleRunner} to apply to this Request
+ * @return a Request with applied global {@link TestRule}s
+ */
+ public Request withGlobalRules(GlobalRuleRunner ruleRunner) {
+ return new GlobalRuleRequest(this, ruleRunner);
+ }
}
diff --git a/src/main/java/org/junit/runner/manipulation/GlobalRuleRunnable.java b/src/main/java/org/junit/runner/manipulation/GlobalRuleRunnable.java
new file mode 100644
index 000000000000..c32219d6f3d3
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/GlobalRuleRunnable.java
@@ -0,0 +1,20 @@
+package org.junit.runner.manipulation;
+
+/**
+ * Runners that allow global {@link org.junit.rules.TestRule} should implement this interface.
+ * Implement {@link #setGlobalRules(GlobalRuleRunner)}} to receive the list of classes assignable to
+ * {@link org.junit.rules.TestRule} to apply on every test.
+ *
+ * @since 4.13
+ */
+public interface GlobalRuleRunnable {
+
+ /**
+ * Instantiate and apply global {@link org.junit.rules.TestRule}s to every test.
+ *
+ * @param ruleRunner the {@link GlobalRuleRunner} to apply
+ * @throws Exception if any class does not meet {@link org.junit.runner.Runner}'s requirements
+ */
+ void setGlobalRules(GlobalRuleRunner ruleRunner) throws Exception;
+
+}
diff --git a/src/main/java/org/junit/runner/manipulation/GlobalRuleRunner.java b/src/main/java/org/junit/runner/manipulation/GlobalRuleRunner.java
new file mode 100644
index 000000000000..d833c7ca72a3
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/GlobalRuleRunner.java
@@ -0,0 +1,33 @@
+package org.junit.runner.manipulation;
+
+import java.util.List;
+
+/**
+ * A GlobalRuleRunner
passes global {@link org.junit.rules.TestRule}s to the {@link org.junit.runner.Runner}.
+ * In general you will not need to use a GlobalRuleRunner
directly. Instead,
+ * use {@link org.junit.runner.Request#withGlobalRules(GlobalRuleRunner)}.
+ *
+ * @since 4.13
+ */
+public class GlobalRuleRunner {
+
+ private final List> rules;
+
+ public GlobalRuleRunner(List> rules) {
+ this.rules = rules;
+ }
+
+ /**
+ * Applies provided global {@link org.junit.rules.TestRule}s to the runner
+ */
+ public void apply(Object object) throws Exception {
+ if (object instanceof GlobalRuleRunnable) {
+ GlobalRuleRunnable runnable = (GlobalRuleRunnable) object;
+ runnable.setGlobalRules(this);
+ }
+ }
+
+ public List> getRules() {
+ return rules;
+ }
+}
diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java
index 4949c242e5b2..b77b928c6315 100644
--- a/src/main/java/org/junit/runners/ParentRunner.java
+++ b/src/main/java/org/junit/runners/ParentRunner.java
@@ -5,6 +5,7 @@
import static org.junit.internal.runners.rules.RuleMemberValidator.CLASS_RULE_VALIDATOR;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@@ -16,11 +17,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Rule;
+import org.junit.*;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.statements.RunAfters;
@@ -29,11 +26,7 @@
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.manipulation.Sortable;
-import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.manipulation.*;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.model.FrameworkMember;
@@ -55,13 +48,13 @@
* 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,
- * handle annotated {@link ClassRule}s, create a composite
- * {@link Description}, and run children sequentially.
+ * handle annotated {@link ClassRule}s, handle provided global {@link TestRule}s,
+ * create a composite {@link Description}, and run children sequentially.
*
* @since 4.5
*/
public abstract class ParentRunner extends Runner implements Filterable,
- Sortable {
+ Sortable, GlobalRuleRunnable {
private static final List VALIDATORS = Arrays.asList(
new AnnotationsValidator());
@@ -71,6 +64,8 @@ public abstract class ParentRunner extends Runner implements Filterable,
// Guarded by childrenLock
private volatile Collection filteredChildren = null;
+ private List globalRules = new ArrayList();
+
private volatile RunnerScheduler scheduler = new RunnerScheduler() {
public void schedule(Runnable childStatement) {
childStatement.run();
@@ -192,6 +187,7 @@ private void validateClassRules(List errors) {
* construct a statement that will:
*
* - Apply all {@code ClassRule}s on the test-class and superclasses.
+ * - Apply all global {@code TestRule}s on the test-class and superclasses.
* - 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.
@@ -212,6 +208,7 @@ protected Statement classBlock(final RunNotifier notifier) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
+ statement = withGlobalRules(statement);
}
return statement;
}
@@ -266,6 +263,20 @@ private Statement withClassRules(Statement statement) {
new RunRules(statement, classRules, getDescription());
}
+ /**
+ * Returns a {@link Statement}: apply all
+ * classes assignable to {@link TestRule} provided
+ * by command line argument {@code --global-rule}.
+ *
+ * @param statement the base statement
+ * @return a RunRules statement if any global-level {@link TestRule}s are
+ * provided, or the base statement
+ */
+ private Statement withGlobalRules(Statement statement) {
+ return globalRules.isEmpty() ? statement :
+ new RunRules(statement, globalRules, getDescription());
+ }
+
/**
* @return the {@code ClassRule}s that can transform the block that runs
* each method in the tested class.
@@ -403,7 +414,7 @@ public void run(final RunNotifier notifier) {
}
//
- // Implementation of Filterable and Sortable
+ // Implementation of Filterable, Sortable and GlobalRuleRunnable
//
public void filter(Filter filter) throws NoTestsRemainException {
@@ -445,6 +456,29 @@ public void sort(Sorter sorter) {
}
}
+ public void setGlobalRules(GlobalRuleRunner rules) throws Exception {
+ childrenLock.lock();
+ try {
+ for (T each : getFilteredChildren()) {
+ rules.apply(each);
+ }
+ for (Class> clazz : rules.getRules()) {
+ Constructor>[] constructors = clazz.getConstructors();
+ if (constructors.length > 1) {
+ throw new IllegalArgumentException("Global TestRule can only have one constructor");
+ }
+ Assert.assertEquals(1, constructors.length);
+ Constructor> constructor = clazz.getConstructors()[0];
+ if (constructor.getParameterTypes().length > 0) {
+ throw new IllegalArgumentException("Global TestRule constructor cannot have parameters");
+ }
+ globalRules.add(TestRule.class.cast(constructor.newInstance()));
+ }
+ } finally {
+ childrenLock.unlock();
+ }
+ }
+
//
// Private implementation
//
diff --git a/src/test/java/org/junit/rules/AllRulesTests.java b/src/test/java/org/junit/rules/AllRulesTests.java
index 8b63e1510db3..efe356fd4195 100644
--- a/src/test/java/org/junit/rules/AllRulesTests.java
+++ b/src/test/java/org/junit/rules/AllRulesTests.java
@@ -8,6 +8,7 @@
@SuiteClasses({
BlockJUnit4ClassRunnerOverrideTest.class,
ClassRulesTest.class,
+ GlobalRulesTest.class,
DisableOnDebugTest.class,
ErrorCollectorTest.class,
ExpectedExceptionTest.class,
diff --git a/src/test/java/org/junit/rules/GlobalRulesTest.java b/src/test/java/org/junit/rules/GlobalRulesTest.java
new file mode 100644
index 000000000000..46de1bc9a131
--- /dev/null
+++ b/src/test/java/org/junit/rules/GlobalRulesTest.java
@@ -0,0 +1,60 @@
+package org.junit.rules;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.manipulation.GlobalRuleRunner;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests to exercise global-level rules.
+ */
+public class GlobalRulesTest {
+ private static int count = 0;
+
+ public static class Counter implements TestRule {
+ public Statement apply(Statement base, Description description) {
+ count++;
+ return base;
+ }
+ }
+
+ public static class ExampleTest {
+ @Test
+ public void firstTest() {
+ assertEquals(2, count);
+ }
+
+ @Test
+ public void secondTest() {
+ assertEquals(2, count);
+ }
+ }
+
+ public static class SecondExampleTest {
+ @Test
+ public void firstTest() {
+ assertEquals(3, count);
+ }
+
+ @Test
+ public void secondTest() {
+ assertEquals(3, count);
+ }
+ }
+
+ @Test
+ public void rulesAreAppliedOnEveryTest() {
+ List> rules = new ArrayList>();
+ rules.add(Counter.class);
+ new JUnitCore().run(Request.classes(ExampleTest.class, SecondExampleTest.class)
+ .withGlobalRules(new GlobalRuleRunner(rules)));
+ assertEquals(3, count);
+ }
+}
diff --git a/src/test/java/org/junit/runner/JUnitCommandLineParseResultTest.java b/src/test/java/org/junit/runner/JUnitCommandLineParseResultTest.java
index ffd1f1c9552f..e76d532b3770 100644
--- a/src/test/java/org/junit/runner/JUnitCommandLineParseResultTest.java
+++ b/src/test/java/org/junit/runner/JUnitCommandLineParseResultTest.java
@@ -11,7 +11,9 @@
import org.junit.Test;
import org.junit.experimental.categories.IncludeCategories;
import org.junit.rules.ExpectedException;
+import org.junit.rules.TestRule;
import org.junit.runner.manipulation.Filter;
+import org.junit.runners.model.Statement;
public class JUnitCommandLineParseResultTest {
@Rule
@@ -47,6 +49,16 @@ public void shouldCreateFailureUponBaldFilterOptionNotFollowedByValue() {
assertThat(description.toString(), containsString("initializationError"));
}
+ @Test
+ public void shouldCreateFailureUponBaldGlobalRuleOptionNotFollowedByValue() {
+ jUnitCommandLineParseResult.parseOptions("--global-rule");
+
+ Runner runner = jUnitCommandLineParseResult.createRequest(new Computer()).getRunner();
+ Description description = runner.getDescription().getChildren().get(0);
+
+ assertThat(description.toString(), containsString("initializationError"));
+ }
+
@Test
public void shouldParseFilterArgInWhichValueIsASeparateArg() throws Exception {
String value= IncludeCategories.class.getName() + "=" + DummyCategory0.class.getName();
@@ -116,6 +128,19 @@ public void shouldAddToClasses() {
assertThat(testClass.getName(), is(DummyTest.class.getName()));
}
+ @Test
+ public void shouldAddToGlobalRules() {
+ jUnitCommandLineParseResult.parseOptions(new String[]{
+ "--global-rule",
+ DummyRule.class.getName()
+ });
+
+ List> classes = jUnitCommandLineParseResult.getGlobalRules();
+ Class> ruleClass = classes.get(0);
+
+ assertThat(ruleClass.getName(), is(DummyRule.class.getName()));
+ }
+
@Test
public void shouldCreateFailureUponUnknownTestClass() throws Exception {
String unknownTestClass = "UnknownTestClass";
@@ -129,6 +154,33 @@ public void shouldCreateFailureUponUnknownTestClass() throws Exception {
assertThat(description.toString(), containsString("initializationError"));
}
+ @Test
+ public void shouldCreateFailureUponUnknownGlobalRule() throws Exception {
+ String unknownRuleClass = "UnknownRuleClass";
+ jUnitCommandLineParseResult.parseOptions(new String[]{
+ "--global-rule",
+ unknownRuleClass
+ });
+
+ Runner runner = jUnitCommandLineParseResult.createRequest(new Computer()).getRunner();
+ Description description = runner.getDescription().getChildren().get(0);
+
+ assertThat(description.toString(), containsString("initializationError"));
+ }
+
+ @Test
+ public void shouldCreateFailureUponNonAssignableToTestRuleGlobalRule() throws Exception {
+ jUnitCommandLineParseResult.parseOptions(new String[]{
+ "--global-rule",
+ DummyTest.class.getName()
+ });
+
+ Runner runner = jUnitCommandLineParseResult.createRequest(new Computer()).getRunner();
+ Description description = runner.getDescription().getChildren().get(0);
+
+ assertThat(description.toString(), containsString("initializationError"));
+ }
+
public static class FilterFactoryStub implements FilterFactory {
public Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException {
throw new FilterNotCreatedException(new Exception("stub"));
@@ -143,4 +195,10 @@ public static class DummyTest {
public void dummyTest() {
}
}
+
+ public static class DummyRule implements TestRule {
+ public Statement apply(Statement base, Description description) {
+ return base;
+ }
+ }
}