Skip to content

Commit 75a0edb

Browse files
author
Vladimir Sitnikov
committed
WIP: use L128X1024Mix for random generator
fixes #363
1 parent 7a37d8d commit 75a0edb

File tree

146 files changed

+1033
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+1033
-844
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ Use _jqwik_ itself for all tests and properties.
3737

3838
Use _AssertJ_ for non trivial assertions.
3939

40-
Use `@ForAll Random random` parameter if you need a random value.
40+
Use `@ForAll JqwikRandom random` parameter if you need a random value.

api/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@ signing {
5353
dependencies {
5454
api("org.opentest4j:opentest4j:${opentest4jVersion}")
5555
api("org.junit.platform:junit-platform-commons:${junitPlatformVersion}")
56+
api(platform("org.apache.commons:commons-rng-bom:1.5"))
57+
api("org.apache.commons:commons-rng-core")
5658
}

api/src/main/java/net/jqwik/api/Arbitraries.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import javax.annotation.*;
44
import java.util.*;
55
import java.util.function.*;
6-
import java.util.stream.*;
76

87
import org.apiguardian.api.*;
98

@@ -112,7 +111,7 @@ public static <T> Arbitrary<T> fromGenerator(RandomGenerator<T> generator) {
112111
* @param <T> The type of values to generate
113112
* @return a new arbitrary instance
114113
*/
115-
public static <T> Arbitrary<T> randomValue(Function<Random, T> generator) {
114+
public static <T> Arbitrary<T> randomValue(Function<JqwikRandom, T> generator) {
116115
return fromGenerator(random -> Shrinkable.unshrinkable(generator.apply(random)));
117116
}
118117

@@ -121,8 +120,8 @@ public static <T> Arbitrary<T> randomValue(Function<Random, T> generator) {
121120
*
122121
* @return a new arbitrary instance
123122
*/
124-
public static Arbitrary<Random> randoms() {
125-
return randomValue(random -> new Random(random.nextLong()));
123+
public static Arbitrary<JqwikRandom> randoms() {
124+
return randomValue(JqwikRandom::split);
126125
}
127126

128127
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package net.jqwik.api;
2+
3+
import net.jqwik.api.random.*;
4+
5+
import org.apache.commons.rng.*;
6+
7+
import java.util.*;
8+
9+
public interface JqwikRandom extends UniformRandomProvider {
10+
JqwikRandom jump();
11+
12+
default JqwikRandom split() {
13+
return split(this);
14+
}
15+
16+
JqwikRandom split(UniformRandomProvider source);
17+
18+
JqwikRandomState saveState();
19+
20+
void restoreState(JqwikRandomState state);
21+
22+
default Random asJdkRandom() {
23+
return new Random() {
24+
@Override
25+
protected int next(int bits) {
26+
int next = JqwikRandom.this.nextInt();
27+
next &= ((1L << bits) - 1);
28+
return next;
29+
}
30+
31+
@Override
32+
public long nextLong() {
33+
return JqwikRandom.this.nextLong();
34+
}
35+
};
36+
}
37+
}

api/src/main/java/net/jqwik/api/RandomDistribution.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.jqwik.api;
22

33
import java.math.*;
4-
import java.util.*;
54

65
import org.apiguardian.api.*;
76

@@ -61,7 +60,7 @@ interface RandomNumericGenerator {
6160
*
6261
* @return an instance of BigInteger. Never {@code null}.
6362
*/
64-
BigInteger next(Random random);
63+
BigInteger next(JqwikRandom random);
6564
}
6665

6766
/**

api/src/main/java/net/jqwik/api/RandomGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public abstract <T, U> Shrinkable<U> flatMap(
4444
* @param random the source of randomness. Injected by jqwik itself.
4545
* @return the next generated value wrapped within the Shrinkable interface. The method must ALWAYS return a next value.
4646
*/
47-
Shrinkable<T> next(Random random);
47+
Shrinkable<T> next(JqwikRandom random);
4848

4949
@API(status = INTERNAL)
5050
default <U> RandomGenerator<U> map(Function<T, U> mapper) {
@@ -87,7 +87,7 @@ default RandomGenerator<T> withEdgeCases(int genSize, EdgeCases<T> edgeCases) {
8787
}
8888

8989
@API(status = INTERNAL)
90-
default Stream<Shrinkable<T>> stream(Random random) {
90+
default Stream<Shrinkable<T>> stream(JqwikRandom random) {
9191
return Stream.generate(() -> this.next(random));
9292
}
9393

api/src/main/java/net/jqwik/api/facades/ShrinkingSupportFacade.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package net.jqwik.api.facades;
22

3-
import java.util.*;
4-
53
import org.apiguardian.api.*;
64

75
import net.jqwik.api.*;
@@ -17,9 +15,9 @@ public abstract class ShrinkingSupportFacade {
1715
implementation = FacadeLoader.load(ShrinkingSupportFacade.class);
1816
}
1917

20-
public abstract <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, Random random, Falsifier<T> falsifier);
18+
public abstract <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier);
2119

22-
public abstract <T> T falsifyThenShrink(RandomGenerator<? extends T> arbitrary, Random random, Falsifier<T> falsifier);
20+
public abstract <T> T falsifyThenShrink(RandomGenerator<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier);
2321

2422
public abstract <T> T shrink(
2523
Shrinkable<T> falsifiedShrinkable,

api/src/main/java/net/jqwik/api/facades/TestingSupportFacade.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public abstract class TestingSupportFacade {
1313
implementation = FacadeLoader.load(TestingSupportFacade.class);
1414
}
1515

16-
public abstract <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, Random random, Function<T, Boolean> condition);
16+
public abstract <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, JqwikRandom random, Function<T, Boolean> condition);
1717

1818
public abstract String singleLineReport(Object any);
1919

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package net.jqwik.api.random;
2+
3+
public interface JqwikRandomSeed {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package net.jqwik.api.random;
2+
3+
public interface JqwikRandomState {
4+
}

documentation/src/docs/include/customized-parameter-generation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ The starting point for generation usually is a static method call on class
195195
return Arbitraries.randomValue(random -> generatePrime(random));
196196
}
197197

198-
private Integer generatePrime(Random random) {
198+
private Integer generatePrime(JqwikRandom random) {
199199
int candidate;
200200
do {
201201
candidate = random.nextInt(10000) + 2;
@@ -309,7 +309,7 @@ Arbitraries.strings().ofMinLength(5).ofMaxLength(25)
309309

310310
#### java.util.Random
311311

312-
- [`Arbitrary<Random> randoms()`](/docs/${docsVersion}/javadoc/net/jqwik/api/Arbitraries.html#randoms()):
312+
- [`Arbitrary<JqwikRandom> randoms()`](/docs/${docsVersion}/javadoc/net/jqwik/api/Arbitraries.html#randoms()):
313313
Random instances will never be shrunk
314314

315315
#### Shuffling Permutations

documentation/src/docs/include/lifecycle-hooks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ and publish the result using a [`Reporter`](/docs/${docsVersion}/javadoc/net/jqw
189189
```java
190190
@Property(tries = 100)
191191
@AddLifecycleHook(MeasureTime.class)
192-
void measureTimeSpent(@ForAll Random random) throws InterruptedException {
192+
void measureTimeSpent(@ForAll JqwikRandom random) throws InterruptedException {
193193
Thread.sleep(random.nextInt(50));
194194
}
195195

@@ -228,7 +228,7 @@ The following example shows how to fail if a single try will take longer than 10
228228
```java
229229
@Property(tries = 10)
230230
@AddLifecycleHook(FailIfTooSlow.class)
231-
void sleepingProperty(@ForAll Random random) throws InterruptedException {
231+
void sleepingProperty(@ForAll JqwikRandom random) throws InterruptedException {
232232
Thread.sleep(random.nextInt(101));
233233
}
234234

documentation/src/test/java/net/jqwik/docs/lifecycle/AroundPropertyHookExamples.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void countingTries(@ForAll String aString) {
3838

3939
@Property(tries = 100)
4040
@AddLifecycleHook(MeasureTime.class)
41-
void measureTimeSpent(@ForAll Random random) throws InterruptedException {
41+
void measureTimeSpent(@ForAll JqwikRandom random) throws InterruptedException {
4242
Thread.sleep(random.nextInt(50));
4343
}
4444

documentation/src/test/java/net/jqwik/docs/lifecycle/AroundTryHookExamples.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class AroundTryHookExamples {
1111

1212
@Property(tries = 10)
1313
@AddLifecycleHook(FailIfTooSlow.class)
14-
void sleepingProperty(@ForAll Random random) throws InterruptedException {
14+
void sleepingProperty(@ForAll JqwikRandom random) throws InterruptedException {
1515
Thread.sleep(random.nextInt(101));
1616
}
1717

Lines changed: 39 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,69 @@
11
package net.jqwik.engine;
22

3-
import java.util.*;
4-
import java.util.concurrent.*;
5-
import java.util.function.*;
3+
import java.math.*;
4+
import java.security.*;
65

76
import net.jqwik.api.*;
87

8+
import net.jqwik.api.random.*;
9+
10+
import net.jqwik.engine.random.*;
11+
12+
import org.apache.commons.rng.*;
13+
import org.apache.commons.rng.core.*;
14+
import org.apache.commons.rng.core.source64.*;
15+
916
public class SourceOfRandomness {
1017

1118
private SourceOfRandomness() {
1219
}
1320

14-
private static final Supplier<Random> RNG = ThreadLocalRandom::current;
21+
private static final L128X1024Mix RNG = new L128X1024Mix(new SecureRandom().longs().limit(20).toArray());
1522

16-
private static final ThreadLocal<Random> current = ThreadLocal.withInitial(SourceOfRandomness::newRandom);
23+
private static final ThreadLocal<JqwikRandom> current = ThreadLocal.withInitial(SourceOfRandomness::newRandom);
1724

25+
public static String encodeSeed(RandomProviderState state) {
26+
return new BigInteger(((RandomProviderDefaultState) state).getState()).toString(36);
27+
}
28+
29+
public static RandomProviderState decodeSeed(String seed) {
30+
return new RandomProviderDefaultState(new BigInteger(seed, 36).toByteArray());
31+
}
32+
1833
public static String createRandomSeed() {
19-
return Long.toString(RNG.get().nextLong());
34+
synchronized (RNG) {
35+
RNG.jump();
36+
return encodeSeed(RNG.saveState());
37+
}
2038
}
2139

22-
public static Random create(String seed) {
40+
public static JqwikRandom create(String seed) {
2341
try {
24-
Random random = newRandom(Long.parseLong(seed));
42+
L128X1024Mix core = new L128X1024Mix(new long[0]);
43+
core.restoreState(decodeSeed(seed));
44+
JqwikRandom random = new JqwikRandomImpl(core);
2545
current.set(random);
2646
return random;
2747
} catch (NumberFormatException nfe) {
2848
throw new JqwikException(String.format("[%s] is not a valid random seed.", seed));
2949
}
3050
}
3151

32-
public static Random newRandom() {
33-
return new XORShiftRandom();
52+
public static JqwikRandom newRandom() {
53+
return new JqwikRandomImpl(new L128X1024Mix(new long[]{ System.nanoTime()}));
3454
}
3555

36-
public static Random newRandom(final long seed) {
37-
return new XORShiftRandom(seed);
56+
@Deprecated
57+
public static JqwikRandom newRandom(final long seed) {
58+
return new JqwikRandomImpl(new L128X1024Mix(new long[]{seed}));
3859
}
39-
40-
public static Random current() {
41-
return current.get();
60+
public static JqwikRandom newRandom(final JqwikRandomState seed) {
61+
JqwikRandomImpl random = new JqwikRandomImpl(new L128X1024Mix(new long[]{1}));
62+
random.restoreState(seed);
63+
return random;
4264
}
4365

44-
/**
45-
* A faster but not thread safe implementation of {@linkplain java.util.Random}.
46-
* It also has a period of 2^n - 1 and better statistical randomness.
47-
*
48-
* See for details: https://www.javamex.com/tutorials/random_numbers/xorshift.shtml
49-
*
50-
* <p>
51-
* For further performance improvements within jqwik, consider to override:
52-
* <ul>
53-
* <li>nextDouble()</li>
54-
* <li>nextBytes(int)</li>
55-
* </ul>
56-
*/
57-
private static class XORShiftRandom extends Random {
58-
private long seed;
59-
60-
private XORShiftRandom() {
61-
this(System.nanoTime());
62-
}
63-
64-
private XORShiftRandom(long seed) {
65-
if (seed == 0l) {
66-
throw new IllegalArgumentException("0L is not an allowed seed value");
67-
}
68-
this.seed = seed;
69-
}
70-
71-
@Override
72-
protected int next(int nbits) {
73-
long x = nextLong();
74-
x &= ((1L << nbits) - 1);
75-
return (int) x;
76-
}
77-
78-
/**
79-
* Will never generate 0L
80-
*/
81-
@Override
82-
public long nextLong() {
83-
long x = this.seed;
84-
x ^= (x << 21);
85-
x ^= (x >>> 35);
86-
x ^= (x << 4);
87-
this.seed = x;
88-
return x;
89-
}
66+
public static JqwikRandom current() {
67+
return current.get();
9068
}
9169
}

engine/src/main/java/net/jqwik/engine/execution/CheckedProperty.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private ForAllParametersGenerator createDataBasedShrinkablesGenerator(PropertyCo
232232
}
233233

234234
private ForAllParametersGenerator createRandomizedShrinkablesGenerator(PropertyConfiguration configuration) {
235-
Random random = SourceOfRandomness.create(configuration.getSeed());
235+
JqwikRandom random = SourceOfRandomness.create(configuration.getSeed());
236236
return RandomizedShrinkablesGenerator.forParameters(
237237
forAllParameters,
238238
arbitraryResolver,

engine/src/main/java/net/jqwik/engine/facades/ShrinkingSupportFacadeImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ public class ShrinkingSupportFacadeImpl extends ShrinkingSupportFacade {
1515
private final TestingSupportFacadeImpl testingSupportFacade = new TestingSupportFacadeImpl();
1616

1717
@Override
18-
public <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, Random random, Falsifier<T> falsifier) {
18+
public <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier) {
1919
RandomGenerator<? extends T> generator = arbitrary.generator(10, true);
2020
return falsifyThenShrink(generator, random, falsifier);
2121
}
2222

2323
@Override
2424
@SuppressWarnings("unchecked")
2525
public <T> T falsifyThenShrink(
26-
RandomGenerator<? extends T> generator,
27-
Random random,
28-
Falsifier<T> falsifier
26+
RandomGenerator<? extends T> generator,
27+
JqwikRandom random,
28+
Falsifier<T> falsifier
2929
) {
3030
Throwable[] originalError = new Throwable[1];
3131
Shrinkable<T> falsifiedShrinkable =

engine/src/main/java/net/jqwik/engine/facades/TestingSupportFacadeImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
public class TestingSupportFacadeImpl extends TestingSupportFacade {
1111

1212
@Override
13-
public <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, Random random, Function<T, Boolean> condition) {
13+
public <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, JqwikRandom random, Function<T, Boolean> condition) {
1414
long maxTries = 1000;
1515
return generator
1616
.stream(random)

engine/src/main/java/net/jqwik/engine/properties/PurelyRandomShrinkablesGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PurelyRandomShrinkablesGenerator {
1414
this.parameterGenerators = parameterGenerators;
1515
}
1616

17-
List<Shrinkable<Object>> generateNext(Random random) {
17+
List<Shrinkable<Object>> generateNext(JqwikRandom random) {
1818
Map<TypeUsage, Arbitrary<Object>> generatorsCache = new LinkedHashMap<>();
1919
return parameterGenerators
2020
.stream()

0 commit comments

Comments
 (0)