1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.core;
18
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.Spliterator;
22 import java.util.SplittableRandom;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.ThreadLocalRandom;
25 import java.util.function.Consumer;
26 import java.util.function.UnaryOperator;
27 import java.util.stream.Stream;
28 import org.apache.commons.rng.SplittableUniformRandomProvider;
29 import org.apache.commons.rng.UniformRandomProvider;
30 import org.apache.commons.rng.core.util.RandomStreamsTestHelper;
31 import org.junit.jupiter.api.Assertions;
32 import org.junit.jupiter.params.ParameterizedTest;
33 import org.junit.jupiter.params.provider.MethodSource;
34
35
36
37
38 class SplittableProvidersParametricTest {
39
40 private static final int SPLITERATOR_CHARACTERISTICS =
41 Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.IMMUTABLE;
42
43
44
45
46
47
48 private static class DummyGenerator implements SplittableUniformRandomProvider {
49
50 static final DummyGenerator INSTANCE = new DummyGenerator();
51
52 @Override
53 public long nextLong() {
54 throw new UnsupportedOperationException("The nextLong method should not be invoked");
55 }
56
57 @Override
58 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
59 throw new UnsupportedOperationException("The split(source) method should not be invoked");
60 }
61 }
62
63
64
65
66
67
68 private static class ThreadLocalGenerator implements SplittableUniformRandomProvider {
69
70 static final ThreadLocalGenerator INSTANCE = new ThreadLocalGenerator();
71
72 @Override
73 public long nextLong() {
74 return ThreadLocalRandom.current().nextLong();
75 }
76
77 @Override
78 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
79 return this;
80 }
81 }
82
83
84
85
86
87
88 private static Iterable<SplittableUniformRandomProvider> getSplittableProviders() {
89 return ProvidersList.listSplittable();
90 }
91
92
93
94
95 @ParameterizedTest
96 @MethodSource("getSplittableProviders")
97 void testSplitThrowsWithNullSource(SplittableUniformRandomProvider generator) {
98 Assertions.assertThrows(NullPointerException.class, () -> generator.split(null));
99 }
100
101
102
103
104 @ParameterizedTest
105 @MethodSource("getSplittableProviders")
106 void testSplitReturnsANewInstance(SplittableUniformRandomProvider generator) {
107 assertSplitReturnsANewInstance(SplittableUniformRandomProvider::split, generator);
108 }
109
110
111
112
113 @ParameterizedTest
114 @MethodSource("getSplittableProviders")
115 void testSplitWithSourceReturnsANewInstance(SplittableUniformRandomProvider generator) {
116 assertSplitReturnsANewInstance(s -> s.split(ThreadLocalGenerator.INSTANCE), generator);
117 }
118
119
120
121
122
123
124
125 private static void assertSplitReturnsANewInstance(UnaryOperator<SplittableUniformRandomProvider> splitFunction,
126 SplittableUniformRandomProvider generator) {
127 final UniformRandomProvider child = splitFunction.apply(generator);
128 Assertions.assertNotSame(generator, child, "The child instance should be a different object");
129 Assertions.assertEquals(generator.getClass(), child.getClass(), "The child instance should be the same class");
130 RandomAssert.assertNextLongNotEquals(10, generator, child);
131 }
132
133
134
135
136
137 @ParameterizedTest
138 @MethodSource("getSplittableProviders")
139 void testSplitWithSourceIsReproducible(SplittableUniformRandomProvider generator) {
140 final long seed = ThreadLocalRandom.current().nextLong();
141 final UniformRandomProvider rng1 = generator.split(new SplittableRandom(seed)::nextLong);
142 final UniformRandomProvider rng2 = generator.split(new SplittableRandom(seed)::nextLong);
143 RandomAssert.assertNextLongEquals(10, rng1, rng2);
144 }
145
146
147
148
149
150
151
152
153
154 @ParameterizedTest
155 @MethodSource("getSplittableProviders")
156 void testSplitsMethodsUseSameSpliterator(SplittableUniformRandomProvider generator) {
157 final long size = 10;
158 final Spliterator<SplittableUniformRandomProvider> s = generator.splits(size, generator).spliterator();
159 Assertions.assertEquals(s.getClass(), generator.splits().spliterator().getClass());
160 Assertions.assertEquals(s.getClass(), generator.splits(size).spliterator().getClass());
161 Assertions.assertEquals(s.getClass(), generator.splits(ThreadLocalGenerator.INSTANCE).spliterator().getClass());
162 }
163
164 @ParameterizedTest
165 @MethodSource("getSplittableProviders")
166 void testSplitsSize(SplittableUniformRandomProvider generator) {
167 for (final long size : new long[] {0, 1, 7, 13}) {
168 Assertions.assertEquals(size, generator.splits(size).count(), "splits");
169 Assertions.assertEquals(size, generator.splits(size, ThreadLocalGenerator.INSTANCE).count(), "splits with source");
170 }
171 }
172
173 @ParameterizedTest
174 @MethodSource("getSplittableProviders")
175 void testSplits(SplittableUniformRandomProvider generator) {
176 assertSplits(generator, false);
177 }
178
179 @ParameterizedTest
180 @MethodSource("getSplittableProviders")
181 void testSplitsParallel(SplittableUniformRandomProvider generator) {
182 assertSplits(generator, true);
183 }
184
185
186
187
188
189
190
191
192
193 private static void assertSplits(SplittableUniformRandomProvider generator, boolean parallel) {
194 final long size = 13;
195 for (final long seed : new long[] {0, RandomStreamsTestHelper.createSeed(ThreadLocalGenerator.INSTANCE)}) {
196 final SplittableUniformRandomProvider source = new SplittableUniformRandomProvider() {
197 @Override
198 public long nextLong() {
199 return seed;
200 }
201
202 @Override
203 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
204 return this;
205 }
206 };
207
208 Assertions.assertEquals(seed | 1, RandomStreamsTestHelper.createSeed(source));
209
210 Stream<SplittableUniformRandomProvider> stream = generator.splits(size, source);
211 Assertions.assertFalse(stream.isParallel(), "Initial stream should be sequential");
212 if (parallel) {
213 stream = stream.parallel();
214 Assertions.assertTrue(stream.isParallel(), "Stream should be parallel");
215 }
216
217
218
219 final Set<SplittableUniformRandomProvider> observed = ConcurrentHashMap.newKeySet();
220 observed.add(generator);
221 stream.forEach(r -> {
222 Assertions.assertTrue(observed.add(r), "Instance should be unique");
223 Assertions.assertEquals(generator.getClass(), r.getClass());
224 });
225
226 Assertions.assertEquals(size, observed.size() - 1);
227
228
229
230 final long[] values = observed.stream().mapToLong(r -> {
231
232
233 for (int i = 0; i < 10; i++) {
234 r.nextLong();
235 }
236 return r.nextLong();
237 }).distinct().toArray();
238
239
240
241
242 Assertions.assertTrue(values.length > size / 2,
243 () -> "splits did not seed randomness from the stream position. Initial seed = " + seed);
244 }
245 }
246
247
248
249
250
251
252
253
254
255
256 private static void failSpliteratorShouldBeEmpty() {
257 Assertions.fail("Spliterator should not have any remaining elements");
258 }
259
260 @ParameterizedTest
261 @MethodSource("getSplittableProviders")
262 void testSplitsInvalidStreamSizeThrows(SplittableUniformRandomProvider rng) {
263 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.splits(-1), "splits(size)");
264 final SplittableUniformRandomProvider source = DummyGenerator.INSTANCE;
265 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.splits(-1, source), "splits(size, source)");
266 }
267
268 @ParameterizedTest
269 @MethodSource("getSplittableProviders")
270 void testSplitsUnlimitedStreamSize(SplittableUniformRandomProvider rng) {
271 assertUnlimitedSpliterator(rng.splits().spliterator(), "splits()");
272 final SplittableUniformRandomProvider source = ThreadLocalGenerator.INSTANCE;
273 assertUnlimitedSpliterator(rng.splits(source).spliterator(), "splits(source)");
274 }
275
276
277
278
279
280
281
282
283 private static void assertUnlimitedSpliterator(Spliterator<?> spliterator, String msg) {
284 Assertions.assertEquals(Long.MAX_VALUE, spliterator.estimateSize(), msg);
285 Assertions.assertTrue(spliterator.hasCharacteristics(SPLITERATOR_CHARACTERISTICS),
286 () -> String.format("%s: characteristics = %s, expected %s", msg,
287 Integer.toBinaryString(spliterator.characteristics()),
288 Integer.toBinaryString(SPLITERATOR_CHARACTERISTICS)
289 ));
290 }
291
292 @ParameterizedTest
293 @MethodSource("getSplittableProviders")
294 void testSplitsNullSourceThrows(SplittableUniformRandomProvider rng) {
295 final SplittableUniformRandomProvider source = null;
296 Assertions.assertThrows(NullPointerException.class, () -> rng.splits(source));
297 Assertions.assertThrows(NullPointerException.class, () -> rng.splits(1, source));
298 }
299
300 @ParameterizedTest
301 @MethodSource("getSplittableProviders")
302 void testSplitsSpliterator(SplittableUniformRandomProvider rng) {
303
304
305 final long size = 41;
306 Spliterator<SplittableUniformRandomProvider> s1 = rng.splits(size).spliterator();
307 Assertions.assertEquals(size, s1.estimateSize());
308 final Spliterator<SplittableUniformRandomProvider> s2 = s1.trySplit();
309 final Spliterator<SplittableUniformRandomProvider> s3 = s1.trySplit();
310 final Spliterator<SplittableUniformRandomProvider> s4 = s2.trySplit();
311 Assertions.assertEquals(size, s1.estimateSize() + s2.estimateSize() + s3.estimateSize() + s4.estimateSize());
312
313
314 while (s1.estimateSize() > 1) {
315 final long currentSize = s1.estimateSize();
316 final Spliterator<SplittableUniformRandomProvider> other = s1.trySplit();
317 Assertions.assertEquals(currentSize, s1.estimateSize() + other.estimateSize());
318 s1 = other;
319 }
320 Assertions.assertNull(s1.trySplit(), "Cannot split when size <= 1");
321
322
323
324 final HashSet<SplittableUniformRandomProvider> observed = new HashSet<>();
325 observed.add(rng);
326
327 final Consumer<SplittableUniformRandomProvider> action = r -> {
328 Assertions.assertTrue(observed.add(r), "Instance should be unique");
329 Assertions.assertEquals(rng.getClass(), r.getClass());
330 };
331
332
333 for (long newSize = s2.estimateSize(); newSize-- > 0;) {
334 Assertions.assertTrue(s2.tryAdvance(action));
335 Assertions.assertEquals(newSize, s2.estimateSize(), "s2 size estimate");
336 }
337 Assertions.assertFalse(s2.tryAdvance(r -> failSpliteratorShouldBeEmpty()));
338 s2.forEachRemaining(r -> failSpliteratorShouldBeEmpty());
339
340
341 s3.forEachRemaining(action);
342 Assertions.assertEquals(0, s3.estimateSize());
343 s3.forEachRemaining(r -> failSpliteratorShouldBeEmpty());
344
345
346 final IllegalStateException ex = new IllegalStateException();
347 final Consumer<SplittableUniformRandomProvider> badAction = r -> {
348 throw ex;
349 };
350 final long currentSize = s4.estimateSize();
351 Assertions.assertTrue(currentSize > 1, "Spliterator requires more elements to test advance");
352 Assertions.assertSame(ex, Assertions.assertThrows(IllegalStateException.class, () -> s4.tryAdvance(badAction)));
353 Assertions.assertEquals(currentSize - 1, s4.estimateSize(), "Spliterator should be advanced even when action throws");
354
355 Assertions.assertSame(ex, Assertions.assertThrows(IllegalStateException.class, () -> s4.forEachRemaining(badAction)));
356 Assertions.assertEquals(0, s4.estimateSize(), "Spliterator should be finished even when action throws");
357 s4.forEachRemaining(r -> failSpliteratorShouldBeEmpty());
358 }
359 }