Skip to content

Commit b70d779

Browse files
committed
A conceptual test engine duplicating tests above jupiter.
1 parent 3ab3bbe commit b70d779

6 files changed

Lines changed: 204 additions & 1 deletion

File tree

randomizedtesting-jupiter/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
dependencies {
22
api libs.junit.jupiter
3+
implementation libs.junit.jupiter.engine
34

45
testImplementation libs.assertj
5-
testImplementation libs.junit.jupiter.engine
66
testImplementation libs.junit.platform.testkit
77
}
88

99
test {
1010
useJUnitPlatform {
1111
excludeTags 'nested-integration-test'
12+
includeEngines 'randomizedtesting-jupiter'
1213
}
1314

1415
def argProvider = objects.newInstance(UnsafeMemoryAccessArgProvider)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.carrotsearch.randomizedtesting.jupiter;
2+
3+
import org.junit.platform.engine.UniqueId;
4+
import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
5+
6+
/** A test descriptor representing one reiteration of the test suite. */
7+
final class IterationDescriptor extends AbstractTestDescriptor {
8+
private final int iteration;
9+
10+
IterationDescriptor(UniqueId uniqueId, int iteration) {
11+
super(uniqueId, "Iteration #" + iteration);
12+
this.iteration = iteration;
13+
}
14+
15+
@Override
16+
public Type getType() {
17+
return Type.CONTAINER;
18+
}
19+
20+
int getIteration() {
21+
return iteration;
22+
}
23+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.carrotsearch.randomizedtesting.jupiter;
2+
3+
import java.util.ServiceLoader;
4+
import org.junit.platform.engine.EngineDiscoveryRequest;
5+
import org.junit.platform.engine.ExecutionRequest;
6+
import org.junit.platform.engine.TestDescriptor;
7+
import org.junit.platform.engine.TestEngine;
8+
import org.junit.platform.engine.TestExecutionResult;
9+
import org.junit.platform.engine.UniqueId;
10+
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
11+
12+
/**
13+
* A {@link TestEngine} that delegates to JUnit Jupiter and multiplies test execution by running
14+
* tests in multiple top-level iteration containers, each receiving independently derived random
15+
* seeds.
16+
*
17+
* <p>The number of iterations is controlled by the {@value #ITERATIONS_PROPERTY} configuration
18+
* parameter (default: {@code 1}). Each iteration's tests have unique context identifiers, causing
19+
* {@link RandomizedContextSupplier} to derive different seeds per iteration even when a fixed root
20+
* seed is set via {@link RandomizedContextSupplier.SysProps#TESTS_SEED}.
21+
*/
22+
public class RandomizedTestEngine implements TestEngine {
23+
/** The unique engine ID ({@value}). */
24+
public static final String ENGINE_ID = "randomizedtesting-jupiter";
25+
26+
/** Configuration parameter controlling the number of test iterations. Default: {@code 1}. */
27+
public static final String ITERATIONS_PROPERTY = "tests.iterations";
28+
29+
private static final String JUPITER_ENGINE_ID = "junit-jupiter";
30+
31+
private final TestEngine jupiterEngine = loadJupiterEngine();
32+
33+
@Override
34+
public String getId() {
35+
return ENGINE_ID;
36+
}
37+
38+
@Override
39+
public TestDescriptor discover(EngineDiscoveryRequest request, UniqueId uniqueId) {
40+
int iterations =
41+
request
42+
.getConfigurationParameters()
43+
.get(ITERATIONS_PROPERTY)
44+
.map(Integer::parseInt)
45+
.orElse(1);
46+
47+
var engineDescriptor = new EngineDescriptor(uniqueId, "Randomized Testing");
48+
for (int i = 1; i <= iterations; i++) {
49+
var iterationUniqueId = uniqueId.append("iteration", String.valueOf(i));
50+
var jupiterRootId = iterationUniqueId.append("engine", JUPITER_ENGINE_ID);
51+
var jupiterDescriptor = jupiterEngine.discover(request, jupiterRootId);
52+
var iterationDescriptor = new IterationDescriptor(iterationUniqueId, i);
53+
iterationDescriptor.addChild(jupiterDescriptor);
54+
engineDescriptor.addChild(iterationDescriptor);
55+
}
56+
return engineDescriptor;
57+
}
58+
59+
@Override
60+
public void execute(ExecutionRequest request) {
61+
var engineDescriptor = request.getRootTestDescriptor();
62+
var listener = request.getEngineExecutionListener();
63+
listener.executionStarted(engineDescriptor);
64+
for (var child : engineDescriptor.getChildren()) {
65+
executeIteration((IterationDescriptor) child, request);
66+
}
67+
listener.executionFinished(engineDescriptor, TestExecutionResult.successful());
68+
}
69+
70+
private void executeIteration(IterationDescriptor iterationDescriptor, ExecutionRequest request) {
71+
var listener = request.getEngineExecutionListener();
72+
listener.executionStarted(iterationDescriptor);
73+
for (var jupiterDescriptor : iterationDescriptor.getChildren()) {
74+
jupiterEngine.execute(
75+
ExecutionRequest.create(
76+
jupiterDescriptor,
77+
listener,
78+
request.getConfigurationParameters(),
79+
request.getOutputDirectoryCreator(),
80+
request.getStore(),
81+
request.getCancellationToken()));
82+
}
83+
listener.executionFinished(iterationDescriptor, TestExecutionResult.successful());
84+
}
85+
86+
private static TestEngine loadJupiterEngine() {
87+
return ServiceLoader.load(TestEngine.class).stream()
88+
.filter(p -> p.type().getName().equals("org.junit.jupiter.engine.JupiterTestEngine"))
89+
.map(ServiceLoader.Provider::get)
90+
.findFirst()
91+
.orElseThrow(
92+
() ->
93+
new IllegalStateException(
94+
"JUnit Jupiter engine not found; add junit-jupiter-engine to the classpath"));
95+
}
96+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
module com.carrotsearch.randomizedtesting {
22
requires org.junit.jupiter.api;
33
requires org.junit.jupiter.params;
4+
requires org.junit.platform.engine;
45

56
exports com.carrotsearch.randomizedtesting.jupiter;
67

8+
uses org.junit.platform.engine.TestEngine;
9+
710
provides org.junit.jupiter.api.extension.Extension with
811
com.carrotsearch.randomizedtesting.jupiter.RandomizedContextSupplier;
12+
provides org.junit.platform.engine.TestEngine with
13+
com.carrotsearch.randomizedtesting.jupiter.RandomizedTestEngine;
914
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.carrotsearch.randomizedtesting.jupiter.RandomizedTestEngine
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.carrotsearch.randomizedtesting.jupiter;
2+
3+
import static com.carrotsearch.randomizedtesting.jupiter.infra.TestInfra.collectExecutionResults;
4+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
5+
import static org.junit.platform.testkit.engine.EventConditions.event;
6+
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
7+
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
8+
import static org.junit.platform.testkit.engine.EventConditions.test;
9+
10+
import com.carrotsearch.randomizedtesting.jupiter.infra.IgnoreInStandaloneRuns;
11+
import java.io.PrintWriter;
12+
import org.assertj.core.api.Assertions;
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.platform.testkit.engine.EngineTestKit;
16+
17+
/** Verifies that {@link RandomizedTestEngine} multiplies test execution by iteration count. */
18+
public class F004_TestEngineIterations {
19+
@Nested
20+
class TestIterationMultiplier {
21+
@Test
22+
void testsRunOnceByDefault() {
23+
var result =
24+
collectExecutionResults(
25+
EngineTestKit.engine(new RandomizedTestEngine())
26+
.configurationParameter("nested-integration-test", "true")
27+
.selectors(selectClass(SimpleTest.class)));
28+
29+
result
30+
.results()
31+
.testEvents()
32+
.assertThatEvents()
33+
.doNotHave(event(finishedWithFailure()))
34+
.haveExactly(1, event(test(), finishedSuccessfully()));
35+
}
36+
37+
@Test
38+
void testsAreMultipliedByIterationCount() {
39+
var result =
40+
collectExecutionResults(
41+
EngineTestKit.engine(new RandomizedTestEngine())
42+
.configurationParameter(RandomizedTestEngine.ITERATIONS_PROPERTY, "3")
43+
.configurationParameter("nested-integration-test", "true")
44+
.selectors(selectClass(SimpleTest.class)));
45+
46+
result.results().testEvents().assertStatistics(s -> s.finished(3).succeeded(3));
47+
}
48+
49+
@Test
50+
void seedsDifferAcrossIterationsWithFixedRootSeed() {
51+
var result =
52+
collectExecutionResults(
53+
EngineTestKit.engine(new RandomizedTestEngine())
54+
.configurationParameter(RandomizedTestEngine.ITERATIONS_PROPERTY, "3")
55+
.configurationParameter("nested-integration-test", "true")
56+
.configurationParameter(
57+
RandomizedContextSupplier.SysProps.TESTS_SEED.propertyKey, "DEAD")
58+
.selectors(selectClass(SimpleTest.class)));
59+
60+
result.results().testEvents().assertStatistics(s -> s.finished(3).succeeded(3));
61+
62+
// Each iteration derives a different seed because its unique ID contains the iteration index.
63+
Assertions.assertThat(result.capturedOutput().values().stream().distinct())
64+
.as("seeds should differ across iterations even with a fixed root seed")
65+
.hasSize(3);
66+
}
67+
68+
@Randomized
69+
static class SimpleTest extends IgnoreInStandaloneRuns {
70+
@Test
71+
void test(PrintWriter pw, RandomizedContext ctx) {
72+
System.out.println(ctx.contextId);
73+
pw.println(ctx.getSeedChain());
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)