Skip to content

Commit d87f3fb

Browse files
committed
Add support for Supplier<Random> injection.
1 parent d5538e2 commit d87f3fb

4 files changed

Lines changed: 105 additions & 7 deletions

File tree

randomizedtesting-jupiter/src/main/java/com/carrotsearch/randomizedtesting/jupiter/internals/RandomizedContextExtension.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
import com.carrotsearch.randomizedtesting.jupiter.SysProps;
99
import java.lang.reflect.Constructor;
1010
import java.lang.reflect.Method;
11+
import java.lang.reflect.ParameterizedType;
12+
import java.lang.reflect.Type;
1113
import java.util.ArrayList;
1214
import java.util.Arrays;
1315
import java.util.List;
1416
import java.util.Objects;
1517
import java.util.Optional;
1618
import java.util.Random;
1719
import java.util.function.LongFunction;
20+
import java.util.function.Supplier;
1821
import org.jspecify.annotations.Nullable;
1922
import org.junit.jupiter.api.extension.BeforeAllCallback;
2023
import org.junit.jupiter.api.extension.DynamicTestInvocationContext;
@@ -103,28 +106,44 @@ private static SeedChain parseRootSeed(Optional<String> rootSeedValue) {
103106
//
104107
// ParameterResolver: inject RandomizedContext and Random instances into test methods.
105108
//
109+
private static final Type supplierOfRandom = getSupplierOfRandomType();
110+
111+
/** Helper method that returns parameterized type for {@code Supplier<Random>}. */
112+
private static Type getSupplierOfRandomType() {
113+
abstract class TypeLiteral<T> {
114+
public Type getType() {
115+
return ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
116+
}
117+
}
118+
119+
return (new TypeLiteral<Supplier<Random>>() {}).getType();
120+
}
106121

107122
@Override
108123
public boolean supportsParameter(
109124
ParameterContext parameterContext, ExtensionContext extensionContext)
110125
throws ParameterResolutionException {
111-
Class<?> parameterType = parameterContext.getParameter().getType();
112-
return parameterType.equals(RandomizedContext.class) || parameterType.equals(Random.class);
126+
var type = parameterContext.getParameter().getParameterizedType();
127+
return type.equals(RandomizedContext.class)
128+
|| type.equals(Random.class)
129+
|| type.equals(supplierOfRandom);
113130
}
114131

115132
@Override
116133
public Object resolveParameter(
117134
ParameterContext parameterContext, ExtensionContext extensionContext)
118135
throws ParameterResolutionException {
119136
var ctx = getRandomizedContextFor(extensionContext);
120-
Class<?> parameterType = parameterContext.getParameter().getType();
121-
if (parameterType.equals(RandomizedContext.class)) {
137+
var type = parameterContext.getParameter().getParameterizedType();
138+
if (type.equals(RandomizedContext.class)) {
122139
return ctx;
123-
} else if (parameterType.equals(Random.class)) {
140+
} else if (type.equals(Random.class)) {
124141
return ctx.getRandom();
142+
} else if (type.equals(supplierOfRandom)) {
143+
return (Supplier<Random>) ctx::splitRandom;
125144
} else {
126145
throw new RuntimeException(
127-
"Unexpected unsupported parameter type in resolveParameter: " + parameterType);
146+
"Unexpected unsupported parameter type in resolveParameter: " + type);
128147
}
129148
}
130149

randomizedtesting-jupiter/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
requires org.junit.jupiter.api;
55
requires org.junit.jupiter.params;
66
requires java.logging;
7+
requires java.compiler;
78

89
exports com.carrotsearch.randomizedtesting.jupiter;
910
exports com.carrotsearch.randomizedtesting.jupiter.generators;

randomizedtesting-jupiter/src/test/java/com/carrotsearch/randomizedtesting/tests/F003_RandomInjection.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
import com.carrotsearch.randomizedtesting.jupiter.SysProps;
1111
import com.carrotsearch.randomizedtesting.tests.infra.IgnoreInStandaloneRuns;
1212
import java.io.PrintWriter;
13+
import java.util.HashSet;
1314
import java.util.Locale;
1415
import java.util.Random;
1516
import java.util.concurrent.atomic.AtomicReference;
17+
import java.util.function.Supplier;
1618
import java.util.stream.Collectors;
1719
import java.util.stream.IntStream;
1820
import java.util.stream.Stream;
@@ -73,6 +75,78 @@ static void afterAll(Random random) {
7375
}
7476
}
7577

78+
@Nested
79+
class TestRandomSupplierInjection {
80+
@Test
81+
public void testAllHooks() {
82+
collectExecutionResults(testKitBuilder(T1.class))
83+
.results()
84+
.allEvents()
85+
.assertThatEvents()
86+
.doNotHave(event(finishedWithFailure()));
87+
}
88+
89+
@Randomized
90+
static class T1 extends IgnoreInStandaloneRuns {
91+
public T1(Supplier<Random> supplier) {
92+
check(supplier);
93+
}
94+
95+
@BeforeAll
96+
static void beforeAll(Supplier<Random> supplier) {
97+
check(supplier);
98+
}
99+
100+
@BeforeEach
101+
void beforeEach(Supplier<Random> supplier) {
102+
check(supplier);
103+
}
104+
105+
@Test
106+
void testMethod(Supplier<Random> supplier) {
107+
check(supplier);
108+
}
109+
110+
@AfterEach
111+
void afterEach(Supplier<Random> supplier) {
112+
check(supplier);
113+
}
114+
115+
@AfterAll
116+
static void afterAll(Supplier<Random> supplier) {
117+
check(supplier);
118+
}
119+
120+
private static void check(Supplier<Random> supplier) {
121+
Assertions.assertThat(supplier).isNotNull();
122+
Assertions.assertThat(supplier.get()).isNotNull();
123+
124+
// ensure any threads that acquire a random from the supplier are started with
125+
// the same initial seed.
126+
var firstLongs =
127+
IntStream.range(0, 5)
128+
.mapToObj(
129+
i -> {
130+
try {
131+
AtomicReference<Object> rnd = new AtomicReference<>();
132+
var t =
133+
new Thread(
134+
() -> {
135+
rnd.set(supplier.get().nextLong());
136+
});
137+
t.start();
138+
t.join();
139+
return rnd.get();
140+
} catch (InterruptedException e) {
141+
throw new RuntimeException(e);
142+
}
143+
})
144+
.toList();
145+
Assertions.assertThat(new HashSet<>(firstLongs)).hasSize(1);
146+
}
147+
}
148+
}
149+
76150
@Nested
77151
class TestRandomFactoryAndState {
78152
@Test

randomizedtesting-jupiter/src/test/java/com/carrotsearch/randomizedtesting/tests/F003_RandomInjection.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class TestClass {
1717

1818
The injected `Random` is initialized with the context's seed.
1919

20-
* It should be possible to pick (via system properties or JUnit5 configuratoin
20+
* It should be possible to pick (via system properties or JUnit5 configuration
2121
parameters) different `Random` implementations for the
2222
injected parameter. Lockless or those providing larger state space than
2323
the default `java.util.Random`.
@@ -27,6 +27,10 @@ The injected `Random` is initialized with the context's seed.
2727
Random instances should verify they are indeed used from within the
2828
right thread.
2929

30+
* It should be possible to inject a parameter of type `Supplier<Random>`. This
31+
supplier is safe to use from any thread; it returns a `Random` instance initialized
32+
with the same starting seed if called from a previously unseen thread.
33+
3034
## Migration notes (from randomizedtesting for junit4)
3135

3236
This is new functionality, it wasn't available before.

0 commit comments

Comments
 (0)