Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global TestRules #1512

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/main/java/org/junit/internal/requests/GlobalRuleRequest.java
Original file line number Diff line number Diff line change
@@ -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
* <code>request</code>
*/
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);
}
}
}
41 changes: 40 additions & 1 deletion src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> filterSpecs = new ArrayList<String>();
private final List<Class<?>> classes = new ArrayList<Class<?>>();
private final List<Throwable> parserErrors = new ArrayList<Throwable>();
private final List<Class<?>> globalRules = new ArrayList<Class<?>>();

/**
* Do not use. Testing purposes only.
Expand All @@ -33,6 +36,13 @@ public List<Class<?>> getClasses() {
return Collections.unmodifiableList(classes);
}

/**
* Returns global rules classes parsed from command line.
*/
public List<Class<?>> getGlobalRules() {
return Collections.unmodifiableList(globalRules);
}

/**
* Parses the arguments.
*
Expand Down Expand Up @@ -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"));
}
Expand Down Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/junit/runner/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -169,4 +172,15 @@ public Request filterWith(Description desiredDescription) {
public Request sortWith(Comparator<Description> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;

}
33 changes: 33 additions & 0 deletions src/main/java/org/junit/runner/manipulation/GlobalRuleRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.junit.runner.manipulation;

import java.util.List;

/**
* A <code>GlobalRuleRunner</code> passes global {@link org.junit.rules.TestRule}s to the {@link org.junit.runner.Runner}.
* In general you will not need to use a <code>GlobalRuleRunner</code> directly. Instead,
* use {@link org.junit.runner.Request#withGlobalRules(GlobalRuleRunner)}.
*
* @since 4.13
*/
public class GlobalRuleRunner {

private final List<Class<?>> rules;

public GlobalRuleRunner(List<Class<?>> rules) {
this.rules = rules;
}

/**
* Applies provided global {@link org.junit.rules.TestRule}s to the <code>runner</code>
*/
public void apply(Object object) throws Exception {
if (object instanceof GlobalRuleRunnable) {
GlobalRuleRunnable runnable = (GlobalRuleRunnable) object;
runnable.setGlobalRules(this);
}
}

public List<Class<?>> getRules() {
return rules;
}
}
62 changes: 48 additions & 14 deletions src/main/java/org/junit/runners/ParentRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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<T> extends Runner implements Filterable,
Sortable {
Sortable, GlobalRuleRunnable {
private static final List<TestClassValidator> VALIDATORS = Arrays.<TestClassValidator>asList(
new AnnotationsValidator());

Expand All @@ -71,6 +64,8 @@ public abstract class ParentRunner<T> extends Runner implements Filterable,
// Guarded by childrenLock
private volatile Collection<T> filteredChildren = null;

private List<TestRule> globalRules = new ArrayList<TestRule>();

private volatile RunnerScheduler scheduler = new RunnerScheduler() {
public void schedule(Runnable childStatement) {
childStatement.run();
Expand Down Expand Up @@ -192,6 +187,7 @@ private void validateClassRules(List<Throwable> errors) {
* construct a statement that will:
* <ol>
* <li>Apply all {@code ClassRule}s on the test-class and superclasses.</li>
* <li>Apply all global {@code TestRule}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>
Expand All @@ -212,6 +208,7 @@ protected Statement classBlock(final RunNotifier notifier) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
statement = withGlobalRules(statement);
}
return statement;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
//
Expand Down
1 change: 1 addition & 0 deletions src/test/java/org/junit/rules/AllRulesTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@SuiteClasses({
BlockJUnit4ClassRunnerOverrideTest.class,
ClassRulesTest.class,
GlobalRulesTest.class,
DisableOnDebugTest.class,
ErrorCollectorTest.class,
ExpectedExceptionTest.class,
Expand Down
Loading