Skip to content

Commit

Permalink
WIP: use L128X1024Mix for random generator
Browse files Browse the repository at this point in the history
fixes #363
  • Loading branch information
Vladimir Sitnikov committed Dec 10, 2022
1 parent 7a37d8d commit 75a0edb
Show file tree
Hide file tree
Showing 146 changed files with 1,033 additions and 844 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ Use _jqwik_ itself for all tests and properties.

Use _AssertJ_ for non trivial assertions.

Use `@ForAll Random random` parameter if you need a random value.
Use `@ForAll JqwikRandom random` parameter if you need a random value.
2 changes: 2 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ signing {
dependencies {
api("org.opentest4j:opentest4j:${opentest4jVersion}")
api("org.junit.platform:junit-platform-commons:${junitPlatformVersion}")
api(platform("org.apache.commons:commons-rng-bom:1.5"))
api("org.apache.commons:commons-rng-core")
}
7 changes: 3 additions & 4 deletions api/src/main/java/net/jqwik/api/Arbitraries.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import javax.annotation.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import org.apiguardian.api.*;

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

Expand All @@ -121,8 +120,8 @@ public static <T> Arbitrary<T> randomValue(Function<Random, T> generator) {
*
* @return a new arbitrary instance
*/
public static Arbitrary<Random> randoms() {
return randomValue(random -> new Random(random.nextLong()));
public static Arbitrary<JqwikRandom> randoms() {
return randomValue(JqwikRandom::split);
}

/**
Expand Down
37 changes: 37 additions & 0 deletions api/src/main/java/net/jqwik/api/JqwikRandom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.jqwik.api;

import net.jqwik.api.random.*;

import org.apache.commons.rng.*;

import java.util.*;

public interface JqwikRandom extends UniformRandomProvider {
JqwikRandom jump();

default JqwikRandom split() {
return split(this);
}

JqwikRandom split(UniformRandomProvider source);

JqwikRandomState saveState();

void restoreState(JqwikRandomState state);

default Random asJdkRandom() {
return new Random() {
@Override
protected int next(int bits) {
int next = JqwikRandom.this.nextInt();
next &= ((1L << bits) - 1);
return next;
}

@Override
public long nextLong() {
return JqwikRandom.this.nextLong();
}
};
}
}
3 changes: 1 addition & 2 deletions api/src/main/java/net/jqwik/api/RandomDistribution.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.jqwik.api;

import java.math.*;
import java.util.*;

import org.apiguardian.api.*;

Expand Down Expand Up @@ -61,7 +60,7 @@ interface RandomNumericGenerator {
*
* @return an instance of BigInteger. Never {@code null}.
*/
BigInteger next(Random random);
BigInteger next(JqwikRandom random);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions api/src/main/java/net/jqwik/api/RandomGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public abstract <T, U> Shrinkable<U> flatMap(
* @param random the source of randomness. Injected by jqwik itself.
* @return the next generated value wrapped within the Shrinkable interface. The method must ALWAYS return a next value.
*/
Shrinkable<T> next(Random random);
Shrinkable<T> next(JqwikRandom random);

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package net.jqwik.api.facades;

import java.util.*;

import org.apiguardian.api.*;

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

public abstract <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, Random random, Falsifier<T> falsifier);
public abstract <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier);

public abstract <T> T falsifyThenShrink(RandomGenerator<? extends T> arbitrary, Random random, Falsifier<T> falsifier);
public abstract <T> T falsifyThenShrink(RandomGenerator<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier);

public abstract <T> T shrink(
Shrinkable<T> falsifiedShrinkable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public abstract class TestingSupportFacade {
implementation = FacadeLoader.load(TestingSupportFacade.class);
}

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

public abstract String singleLineReport(Object any);

Expand Down
4 changes: 4 additions & 0 deletions api/src/main/java/net/jqwik/api/random/JqwikRandomSeed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.jqwik.api.random;

public interface JqwikRandomSeed {
}
4 changes: 4 additions & 0 deletions api/src/main/java/net/jqwik/api/random/JqwikRandomState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.jqwik.api.random;

public interface JqwikRandomState {
}
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ The starting point for generation usually is a static method call on class
return Arbitraries.randomValue(random -> generatePrime(random));
}

private Integer generatePrime(Random random) {
private Integer generatePrime(JqwikRandom random) {
int candidate;
do {
candidate = random.nextInt(10000) + 2;
Expand Down Expand Up @@ -309,7 +309,7 @@ Arbitraries.strings().ofMinLength(5).ofMaxLength(25)

#### java.util.Random

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

#### Shuffling Permutations
Expand Down
4 changes: 2 additions & 2 deletions documentation/src/docs/include/lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ and publish the result using a [`Reporter`](/docs/${docsVersion}/javadoc/net/jqw
```java
@Property(tries = 100)
@AddLifecycleHook(MeasureTime.class)
void measureTimeSpent(@ForAll Random random) throws InterruptedException {
void measureTimeSpent(@ForAll JqwikRandom random) throws InterruptedException {
Thread.sleep(random.nextInt(50));
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void countingTries(@ForAll String aString) {

@Property(tries = 100)
@AddLifecycleHook(MeasureTime.class)
void measureTimeSpent(@ForAll Random random) throws InterruptedException {
void measureTimeSpent(@ForAll JqwikRandom random) throws InterruptedException {
Thread.sleep(random.nextInt(50));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AroundTryHookExamples {

@Property(tries = 10)
@AddLifecycleHook(FailIfTooSlow.class)
void sleepingProperty(@ForAll Random random) throws InterruptedException {
void sleepingProperty(@ForAll JqwikRandom random) throws InterruptedException {
Thread.sleep(random.nextInt(101));
}

Expand Down
100 changes: 39 additions & 61 deletions engine/src/main/java/net/jqwik/engine/SourceOfRandomness.java
Original file line number Diff line number Diff line change
@@ -1,91 +1,69 @@
package net.jqwik.engine;

import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.math.*;
import java.security.*;

import net.jqwik.api.*;

import net.jqwik.api.random.*;

import net.jqwik.engine.random.*;

import org.apache.commons.rng.*;
import org.apache.commons.rng.core.*;
import org.apache.commons.rng.core.source64.*;

public class SourceOfRandomness {

private SourceOfRandomness() {
}

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

private static final ThreadLocal<Random> current = ThreadLocal.withInitial(SourceOfRandomness::newRandom);
private static final ThreadLocal<JqwikRandom> current = ThreadLocal.withInitial(SourceOfRandomness::newRandom);

public static String encodeSeed(RandomProviderState state) {
return new BigInteger(((RandomProviderDefaultState) state).getState()).toString(36);
}

public static RandomProviderState decodeSeed(String seed) {
return new RandomProviderDefaultState(new BigInteger(seed, 36).toByteArray());
}

public static String createRandomSeed() {
return Long.toString(RNG.get().nextLong());
synchronized (RNG) {
RNG.jump();
return encodeSeed(RNG.saveState());
}
}

public static Random create(String seed) {
public static JqwikRandom create(String seed) {
try {
Random random = newRandom(Long.parseLong(seed));
L128X1024Mix core = new L128X1024Mix(new long[0]);
core.restoreState(decodeSeed(seed));
JqwikRandom random = new JqwikRandomImpl(core);
current.set(random);
return random;
} catch (NumberFormatException nfe) {
throw new JqwikException(String.format("[%s] is not a valid random seed.", seed));
}
}

public static Random newRandom() {
return new XORShiftRandom();
public static JqwikRandom newRandom() {
return new JqwikRandomImpl(new L128X1024Mix(new long[]{ System.nanoTime()}));
}

public static Random newRandom(final long seed) {
return new XORShiftRandom(seed);
@Deprecated
public static JqwikRandom newRandom(final long seed) {
return new JqwikRandomImpl(new L128X1024Mix(new long[]{seed}));
}

public static Random current() {
return current.get();
public static JqwikRandom newRandom(final JqwikRandomState seed) {
JqwikRandomImpl random = new JqwikRandomImpl(new L128X1024Mix(new long[]{1}));
random.restoreState(seed);
return random;
}

/**
* A faster but not thread safe implementation of {@linkplain java.util.Random}.
* It also has a period of 2^n - 1 and better statistical randomness.
*
* See for details: https://www.javamex.com/tutorials/random_numbers/xorshift.shtml
*
* <p>
* For further performance improvements within jqwik, consider to override:
* <ul>
* <li>nextDouble()</li>
* <li>nextBytes(int)</li>
* </ul>
*/
private static class XORShiftRandom extends Random {
private long seed;

private XORShiftRandom() {
this(System.nanoTime());
}

private XORShiftRandom(long seed) {
if (seed == 0l) {
throw new IllegalArgumentException("0L is not an allowed seed value");
}
this.seed = seed;
}

@Override
protected int next(int nbits) {
long x = nextLong();
x &= ((1L << nbits) - 1);
return (int) x;
}

/**
* Will never generate 0L
*/
@Override
public long nextLong() {
long x = this.seed;
x ^= (x << 21);
x ^= (x >>> 35);
x ^= (x << 4);
this.seed = x;
return x;
}
public static JqwikRandom current() {
return current.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private ForAllParametersGenerator createDataBasedShrinkablesGenerator(PropertyCo
}

private ForAllParametersGenerator createRandomizedShrinkablesGenerator(PropertyConfiguration configuration) {
Random random = SourceOfRandomness.create(configuration.getSeed());
JqwikRandom random = SourceOfRandomness.create(configuration.getSeed());
return RandomizedShrinkablesGenerator.forParameters(
forAllParameters,
arbitraryResolver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ public class ShrinkingSupportFacadeImpl extends ShrinkingSupportFacade {
private final TestingSupportFacadeImpl testingSupportFacade = new TestingSupportFacadeImpl();

@Override
public <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, Random random, Falsifier<T> falsifier) {
public <T> T falsifyThenShrink(Arbitrary<? extends T> arbitrary, JqwikRandom random, Falsifier<T> falsifier) {
RandomGenerator<? extends T> generator = arbitrary.generator(10, true);
return falsifyThenShrink(generator, random, falsifier);
}

@Override
@SuppressWarnings("unchecked")
public <T> T falsifyThenShrink(
RandomGenerator<? extends T> generator,
Random random,
Falsifier<T> falsifier
RandomGenerator<? extends T> generator,
JqwikRandom random,
Falsifier<T> falsifier
) {
Throwable[] originalError = new Throwable[1];
Shrinkable<T> falsifiedShrinkable =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class TestingSupportFacadeImpl extends TestingSupportFacade {

@Override
public <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, Random random, Function<T, Boolean> condition) {
public <T> Shrinkable<T> generateUntil(RandomGenerator<T> generator, JqwikRandom random, Function<T, Boolean> condition) {
long maxTries = 1000;
return generator
.stream(random)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class PurelyRandomShrinkablesGenerator {
this.parameterGenerators = parameterGenerators;
}

List<Shrinkable<Object>> generateNext(Random random) {
List<Shrinkable<Object>> generateNext(JqwikRandom random) {
Map<TypeUsage, Arbitrary<Object>> generatorsCache = new LinkedHashMap<>();
return parameterGenerators
.stream()
Expand Down
Loading

0 comments on commit 75a0edb

Please sign in to comment.