Skip to content

Commit 02c3280

Browse files
authored
Add Ordering, Orderable and @OrderWith (#1130)
* Add Ordering, Orderable and @OrderWith. These APIs allow arbitrary ordering of tests, including randomization.
1 parent 64eb730 commit 02c3280

24 files changed

+1292
-56
lines changed

src/main/java/junit/framework/JUnit4TestAdapter.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import org.junit.runner.Runner;
1010
import org.junit.runner.manipulation.Filter;
1111
import org.junit.runner.manipulation.Filterable;
12+
import org.junit.runner.manipulation.Orderer;
13+
import org.junit.runner.manipulation.InvalidOrderingException;
1214
import org.junit.runner.manipulation.NoTestsRemainException;
13-
import org.junit.runner.manipulation.Sortable;
15+
import org.junit.runner.manipulation.Orderable;
1416
import org.junit.runner.manipulation.Sorter;
1517

1618
/**
@@ -23,7 +25,7 @@ public static Test suite() {
2325
}
2426
</pre>
2527
*/
26-
public class JUnit4TestAdapter implements Test, Filterable, Sortable, Describable {
28+
public class JUnit4TestAdapter implements Test, Filterable, Orderable, Describable {
2729
private final Class<?> fNewTestClass;
2830

2931
private final Runner fRunner;
@@ -93,4 +95,13 @@ public void filter(Filter filter) throws NoTestsRemainException {
9395
public void sort(Sorter sorter) {
9496
sorter.apply(fRunner);
9597
}
98+
99+
/**
100+
* {@inheritDoc}
101+
*
102+
* @since 4.13
103+
*/
104+
public void order(Orderer orderer) throws InvalidOrderingException {
105+
orderer.apply(fRunner);
106+
}
96107
}

src/main/java/org/junit/internal/requests/ClassRequest.java

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
package org.junit.internal.requests;
22

3-
import java.util.concurrent.locks.Lock;
4-
import java.util.concurrent.locks.ReentrantLock;
5-
63
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
74
import org.junit.internal.builders.SuiteMethodBuilder;
8-
import org.junit.runner.Request;
95
import org.junit.runner.Runner;
106
import org.junit.runners.model.RunnerBuilder;
117

12-
public class ClassRequest extends Request {
13-
private final Lock runnerLock = new ReentrantLock();
14-
8+
public class ClassRequest extends MemoizingRequest {
159
/*
1610
* We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses
1711
* reflection to access this field. See
1812
* https://github.com/junit-team/junit4/issues/960
1913
*/
2014
private final Class<?> fTestClass;
2115
private final boolean canUseSuiteMethod;
22-
private volatile Runner runner;
2316

2417
public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) {
2518
this.fTestClass = testClass;
@@ -31,18 +24,8 @@ public ClassRequest(Class<?> testClass) {
3124
}
3225

3326
@Override
34-
public Runner getRunner() {
35-
if (runner == null) {
36-
runnerLock.lock();
37-
try {
38-
if (runner == null) {
39-
runner = new CustomAllDefaultPossibilitiesBuilder().safeRunnerForClass(fTestClass);
40-
}
41-
} finally {
42-
runnerLock.unlock();
43-
}
44-
}
45-
return runner;
27+
protected Runner createRunner() {
28+
return new CustomAllDefaultPossibilitiesBuilder().safeRunnerForClass(fTestClass);
4629
}
4730

4831
private class CustomAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.junit.internal.requests;
2+
3+
import java.util.concurrent.locks.Lock;
4+
import java.util.concurrent.locks.ReentrantLock;
5+
6+
import org.junit.runner.Request;
7+
import org.junit.runner.Runner;
8+
9+
abstract class MemoizingRequest extends Request {
10+
private final Lock runnerLock = new ReentrantLock();
11+
private volatile Runner runner;
12+
13+
@Override
14+
public final Runner getRunner() {
15+
if (runner == null) {
16+
runnerLock.lock();
17+
try {
18+
if (runner == null) {
19+
runner = createRunner();
20+
}
21+
} finally {
22+
runnerLock.unlock();
23+
}
24+
}
25+
return runner;
26+
}
27+
28+
/** Creates the {@link Runner} to return from {@link #getRunner()}. Called at most once. */
29+
protected abstract Runner createRunner();
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.junit.internal.requests;
2+
3+
import org.junit.internal.runners.ErrorReportingRunner;
4+
import org.junit.runner.Request;
5+
import org.junit.runner.Runner;
6+
import org.junit.runner.manipulation.InvalidOrderingException;
7+
import org.junit.runner.manipulation.Ordering;
8+
9+
/** @since 4.13 */
10+
public class OrderingRequest extends MemoizingRequest {
11+
private final Request request;
12+
private final Ordering ordering;
13+
14+
public OrderingRequest(Request request, Ordering ordering) {
15+
this.request = request;
16+
this.ordering = ordering;
17+
}
18+
19+
@Override
20+
protected Runner createRunner() {
21+
Runner runner = request.getRunner();
22+
try {
23+
ordering.apply(runner);
24+
} catch (InvalidOrderingException e) {
25+
return new ErrorReportingRunner(ordering.getClass(), e);
26+
}
27+
return runner;
28+
}
29+
}

src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.junit.internal.runners;
22

3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Method;
5+
36
import junit.extensions.TestDecorator;
47
import junit.framework.AssertionFailedError;
58
import junit.framework.Test;
@@ -12,15 +15,16 @@
1215
import org.junit.runner.Runner;
1316
import org.junit.runner.manipulation.Filter;
1417
import org.junit.runner.manipulation.Filterable;
18+
import org.junit.runner.manipulation.Orderer;
19+
import org.junit.runner.manipulation.InvalidOrderingException;
1520
import org.junit.runner.manipulation.NoTestsRemainException;
21+
import org.junit.runner.manipulation.Orderable;
1622
import org.junit.runner.manipulation.Sortable;
1723
import org.junit.runner.manipulation.Sorter;
1824
import org.junit.runner.notification.Failure;
1925
import org.junit.runner.notification.RunNotifier;
20-
import java.lang.annotation.Annotation;
21-
import java.lang.reflect.Method;
2226

23-
public class JUnit38ClassRunner extends Runner implements Filterable, Sortable {
27+
public class JUnit38ClassRunner extends Runner implements Filterable, Orderable {
2428
private static final class OldTestClassAdaptingListener implements
2529
TestListener {
2630
private final RunNotifier notifier;
@@ -170,6 +174,18 @@ public void sort(Sorter sorter) {
170174
}
171175
}
172176

177+
/**
178+
* {@inheritDoc}
179+
*
180+
* @since 4.13
181+
*/
182+
public void order(Orderer orderer) throws InvalidOrderingException {
183+
if (getTest() instanceof Orderable) {
184+
Orderable adapter = (Orderable) getTest();
185+
adapter.order(orderer);
186+
}
187+
}
188+
173189
private void setTest(Test test) {
174190
this.test = test;
175191
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.junit.runner;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Inherited;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
import org.junit.runner.manipulation.Ordering;
10+
11+
/**
12+
* When a test class is annotated with <code>&#064;OrderWith</code> or extends a class annotated
13+
* with <code>&#064;OrderWith</code>, JUnit will order the tests in the test class (and child
14+
* test classes, if any) using the ordering defined by the {@link Ordering} class.
15+
*
16+
* @since 4.13
17+
*/
18+
@Retention(RetentionPolicy.RUNTIME)
19+
@Target(ElementType.TYPE)
20+
@Inherited
21+
public @interface OrderWith {
22+
/**
23+
* Gets a class that extends {@link Ordering}. The class must have a public no-arg constructor.
24+
*/
25+
Class<? extends Ordering.Factory> value();
26+
}

src/main/java/org/junit/runner/Request.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
66
import org.junit.internal.requests.ClassRequest;
77
import org.junit.internal.requests.FilterRequest;
8+
import org.junit.internal.requests.OrderingRequest;
89
import org.junit.internal.requests.SortingRequest;
910
import org.junit.internal.runners.ErrorReportingRunner;
1011
import org.junit.runner.manipulation.Filter;
12+
import org.junit.runner.manipulation.Ordering;
1113
import org.junit.runners.model.InitializationError;
1214

1315
/**
@@ -151,15 +153,15 @@ public Request filterWith(Description desiredDescription) {
151153
* For example, here is code to run a test suite in alphabetical order:
152154
* <pre>
153155
* private static Comparator&lt;Description&gt; forward() {
154-
* return new Comparator&lt;Description&gt;() {
155-
* public int compare(Description o1, Description o2) {
156-
* return o1.getDisplayName().compareTo(o2.getDisplayName());
157-
* }
158-
* };
156+
* return new Comparator&lt;Description&gt;() {
157+
* public int compare(Description o1, Description o2) {
158+
* return o1.getDisplayName().compareTo(o2.getDisplayName());
159+
* }
160+
* };
159161
* }
160162
*
161163
* public static main() {
162-
* new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
164+
* new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
163165
* }
164166
* </pre>
165167
*
@@ -169,4 +171,32 @@ public Request filterWith(Description desiredDescription) {
169171
public Request sortWith(Comparator<Description> comparator) {
170172
return new SortingRequest(this, comparator);
171173
}
174+
175+
/**
176+
* Returns a Request whose Tests can be run in a certain order, defined by
177+
* <code>ordering</code>
178+
* <p>
179+
* For example, here is code to run a test suite in reverse order:
180+
* <pre>
181+
* private static Ordering reverse() {
182+
* return new Ordering() {
183+
* public List&lt;Description&gt; orderItems(Collection&lt;Description&gt; descriptions) {
184+
* List&lt;Description&gt; ordered = new ArrayList&lt;&gt;(descriptions);
185+
* Collections.reverse(ordered);
186+
* return ordered;
187+
* }
188+
* }
189+
* }
190+
*
191+
* public static main() {
192+
* new JUnitCore().run(Request.aClass(AllTests.class).orderWith(reverse()));
193+
* }
194+
* </pre>
195+
*
196+
* @return a Request with ordered Tests
197+
* @since 4.13
198+
*/
199+
public Request orderWith(Ordering ordering) {
200+
return new OrderingRequest(this, ordering);
201+
}
172202
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.junit.runner.manipulation;
2+
3+
import java.util.Comparator;
4+
5+
import org.junit.runner.Description;
6+
7+
/**
8+
* A sorter that orders tests alphanumerically by test name.
9+
*
10+
* @since 4.13
11+
*/
12+
public final class Alphanumeric extends Sorter implements Ordering.Factory {
13+
14+
public Alphanumeric() {
15+
super(COMPARATOR);
16+
}
17+
18+
public Ordering create(Context context) {
19+
return this;
20+
}
21+
22+
private static final Comparator<Description> COMPARATOR = new Comparator<Description>() {
23+
public int compare(Description o1, Description o2) {
24+
return o1.getDisplayName().compareTo(o2.getDisplayName());
25+
}
26+
};
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.junit.runner.manipulation;
2+
3+
/**
4+
* Thrown when an ordering does something invalid (like remove or add children)
5+
*
6+
* @since 4.13
7+
*/
8+
public class InvalidOrderingException extends Exception {
9+
private static final long serialVersionUID = 1L;
10+
11+
public InvalidOrderingException() {
12+
}
13+
14+
public InvalidOrderingException(String message) {
15+
super(message);
16+
}
17+
18+
public InvalidOrderingException(String message, Throwable cause) {
19+
super(message, cause);
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.junit.runner.manipulation;
2+
3+
/**
4+
* Interface for runners that allow ordering of tests.
5+
*
6+
* <p>Beware of using this interface to cope with order dependencies between tests.
7+
* Tests that are isolated from each other are less expensive to maintain and
8+
* can be run individually.
9+
*
10+
* @since 4.13
11+
*/
12+
public interface Orderable extends Sortable {
13+
14+
/**
15+
* Orders the tests using <code>orderer</code>
16+
*
17+
* @throws InvalidOrderingException if orderer does something invalid (like remove or add
18+
* children)
19+
*/
20+
void order(Orderer orderer) throws InvalidOrderingException;
21+
}

0 commit comments

Comments
 (0)