8346107: Generators: testing utility for random value generation

Co-authored-by: Emanuel Peter <epeter@openjdk.org>
Reviewed-by: epeter, chagedorn
This commit is contained in:
Theo Weidmann 2025-01-16 12:17:21 +00:00 committed by Christian Hagedorn
parent 4ad5d5514e
commit f64f22b360
23 changed files with 2292 additions and 0 deletions

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides an any-bits double distribution random generator, i.e. the bits are uniformly sampled,
* thus creating any possible double value, including the multiple different NaN representations.
*/
final class AnyBitsDoubleGenerator extends BoundGenerator<Double> {
/**
* Create a new {@link AnyBitsDoubleGenerator}.
*/
public AnyBitsDoubleGenerator(Generators g) {
super(g);
}
@Override
public Double next() {
return Double.longBitsToDouble(g.random.nextLong());
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides an any-bits float distribution random generator, i.e. the bits are uniformly sampled,
* thus creating any possible float value, including the multiple different NaN representations.
*/
final class AnyBitsFloatGenerator extends BoundGenerator<Float> {
/**
* Creates a new {@link AnyBitsFloatGenerator}.
*/
public AnyBitsFloatGenerator(Generators g) {
super(g);
}
@Override
public Float next() {
return Float.intBitsToFloat(g.random.nextInt());
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* This is a common superclass for all generators that maintain a reference to the Generators object that created them.
* This allows internally creating other generators or using the {@link RandomnessSource} provided in
* {@link Generators#random}.
*/
abstract class BoundGenerator<T> implements Generator<T> {
Generators g;
BoundGenerator(Generators g) {
this.g = g;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* An EmptyGeneratorException is thrown if a generator configuration is requested that would result in an empty
* set of values. For example, bounds such as [1, 0] cause an EmptyGeneratorException. Another example would be
* restricting a uniform integer generator over the range [0, 1] to [10, 11].
*/
public class EmptyGeneratorException extends RuntimeException {
public EmptyGeneratorException() {}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* A stream of values according to a specific distribution.
*/
public interface Generator<T> {
/**
* Returns the next value from the stream.
*/
T next();
}

View File

@ -0,0 +1,584 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.*;
import jdk.test.lib.Utils;
/**
* The Generators class provides a set of random generator functions for testing.
* The goal is to cover many special cases, such as NaNs in Floats or values
* close to overflow in ints. They should produce values from specific
* "interesting" distributions which might trigger various behaviours in
* optimizations.
* <p>
* Normally, clients get the default Generators instance by referring to the static variable {@link #G}.
* <p>
* The Generators class offers generators with essential distributions, for example, {@link #uniformInts(int, int)},
* {@link #uniformLongs(long, long)}, {@link #uniformDoubles(double, double)} or {@link #uniformFloats()}. For floating
* points, you may choose to get random bit patterns uniformly at random, rather than the values they represent.
* The Generators class also offers special generators of interesting values such as {@link #powerOfTwoInts(int)},
* {@link #powerOfTwoLongs(int)}, which are values close to the powers of 2, or {@link #SPECIAL_DOUBLES} and
* {@link #SPECIAL_FLOATS}, which are values such as infinity, NaN, zero or the maximum and minimum values.
* <p>
* Many distributions are <i>restrictable</i>. For example, if you first create a uniform integer generator over [1, 10],
* you can obtain a new generator by further restricting this range to [1, 5]. This is useful in cases where a function
* should be tested with different distributions. For example, a function <code>h(int, int, int)</code> under test might
* be worthwhile to test not only with uniformly sampled integers but might also exhibit interesting behavior if tested
* specifically with powers of two. Suppose further that each argument has a different range of allowed values. We
* can write a test function as below:
*
* <pre><code>
* void test(Generator{@literal <Integer>} g) {
* h(g.restricted(1, 10).next(), g.next(), g.restricted(-10, 100).next());
* }
* </code></pre>
*
* Then <code>test</code> can be called with different distributions, for example:
*
* <pre><code>
* test(G.uniformInts());
* test(G.specialInts(0));
* </code></pre>
* <p>
* If there is a single value that is interesting as an argument to all three parameters, we might even call this
* method with a single generator, ensuring that the single value is within the restriction ranges:
*
* <pre><code>
* test(G.single(1));
* </code></pre>
*
* <p>
* Furthermore, this class offers utility generators, such as {@link #randomElement(Collection)} or
* {@link #orderedRandomElement(Collection)} for sampling from a list of elements; {@link #single(Object)} for a
* generator that only produces a single value; and {@link #mixed(Generator, Generator, int, int)} which combines
* two generators with the provided weights.
* <p>
* Thus, the generators provided by this class are composable and therefore extensible. This allows to easily
* create random generators even with types and distributions that are not predefined. For example, to create a
* generator that provides true with 60 percent probably and false with 40 percent probably, one can simply write:
* <pre><code>G.mixed(G.single(true), G.single(false), 60, 40)</code></pre>
* <p>
* Generators are by no means limited to work with numbers. Restrictable generators can work with any type that
* implements {@link Comparable} while generators such as {@link #randomElement(Collection)} and {@link #single(Object)}
* work with any type. Note that there are separate restrictable versions of the last two generators
* (namely, {@link #orderedRandomElement(Collection)} and {@link #single(Comparable)}) that work with comparable types.
* For example, you might restrict a generator choosing strings at random:
* <pre><code>G.orderedRandomElement(List.of("Bob", "Alice", "Carol")).restricted("Al", "Bz")</code></pre>
* This returns a new generator which only returns elements greater or equal than "Al" and less than or equal to
* "Bz". Thus, the only two values remaining in the example are "Alice" and "Bob". In general, you should always refer
* to the method that created the generator to learn about the exact semantics of restricting it.
* <p>
* For all the generators created by instances of this class, the following rule applies: Integral generators are
* always inclusive of both the lower and upper bound, while floating point generators are always inclusive of the
* lower bound but always exclusive of the upper bound. This also applies to all generators obtained by restricting
* these generators further.
* <p>
* Unless you have reasons to pick a specific distribution, you are encouraged to rely on {@link #ints()},
* {@link #longs()}, {@link #doubles()} and {@link #floats()}, which will randomly pick an interesting distribution.
* This is best practice, because that allows the test to be run under different conditions maybe only a single
* distribution can trigger a bug.
*/
public final class Generators {
/**
* This is the default Generators instance that should be used by tests normally.
*/
public static final Generators G = new Generators(new RandomnessSourceAdapter(Utils.getRandomInstance()));
final RandomnessSource random;
public Generators(RandomnessSource random) {
this.random = random;
}
/**
* Returns a generator that generates integers in the range [lo, hi] (inclusive of both lo and hi).
*/
public RestrictableGenerator<Integer> uniformInts(int lo, int hi) {
return new UniformIntGenerator(this, lo, hi);
}
/**
* Returns a generator that generates integers over the entire range of int.
*/
public RestrictableGenerator<Integer> uniformInts() {
return uniformInts(Integer.MIN_VALUE, Integer.MAX_VALUE);
}
/**
* Returns a generator that generates longs in the range [lo, hi] (inclusive of both lo and hi).
*/
public RestrictableGenerator<Long> uniformLongs(long lo, long hi) {
return new UniformLongGenerator(this, lo, hi);
}
/**
* Returns a generator that generates integers over the entire range of int.
*/
public RestrictableGenerator<Long> uniformLongs() {
return uniformLongs(Long.MIN_VALUE, Long.MAX_VALUE);
}
/**
* Generates uniform doubles in the range of [lo, hi) (inclusive of lo, exclusive of hi).
*/
public RestrictableGenerator<Double> uniformDoubles(double lo, double hi) {
return new UniformDoubleGenerator(this, lo, hi);
}
/**
* Generates uniform doubles in the range of [0, 1) (inclusive of 0, exclusive of 1).
*/
public RestrictableGenerator<Double> uniformDoubles() {
return uniformDoubles(0, 1);
}
/**
* Provides an any-bits double distribution random generator, i.e. the bits are uniformly sampled,
* thus creating any possible double value, including the multiple different NaN representations.
*/
public Generator<Double> anyBitsDouble() {
return new AnyBitsDoubleGenerator(this);
}
/**
* Generates uniform doubles in the range of [lo, hi) (inclusive of lo, exclusive of hi).
*/
public RestrictableGenerator<Float> uniformFloats(float lo, float hi) {
return new UniformFloatGenerator(this, lo, hi);
}
/**
* Generates uniform floats in the range of [0, 1) (inclusive of 0, exclusive of 1).
*/
public RestrictableGenerator<Float> uniformFloats() {
return uniformFloats(0, 1);
}
/**
* Provides an any-bits float distribution random generator, i.e. the bits are uniformly sampled,
* thus creating any possible float value, including the multiple different NaN representations.
*/
public Generator<Float> anyBitsFloats() {
return new AnyBitsFloatGenerator(this);
}
/**
* Returns a generator that uniformly randomly samples elements from the provided collection.
* Each element in the collection is treated as a separate, unique value, even if equals might be true.
* The result is an unrestrictable generator. If you want a restrictable generator that selects values from a
* list and are working with Comparable values, use {@link #orderedRandomElement(Collection)}.
*/
public <T> Generator<T> randomElement(Collection<T> list) {
return new RandomElementGenerator<>(this, list);
}
/**
* Returns a restrictable generator that uniformly randomly samples elements from the provided collection.
* Duplicate elements are discarded from the collection. Restrictions are inclusive of both the uppper and lower
* bound.
*/
public <T extends Comparable<T>> RestrictableGenerator<T> orderedRandomElement(Collection<T> list) {
NavigableSet<T> set = list instanceof NavigableSet<T> ? (NavigableSet<T>) list : new TreeSet<>(list);
return new RestrictableRandomElementGenerator<>(this, set);
}
/**
* Returns a generator that always generate the provided value.
*/
public <T> Generator<T> single(T value) {
return new SingleValueGenerator<>(value);
}
/**
* Returns a restrictable generator that always generate the provided value.
*/
public <T extends Comparable<T>> RestrictableGenerator<T> single(T value) {
return new RestrictableSingleValueGenerator<>(value);
}
/**
* Returns a new generator that samples its next element from either generator A or B, with assignable weights.
* An overload for restrictable generators exists.
*/
public <T> Generator<T> mixed(Generator<T> a, Generator<T> b, int weightA, int weightB) {
return new MixedGenerator<>(this, List.of(a, b), List.of(weightA, weightB));
}
/**
* Returns a new generator that samples its next element randomly from one of the provided generators with
* assignable weights.
* An overload for restrictable generators exists.
*/
@SafeVarargs
public final <T> Generator<T> mixed(List<Integer> weights, Generator<T>... generators) {
return new MixedGenerator<>(this, Arrays.asList(generators), weights);
}
/**
* Returns a new restrictable generator that samples its next element from either generator A or B, with assignable weights.
* Restricting this generator restricts each subgenerator. Generators which become empty by the restriction are
* removed from the new mixed generator. Weights stay their original value if a generator is removed. If the mixed
* generator would become empty by applying a restriction {@link EmptyGeneratorException} is thrown.
*/
public <T extends Comparable<T>> RestrictableGenerator<T> mixed(RestrictableGenerator<T> a, RestrictableGenerator<T> b, int weightA, int weightB) {
return new RestrictableMixedGenerator<>(this, List.of(a, b), List.of(weightA, weightB));
}
/**
* Returns a new restrictable generator that samples its next element randomly from one of the provided restrictable
* generators with assignable weights.
* See {@link #mixed(RestrictableGenerator, RestrictableGenerator, int, int)} for details about restricting this
* generator.
*/
@SafeVarargs
public final <T extends Comparable<T>> RestrictableGenerator<T> mixed(List<Integer> weights, RestrictableGenerator<T>... generators) {
return new RestrictableMixedGenerator<>(this, Arrays.asList(generators), weights);
}
/**
* Randomly pick an int generator.
*
* @return Random int generator.
*/
public RestrictableGenerator<Integer> ints() {
switch(random.nextInt(0, 6)) {
case 0 -> { return uniformInts(); }
case 1 -> { return powerOfTwoInts(0); }
case 2 -> { return powerOfTwoInts(2); }
case 3 -> { return powerOfTwoInts(16); }
case 4 -> { return uniformIntsMixedWithPowersOfTwo(1, 1, 16); }
case 5 -> { return uniformIntsMixedWithPowersOfTwo(1, 2, 2); }
default -> { throw new RuntimeException("impossible"); }
}
}
/**
* A generator of special ints. Special ints are powers of two or values close to powers of 2, where a value
* is close to a power of two p if it is in the interval [p - range, p + range]. Note that we also consider negative
* values as powers of two. Note that for range >= 1, the set of values includes {@link Integer#MAX_VALUE} and
* {@link Integer#MIN_VALUE}.
*/
public RestrictableGenerator<Integer> powerOfTwoInts(int range) {
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < 32; i++) {
int pow2 = 1 << i;
for (int j = -range; j <= range; j++) {
set.add(+pow2 + j);
set.add(-pow2 + j);
}
}
return orderedRandomElement(set);
}
/**
* A convenience helper to mix {@link #powerOfTwoInts(int)} with {@link #uniformInts(int, int)}.
*/
public RestrictableGenerator<Integer> uniformIntsMixedWithPowersOfTwo(int weightUniform, int weightSpecial, int rangeSpecial) {
return mixed(uniformInts(), powerOfTwoInts(rangeSpecial), weightUniform, weightSpecial);
}
/**
* Randomly pick a long generator.
*
* @return Random long generator.
*/
public RestrictableGenerator<Long> longs() {
switch(random.nextInt(0, 6)) {
case 0 -> { return uniformLongs(); }
case 1 -> { return powerOfTwoLongs(0); }
case 2 -> { return powerOfTwoLongs(2); }
case 3 -> { return powerOfTwoLongs(16); }
case 4 -> { return uniformLongsMixedWithPowerOfTwos(1, 1, 16); }
case 5 -> { return uniformLongsMixedWithPowerOfTwos(1, 2, 2); }
default -> { throw new RuntimeException("impossible"); }
}
}
/**
* A generator of special longs. Special longs are powers of two or values close to powers of 2, where a value
* is close to a power of two p if it is in the interval [p - range, p + range]. Note that we also consider negative
* values as powers of two. Note that for range >= 1, the set of values includes {@link Long#MAX_VALUE} and
* {@link Long#MIN_VALUE}.
*/
public RestrictableGenerator<Long> powerOfTwoLongs(int range) {
TreeSet<Long> set = new TreeSet<>();
for (int i = 0; i < 64; i++) {
long pow2 = 1L << i;
for (int j = -range; j <= range; j++) {
set.add(+pow2 + j);
set.add(-pow2 + j);
}
}
return orderedRandomElement(set);
}
/**
* A convenience helper to mix {@link #powerOfTwoLongs(int)} with {@link #uniformLongs(long, long)}.
*/
public RestrictableGenerator<Long> uniformLongsMixedWithPowerOfTwos(int weightUniform, int weightSpecial, int rangeSpecial) {
return mixed(uniformLongs(), powerOfTwoLongs(rangeSpecial), weightUniform, weightSpecial);
}
/**
* Randomly pick a float generator.
*
* @return Random float generator.
*/
public Generator<Float> floats() {
switch(random.nextInt(0, 5)) {
case 0 -> { return uniformFloats(-1, 1); }
// Well-balanced, so that multiplication reduction never explodes or collapses to zero:
case 1 -> { return uniformFloats(0.999f, 1.001f); }
case 2 -> { return anyBitsFloats(); }
// A tame distribution, mixed in with the occasional special float value:
case 3 -> { return mixedWithSpecialFloats(uniformFloats(0.999f, 1.001f), 10, 1000); }
// Generating any bits, but special values are more frequent.
case 4 -> { return mixedWithSpecialFloats(anyBitsFloats(), 100, 200); }
default -> { throw new RuntimeException("impossible"); }
}
}
/**
* Randomly pick a double generator.
*
* @return Random double generator.
*/
public Generator<Double> doubles() {
switch(random.nextInt(0, 5)) {
case 0 -> { return uniformDoubles(-1, 1); }
// Well-balanced, so that multiplication reduction never explodes or collapses to zero:
case 1 -> { return uniformDoubles(0.999f, 1.001f); }
case 2 -> { return anyBitsDouble(); }
// A tame distribution, mixed in with the occasional special double value:
case 3 -> { return mixedWithSpecialDoubles(uniformDoubles(0.999f, 1.001f), 10, 1000); }
// Generating any bits, but special values are more frequent.
case 4 -> { return mixedWithSpecialDoubles(anyBitsDouble(), 100, 200); }
default -> { throw new RuntimeException("impossible"); }
}
}
/**
* Generates interesting double values, which often are corner cases such as, 0, 1, -1, NaN, +/- Infinity, Min,
* Max.
*/
public final RestrictableGenerator<Double> SPECIAL_DOUBLES = orderedRandomElement(List.of(
0d,
1d,
-1d,
Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY,
Double.NaN,
Double.MAX_VALUE,
Double.MIN_NORMAL,
Double.MIN_VALUE
));
/**
* Returns a mixed generator that mixes the provided background generator and {@link #SPECIAL_DOUBLES} with the provided
* weights.
*/
public Generator<Double> mixedWithSpecialDoubles(Generator<Double> background, int weightNormal, int weightSpecial) {
return mixed(background, SPECIAL_DOUBLES, weightNormal, weightSpecial);
}
/**
* Returns a restrictable mixed generator that mixes the provided background generator and {@link #SPECIAL_DOUBLES} with the provided
* weights.
*/
public RestrictableGenerator<Double> mixedWithSpecialDoubles(RestrictableGenerator<Double> background, int weightNormal, int weightSpecial) {
return mixed(background, SPECIAL_DOUBLES, weightNormal, weightSpecial);
}
/**
* Generates interesting double values, which often are corner cases such as, 0, 1, -1, NaN, +/- Infinity, Min,
* Max.
*/
public final RestrictableGenerator<Float> SPECIAL_FLOATS = orderedRandomElement(List.of(
0f,
1f,
-1f,
Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY,
Float.NaN,
Float.MAX_VALUE,
Float.MIN_NORMAL,
Float.MIN_VALUE
));
/**
* Returns a mixed generator that mixes the provided background generator and {@link #SPECIAL_FLOATS} with the provided
* weights.
*/
public Generator<Float> mixedWithSpecialFloats(Generator<Float> background, int weightNormal, int weightSpecial) {
return mixed(background, SPECIAL_FLOATS, weightNormal, weightSpecial);
}
/**
* Returns a restrictable mixed generator that mixes the provided background generator and {@link #SPECIAL_FLOATS} with the provided
* weights.
*/
public RestrictableGenerator<Float> mixedWithSpecialFloats(RestrictableGenerator<Float> background, int weightNormal, int weightSpecial) {
return mixed(background, SPECIAL_FLOATS, weightNormal, weightSpecial);
}
/**
* Trys to restrict the provided restrictable generator to the provided range. If the restriction fails no
* exception is raised, but instead a uniform int generator for the range is returned.
*/
public RestrictableGenerator<Integer> safeRestrict(RestrictableGenerator<Integer> g, int lo, int hi) {
try {
return g.restricted(lo, hi);
} catch (EmptyGeneratorException e) {
return uniformInts(lo, hi);
}
}
/**
* Trys to restrict the provided restrictable generator to the provided range. If the restriction fails no
* exception is raised, but instead a uniform long generator for the range is returned.
*/
public RestrictableGenerator<Long> safeRestrict(RestrictableGenerator<Long> g, long lo, long hi) {
try {
return g.restricted(lo, hi);
} catch (EmptyGeneratorException e) {
return uniformLongs(lo, hi);
}
}
/**
* Trys to restrict the provided restrictable generator to the provided range. If the restriction fails no
* exception is raised, but instead a uniform double generator for the range is returned.
*/
public RestrictableGenerator<Double> safeRestrict(RestrictableGenerator<Double> g, double lo, double hi) {
try {
return g.restricted(lo, hi);
} catch (EmptyGeneratorException e) {
return uniformDoubles(lo, hi);
}
}
/**
* Trys to restrict the provided restrictable generator to the provided range. If the restriction fails no
* exception is raised, but instead a uniform float generator for the range is returned.
*/
public RestrictableGenerator<Float> safeRestrict(RestrictableGenerator<Float> g, float lo, float hi) {
try {
return g.restricted(lo, hi);
} catch (EmptyGeneratorException e) {
return uniformFloats(lo, hi);
}
}
/**
* Fills the memory segments with doubles obtained by calling next on the generator.
*
* @param generator The generator from which to source the values.
* @param ms Memory segment to be filled with random values.
*/
public void fillDouble(Generator<Double> generator, MemorySegment ms) {
var layout = ValueLayout.JAVA_DOUBLE_UNALIGNED;
for (long i = 0; i < ms.byteSize() / layout.byteSize(); i++) {
ms.setAtIndex(layout, i, generator.next());
}
}
/**
* Fill the array with doubles using the distribution of nextDouble.
*
* @param a Array to be filled with random values.
*/
public void fill(Generator<Double> generator, double[] a) {
fillDouble(generator, MemorySegment.ofArray(a));
}
/**
* Fills the memory segments with floats obtained by calling next on the generator.
*
* @param generator The generator from which to source the values.
* @param ms Memory segment to be filled with random values.
*/
public void fillFloat(Generator<Float> generator, MemorySegment ms) {
var layout = ValueLayout.JAVA_FLOAT_UNALIGNED;
for (long i = 0; i < ms.byteSize() / layout.byteSize(); i++) {
ms.setAtIndex(layout, i, generator.next());
}
}
/**
* Fill the array with floats using the distribution of nextDouble.
*
* @param a Array to be filled with random values.
*/
public void fill(Generator<Float> generator, float[] a) {
fillFloat(generator, MemorySegment.ofArray(a));
}
/**
* Fills the memory segments with ints obtained by calling next on the generator.
*
* @param generator The generator from which to source the values.
* @param ms Memory segment to be filled with random values.
*/
public void fillInt(Generator<Integer> generator, MemorySegment ms) {
var layout = ValueLayout.JAVA_INT_UNALIGNED;
for (long i = 0; i < ms.byteSize() / layout.byteSize(); i++) {
ms.setAtIndex(layout, i, generator.next());
}
}
/**
* Fill the array with ints using the distribution of nextDouble.
*
* @param a Array to be filled with random values.
*/
public void fill(Generator<Integer> generator, int[] a) {
fillInt(generator, MemorySegment.ofArray(a));
}
/**
* Fills the memory segments with longs obtained by calling next on the generator.
*
* @param generator The generator from which to source the values.
* @param ms Memory segment to be filled with random values.
*/
public void fillLong(Generator<Long> generator, MemorySegment ms) {
var layout = ValueLayout.JAVA_LONG_UNALIGNED;
for (long i = 0; i < ms.byteSize() / layout.byteSize(); i++) {
ms.setAtIndex(layout, i, generator.next());
}
}
/**
* Fill the array with longs using the distribution of nextDouble.
*
* @param a Array to be filled with random values.
*/
public void fill(Generator<Long> generator, long[] a) {
fillLong(generator, MemorySegment.ofArray(a));
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
import java.util.List;
import java.util.TreeMap;
import java.util.function.Function;
/**
* Mixed results between different generators with configurable weights.
*/
class MixedGenerator<G extends Generator<T>, T> extends BoundGenerator<T> {
private final TreeMap<Integer, G> generators = new TreeMap<>();
private final int totalWeight;
/**
* Creates a new {@link MixedGenerator}, which samples from a list of generators at random,
* according to specified weights.
*/
MixedGenerator(Generators g, List<G> generators, List<Integer> weights) {
super(g);
if (weights.size() != generators.size()) {
throw new IllegalArgumentException("weights and generators must have the same size");
}
int acc = 0;
for (int i = 0; i < generators.size(); i++) {
int weight = weights.get(i);
if (weight <= 0) {
throw new IllegalArgumentException("weights must be positive");
}
acc += weight;
this.generators.put(acc, generators.get(i));
}
this.totalWeight = acc;
}
/**
* Creates a new mixed generator by mapping each generator of the old generator to a new value or removing it.
* @param other The generator to copy from.
* @param generatorMapper A function that is called for each subgenerator in the old generator. Either return a
* generator that takes the role of the old generator (might be the same) or null to remove
* the generator completely. In this case, the weights of the other generators stay the same.
*/
MixedGenerator(MixedGenerator<G, T> other, Function<G, G> generatorMapper) {
super(other.g);
// We could map and create new lists and delegate to the other constructor but that would allocate
// two additional lists, so in the interest of memory efficiency we construct the new TreeMap ourselves.
int acc = 0;
int prevKey = 0;
// entrySet: "The set's iterator returns the entries in ascending key order." (documentation)
// This means we iterate over the generators exactly in the order they were inserted as we insert with ascending
// keys (due to summing positive numbers).
for (var entry : other.generators.entrySet()) {
var gen = generatorMapper.apply(entry.getValue());
if (gen != null) {
// entry.getKey() is the sum of all generator weights up to this one.
// We compute this generator's weight by taking the difference to the previous key
int weight = entry.getKey() - prevKey;
acc += weight;
this.generators.put(acc, gen);
}
prevKey = entry.getKey();
}
if (this.generators.isEmpty()) {
throw new EmptyGeneratorException();
}
this.totalWeight = acc;
}
@Override
public T next() {
int r = g.random.nextInt(0, totalWeight);
return generators.higherEntry(r).getValue().next();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
import java.util.ArrayList;
import java.util.Collection;
class RandomElementGenerator<T> extends BoundGenerator<T> {
private final ArrayList<T> elements;
private final Generator<Integer> generator;
RandomElementGenerator(Generators g, Collection<T> elements) {
super(g);
this.elements = new ArrayList<>(elements);
if (this.elements.isEmpty()) throw new EmptyGeneratorException();
this.generator = g.uniformInts(0, elements.size() - 1);
}
@Override
public final T next() {
return elements.get(generator.next());
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Defines the underlying randomness source used by the generators. This is essentially a subset of
* {@link java.util.random.RandomGenerator} and the present methods have the same contract.
* This interface greatly benefits testing, as it is much easier to implement than
* {@link java.util.random.RandomGenerator} and thus makes creating test doubles more convenient.
*/
public interface RandomnessSource {
/** Samples the next long value uniformly at random. */
long nextLong();
/** Samples the next long value in the half-open interval [lo, hi) uniformly at random. */
long nextLong(long lo, long hi);
/** Samples the next int value uniformly at random. */
int nextInt();
/** Samples the next int value in the half-open interval [lo, hi) uniformly at random. */
int nextInt(int lo, int hi);
/** Samples the next double value in the half-open interval [lo, hi) uniformly at random. */
double nextDouble(double lo, double hi);
/** Samples the next float value in the half-open interval [lo, hi) uniformly at random. */
float nextFloat(float lo, float hi);
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
import java.util.random.RandomGenerator;
/**
* An adapter for using a {@link RandomGenerator} as a {@link RandomnessSource}.
* See RandomnessSource for more information.
*/
public class RandomnessSourceAdapter implements RandomnessSource {
private final RandomGenerator rand;
RandomnessSourceAdapter(RandomGenerator rand) {
this.rand = rand;
}
@Override
public long nextLong() {
return rand.nextLong();
}
@Override
public long nextLong(long lo, long hi) {
return rand.nextLong(lo, hi);
}
@Override
public int nextInt() {
return rand.nextInt();
}
@Override
public int nextInt(int lo, int hi) {
return rand.nextInt(lo, hi);
}
@Override
public double nextDouble(double lo, double hi) {
return rand.nextDouble(lo, hi);
}
@Override
public float nextFloat(float lo, float hi) {
return rand.nextFloat(lo, hi);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* A restrictable generator allows the creation of a new generator by restricting the range of its output values.
* The exact semantics of this restriction depend on the concrete implementation, but it usually means taking the
* intersection of the old range and the newly requested range of values.
*/
public interface RestrictableGenerator<T> extends Generator<T> {
/**
* Returns a new generator where the range of this generator has been restricted to the range of newLo and newHi.
* Whether newHi is inclusive or exclusive depends on the concrete implementation.
* @throws EmptyGeneratorException if this restriction would result in an empty generator.
*/
RestrictableGenerator<T> restricted(T newLo, T newHi);
}

View File

@ -0,0 +1,24 @@
package compiler.lib.generators;
import java.util.List;
final class RestrictableMixedGenerator<T extends Comparable<T>> extends MixedGenerator<RestrictableGenerator<T>, T> implements RestrictableGenerator<T> {
RestrictableMixedGenerator(Generators g, List<RestrictableGenerator<T>> generators, List<Integer> weights) {
super(g, generators, weights);
}
RestrictableMixedGenerator(RestrictableMixedGenerator<T> other, T newLo, T newHi) {
super(other, (generator) -> {
try {
return generator.restricted(newLo, newHi);
} catch (EmptyGeneratorException e) {
return null;
}
});
}
@Override
public RestrictableGenerator<T> restricted(T newLo, T newHi) {
return new RestrictableMixedGenerator<>(this, newLo, newHi);
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
import java.util.NavigableSet;
/**
* Selects values from a pre-defined list.
*/
final class RestrictableRandomElementGenerator<T extends Comparable<T>> extends RandomElementGenerator<T> implements RestrictableGenerator<T> {
/*
* Pre-generated values we can choose from. Maintained for restriction.
*/
private final NavigableSet<T> values;
public RestrictableRandomElementGenerator(Generators g, NavigableSet<T> values) {
super(g, values);
if (values.isEmpty()) throw new EmptyGeneratorException();
this.values = values;
}
@Override
public RestrictableGenerator<T> restricted(T newLo, T newHi) {
return new RestrictableRandomElementGenerator<>(g, values.subSet(newLo, true, newHi, true));
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
class RestrictableSingleValueGenerator<T extends Comparable<T>> implements RestrictableGenerator<T> {
private final T value;
RestrictableSingleValueGenerator(T value) {
this.value = value;
}
@Override
public RestrictableGenerator<T> restricted(T newLo, T newHi) {
if (newLo.compareTo(value) <= 0 && value.compareTo(newHi) <= 0) {
return this;
}
throw new EmptyGeneratorException();
}
@Override
public T next() {
return value;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* A generator which always returns the same value.
*/
class SingleValueGenerator<T> implements Generator<T> {
private final T value;
SingleValueGenerator(T value) {
this.value = value;
}
@Override
public T next() {
return this.value;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides a uniform double distribution random generator, in the provided range [lo, hi).
*/
final class UniformDoubleGenerator extends UniformIntersectionRestrictableGenerator<Double> {
/**
* Creates a new {@link UniformFloatGenerator}.
*
* @param lo Lower bound of the range (inclusive).
* @param hi Higher bound of the range (exclusive).
*/
public UniformDoubleGenerator(Generators g, double lo, double hi) {
super(g, lo, hi);
}
@Override
public Double next() {
return g.random.nextDouble(lo(), hi());
}
@Override
protected RestrictableGenerator<Double> doRestrictionFromIntersection(Double lo, Double hi) {
return new UniformDoubleGenerator(g, lo, hi);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides a uniform float distribution random generator, in the provided range [lo, hi).
*/
final class UniformFloatGenerator extends UniformIntersectionRestrictableGenerator<Float> {
/**
* Creates a new {@link UniformFloatGenerator}.
*
* @param lo Lower bound of the range (inclusive).
* @param hi Higher bound of the range (exclusive).
*/
public UniformFloatGenerator(Generators g, float lo, float hi) {
super(g, lo, hi);
}
@Override
public Float next() {
return g.random.nextFloat(lo(), hi());
}
@Override
protected RestrictableGenerator<Float> doRestrictionFromIntersection(Float lo, Float hi) {
return new UniformFloatGenerator(g, lo, hi);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides a uniform int distribution random generator.
*/
final class UniformIntGenerator extends UniformIntersectionRestrictableGenerator<Integer> {
public UniformIntGenerator(Generators g, int lo, int hi) {
super(g, lo, hi);
}
@Override
public Integer next() {
if (hi() == Integer.MAX_VALUE) {
if (lo() == Integer.MIN_VALUE) {
return g.random.nextInt();
}
return g.random.nextInt(lo() - 1, hi()) + 1;
}
return g.random.nextInt(lo(), hi() + 1);
}
@Override
protected RestrictableGenerator<Integer> doRestrictionFromIntersection(Integer lo, Integer hi) {
return new UniformIntGenerator(g, lo, hi);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* A generators whose outputs are restricted by taking the intersection of the previous interval and the new interval.
*/
abstract class UniformIntersectionRestrictableGenerator<T extends Comparable<T>> extends BoundGenerator<T> implements RestrictableGenerator<T> {
private final T lo;
private final T hi;
public UniformIntersectionRestrictableGenerator(Generators g, T lo, T hi) {
super(g);
if (lo.compareTo(hi) > 0) throw new EmptyGeneratorException();
this.lo = lo;
this.hi = hi;
}
/**
* Creates a new generator by further restricting the range of values. The range of values will be the
* intersection of the previous values and the values in the provided range.
* The probability of each element occurring in the new generator stay the same relative to each other.
*/
@Override
public RestrictableGenerator<T> restricted(T newLo /*as*/, T newHi /*ae*/) {
if (lo().compareTo(newHi) > 0 || newLo.compareTo(hi()) > 0) {
throw new EmptyGeneratorException();
}
return doRestrictionFromIntersection(max(newLo, lo()), min(newHi, hi()));
}
private T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
private T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
/**
* Your subclass can just override this method which will receive the computed intersection between the old and
* new interval. It is guaranteed that the interval is non-empty.
*/
protected abstract RestrictableGenerator<T> doRestrictionFromIntersection(T lo, T hi);
T hi() { return hi; }
T lo() { return lo; }
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.generators;
/**
* Provides a uniform long distribution random generator.
*/
final class UniformLongGenerator extends UniformIntersectionRestrictableGenerator<Long> {
public UniformLongGenerator(Generators g, Long lo, Long hi) {
super(g, lo, hi);
}
@Override
public Long next() {
if (hi() == Long.MAX_VALUE) {
if (lo() == Long.MIN_VALUE) {
return g.random.nextLong();
}
return g.random.nextLong(lo() - 1, hi()) + 1;
}
return g.random.nextLong(lo(), hi() + 1);
}
@Override
protected RestrictableGenerator<Long> doRestrictionFromIntersection(Long lo, Long hi) {
return new UniformLongGenerator(g, lo, hi);
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary An example test that shows how to use the Generators library.
* @modules java.base/jdk.internal.misc
* @library /test/lib /
* @run driver testlibrary_tests.generators.tests.ExampleTest
*/
package testlibrary_tests.generators.tests;
import compiler.lib.generators.Generator;
import static compiler.lib.generators.Generators.G;
public class ExampleTest {
static class FakeException extends RuntimeException {}
static class UnderTest {
private enum State { STAND_BY, FIRST, SECOND };
private State state = State.STAND_BY;
void doIt(int x) {
state = switch (state) {
case State.STAND_BY -> x == (1 << 10) + 3 ? State.FIRST : State.STAND_BY;
case State.FIRST -> x == (1 << 5) - 2 ? State.SECOND : State.STAND_BY;
case State.SECOND -> {
if (x == (1 << 4)) throw new FakeException();
yield State.STAND_BY;
}
};
}
}
public static void main(String[] args) {
// This test should print "Assertion triggered by special" (see the math below) but almost never
// "Assertion triggered by uniform" as the chance of triggering is about 2^-96.
try {
test(G.uniformInts());
} catch (FakeException e) {
System.out.println("Assertion triggered by uniform");
}
try {
// 408 ints => 1/408 * 1/408 * 1/408 => 1/67_917_312 => with 70_000_000 loop iterations we should trigger
test(G.powerOfTwoInts(3));
} catch (FakeException e) {
System.out.println("Assertion triggered by special");
}
}
public static void test(Generator<Integer> g) {
UnderTest underTest = new UnderTest();
for (int i = 0; i < 70_000_000 * 3; i++) {
underTest.doIt(g.next());
}
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package testlibrary_tests.generators.tests;
import compiler.lib.generators.RandomnessSource;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* This class is a mock for RandomnessSource. For each method defined in {@link RandomnessSource}, it maintains
* a queue. For the methods {@link #nextInt()} and {@link #nextLong()} the queue simply contains ints and longs,
* respectively, and they are dequeue and returned when the methods are called. For the bounded methods, each queue
* element is a value associated with the bounds that are expected for the call. If the actual bounds do not match
* the arguments provided, a RuntimeException is raised. This allows verifying that the correct bounds are passed to
* the randomness source.
* Furthermore, if a method is called and its queue is empty, an exception is raised.
* To ensure all expected methods have been called in a test, you should call {@link #checkEmpty()} in-between tests
* to ensure that queues are empty, that is, all expected methods have been called.
*/
class MockRandomnessSource implements RandomnessSource {
private record Bounded<T>(T lo, T hi, T value) {}
private final Queue<Long> unboundedLongQueue = new ArrayDeque<>();
private final Queue<Integer> unboundedIntegerQueue = new ArrayDeque<>();
private final Queue<Bounded<Long>> boundedLongQueue = new ArrayDeque<>();
private final Queue<Bounded<Integer>> boundedIntegerQueue = new ArrayDeque<>();
private final Queue<Bounded<Double>> boundedDoubleQueue = new ArrayDeque<>();
private final Queue<Bounded<Float>> boundedFloatQueue = new ArrayDeque<>();
private <T> T dequeueBounded(Queue<Bounded<T>> queue, T lo, T hi) {
Bounded<T> bounded = queue.remove();
if (!bounded.lo.equals(lo) || !bounded.hi.equals(hi)) {
throw new RuntimeException("Expected bounds " + bounded.lo + " and " + bounded.hi + " but found " + lo + " and " + hi);
}
return bounded.value;
}
private void checkQueueEmpty(Queue<?> queue, String name) {
if (!queue.isEmpty()) throw new RuntimeException("Expected empty queue for " + name + " but found " + queue);
}
public MockRandomnessSource enqueueLong(long value) {
unboundedLongQueue.add(value);
return this;
}
public MockRandomnessSource enqueueInteger(int value) {
unboundedIntegerQueue.add(value);
return this;
}
public MockRandomnessSource enqueueLong(long lo, long hi, long value) {
boundedLongQueue.add(new Bounded<>(lo, hi, value));
return this;
}
public MockRandomnessSource enqueueInteger(int lo, int hi, int value) {
boundedIntegerQueue.add(new Bounded<>(lo, hi, value));
return this;
}
public MockRandomnessSource enqueueDouble(double lo, double hi, double value) {
boundedDoubleQueue.add(new Bounded<>(lo, hi, value));
return this;
}
public MockRandomnessSource enqueueFloat(float lo, float hi, float value) {
boundedFloatQueue.add(new Bounded<>(lo, hi, value));
return this;
}
public MockRandomnessSource checkEmpty() {
checkQueueEmpty(unboundedLongQueue, "unbounded longs");
checkQueueEmpty(unboundedIntegerQueue, "unbounded integers");
checkQueueEmpty(boundedLongQueue, "bounded longs");
checkQueueEmpty(boundedIntegerQueue, "bounded integers");
checkQueueEmpty(boundedDoubleQueue, "bounded doubles");
checkQueueEmpty(boundedFloatQueue, "bounded floats");
return this;
}
@Override
public long nextLong() {
return unboundedLongQueue.remove();
}
@Override
public long nextLong(long lo, long hi) {
return dequeueBounded(boundedLongQueue, lo, hi);
}
@Override
public int nextInt() {
return unboundedIntegerQueue.remove();
}
@Override
public int nextInt(int lo, int hi) {
return dequeueBounded(boundedIntegerQueue, lo, hi);
}
@Override
public double nextDouble(double lo, double hi) {
return dequeueBounded(boundedDoubleQueue, lo, hi);
}
@Override
public float nextFloat(float lo, float hi) {
return dequeueBounded(boundedFloatQueue, lo, hi);
}
}

View File

@ -0,0 +1,593 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test functionality of the Generators library.
* @modules java.base/jdk.internal.misc
* @library /test/lib /
* @build MockRandomnessSource
* @run driver testlibrary_tests.generators.tests.TestGenerators
*/
package testlibrary_tests.generators.tests;
import compiler.lib.generators.EmptyGeneratorException;
import compiler.lib.generators.Generator;
import compiler.lib.generators.Generators;
import compiler.lib.generators.RestrictableGenerator;
import jdk.test.lib.Asserts;
import java.util.*;
import static compiler.lib.generators.Generators.G;
public class TestGenerators {
// As it's hard to write tests with real randomness, we mock the randomness source so we can control which "random"
// values are fed to the generators. Thus, a lot of the tests below are white-box tests, that have knowledge about
// the internals of when randomness is consumed. There are also black-box tests which refer to Generators.G.
// Please also see MockRandomness to learn more about this class.
static MockRandomnessSource mockSource = new MockRandomnessSource();
static Generators mockGS = new Generators(mockSource);
public static void main(String[] args) {
testEmptyGenerators();
testUniformInts();
testUniformLongs();
testAnyBits();
testUniformFloat();
testUniformDouble();
testSingle();
testMixed();
testRandomElement();
specialInt();
specialLong();
testSpecialFloat();
testSpecialDouble();
testSafeRestrict();
testFill();
testFuzzy();
}
static void testMixed() {
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 7) // MixedGenerator chooses a generator: single
// single was chosen but does not consume randomness
.enqueueInteger(0, 10, 5) // MixedGenerator chooses a generator: uniform ints
.enqueueInteger(0, 31, 4) // uniform ints samples
.enqueueInteger(0, 10, 1) // MixedGenerator chooses a generator: uniform ints
.enqueueInteger(0, 31, 18); // uniform ints samples
var g0 = mockGS.mixed(mockGS.uniformInts(0, 30), mockGS.single(-1), 7, 3);
Asserts.assertEQ(g0.next(), -1);
Asserts.assertEQ(g0.next(), 4);
Asserts.assertEQ(g0.next(), 18);
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 1) // MixedGenerator chooses a generator: the first uniform ints
.enqueueInteger(0, 31, 24) // uniform ints (1) samples
.enqueueInteger(0, 10, 2) // MixedGenerator chooses a generator: single
// single does not use randomness
.enqueueInteger(0, 10, 7) // MixedGenerator chooses a generator: the second uniform ints
.enqueueInteger(-10, 0, -2) // uniform ints (2) samples
.enqueueInteger(0, 10, 9) // MixedGenerator chooses a generator: the second uniform ints
.enqueueInteger(-10, 0, -4) // uniform ints (2) samples
.enqueueInteger(0, 10, 1) // MixedGenerator chooses a generator: the first uniform ints
.enqueueInteger(0, 31, 29); // uniform ints (1) samples
var g1 = mockGS.mixed(
List.of(2, 5, 3),
mockGS.uniformInts(0, 30), mockGS.single(-1), mockGS.uniformInts(-10, -1)
);
Asserts.assertEQ(g1.next(), 24);
Asserts.assertEQ(g1.next(), -1);
Asserts.assertEQ(g1.next(), -2);
Asserts.assertEQ(g1.next(), -4);
Asserts.assertEQ(g1.next(), 29);
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 7) // MixedGenerator chooses a generator: single
// single was chosen but does not consume randomness
.enqueueInteger(0, 10, 5) // MixedGenerator chooses a generator: uniform ints
.enqueueInteger(0, 21, 18); // uniform ints samples
var g0r0 = g0.restricted(-1, 20);
Asserts.assertEQ(g0r0.next(), -1);
Asserts.assertEQ(g0r0.next(), 18);
mockSource
.checkEmpty()
.enqueueInteger(0, 7, 6) // MixedGenerator chooses a generator (weight for single will have been removed): uniform ints
.enqueueInteger(4, 21, 9); // MixedGenerator chooses a generator: uniform ints
var g0r1 = g0.restricted(4, 20);
Asserts.assertEQ(g0r1.next(), 9);
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 1) // MixedGenerator chooses a generator: the first uniform ints
.enqueueInteger(0, 21, 2) // uniform ints (1) samples
.enqueueInteger(0, 10, 2) // MixedGenerator chooses a generator: single
// single does not use randomness
.enqueueInteger(0, 10, 7) // MixedGenerator chooses a generator: the second uniform ints
.enqueueInteger(-1, 0, -1);
var g1r0 = g1.restricted(-1, 20);
Asserts.assertEQ(g1r0.next(), 2);
Asserts.assertEQ(g1r0.next(), -1);
Asserts.assertEQ(g1r0.next(), -1);
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 1) // MixedGenerator chooses a generator: the first uniform ints
.enqueueInteger(0, 21, 2) // uniform ints (1) samples
.enqueueInteger(0, 10, 2) // MixedGenerator chooses a generator: single
// single does not use randomness
.enqueueInteger(0, 10, 7) // MixedGenerator chooses a generator: the second uniform ints
.enqueueInteger(-1, 0, -1);
var g1r1 = g1.restricted(-1, 20);
Asserts.assertEQ(g1r1.next(), 2);
Asserts.assertEQ(g1r1.next(), -1);
Asserts.assertEQ(g1r1.next(), -1);
}
static void testSpecialDouble() {
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 3)
.enqueueDouble(0, 1, 3.4d)
.enqueueInteger(0, 10, 6)
.enqueueInteger(0, 9, 1);
var g = mockGS.mixedWithSpecialDoubles(mockGS.uniformDoubles(), 5, 5);
Asserts.assertEQ(g.next(), 3.4d);
Asserts.assertEQ(g.next(), -1d);
}
static void testSpecialFloat() {
mockSource
.checkEmpty()
.enqueueInteger(0, 10, 3)
.enqueueFloat(0, 1, 3.4f)
.enqueueInteger(0, 10, 6)
.enqueueInteger(0, 9, 1);
var g = mockGS.mixedWithSpecialFloats(mockGS.uniformFloats(), 5, 5);
Asserts.assertEQ(g.next(), 3.4f);
Asserts.assertEQ(g.next(), -1f);
}
static void testUniformFloat() {
mockSource.checkEmpty().enqueueFloat(-1, 10, 3.14159f);
Asserts.assertEQ(mockGS.uniformFloats(-1, 10).next(), 3.14159f);
mockSource.checkEmpty().enqueueFloat(0, 1, 3.14159f);
Asserts.assertEQ(mockGS.uniformFloats(0, 1).next(), 3.14159f);
}
static void testUniformDouble() {
mockSource.checkEmpty().enqueueDouble(-1, 10, 3.14159d);
Asserts.assertEQ(mockGS.uniformDoubles(-1, 10).next(), 3.14159d);
mockSource.checkEmpty().enqueueDouble(0, 1, 3.14159d);
Asserts.assertEQ(mockGS.uniformDoubles(0, 1).next(), 3.14159d);
}
static void testRandomElement() {
mockSource.checkEmpty().enqueueInteger(0, 3, 1).enqueueInteger(0, 3, 0);
var g = mockGS.randomElement(List.of("a", "b", "c"));
Asserts.assertEQ(g.next(), "b");
Asserts.assertEQ(g.next(), "a");
mockSource.checkEmpty().enqueueInteger(0, 8, 1).enqueueInteger(0, 8, 2);
// The list below is intentionally not sorted and is equivalent to: 1, 4, 4, 8, 9, 10, 13, 18, 20
// It contains 8 distinct values. Note that orderedRandomElement removes duplicates. Therefore the internal
// value list is: 1, 4, 8, 9, 10, 13, 18, 20
var g1 = mockGS.orderedRandomElement(List.of(10, 4, 1, 8, 9, 4, 20, 18, 13));
Asserts.assertEQ(g1.next(), 4);
Asserts.assertEQ(g1.next(), 8);
mockSource.checkEmpty().enqueueInteger(0, 3, 1).enqueueInteger(0, 3, 2);
// Ordered lists can also be restricted. Our new values are 9, 10, 13.
var gr = g1.restricted(9, 13);
Asserts.assertEQ(gr.next(), 10);
Asserts.assertEQ(gr.next(), 13);
mockSource.checkEmpty().enqueueInteger(0, 2, 1);
var gs = mockGS.orderedRandomElement(List.of("Bob", "Alice", "Carol")).restricted("Al", "Bz");
Asserts.assertEQ(gs.next(), "Bob");
}
static void specialInt() {
mockSource.checkEmpty().enqueueInteger(0, 63, 1).enqueueInteger(0, 63, 32);
var si = mockGS.powerOfTwoInts(0);
Asserts.assertEQ(si.next(), -(1 << 30));
Asserts.assertEQ(si.next(), 1);
mockSource.checkEmpty().enqueueInteger(0, 182, 1);
var si1 = mockGS.powerOfTwoInts(1);
Asserts.assertEQ(si1.next(), -(1 << 31) + 1);
}
static void specialLong() {
mockSource.checkEmpty().enqueueInteger(0, 127, 1).enqueueInteger(0, 127, 64);
var si = mockGS.powerOfTwoLongs(0);
Asserts.assertEQ(si.next(), -(1L << 62));
Asserts.assertEQ(si.next(), 1L);
mockSource.checkEmpty().enqueueInteger(0, 374, 1);
var si1 = mockGS.powerOfTwoLongs(1);
Asserts.assertEQ(si1.next(), -(1L << 63) + 1);
}
static void testSingle() {
mockSource.checkEmpty();
var g = mockGS.single(30);
Asserts.assertEQ(g.next(), 30);
Asserts.assertEQ(g.next(), 30);
Asserts.assertEQ(g.restricted(10, 50).next(), 30);
var gs = mockGS.single("hello");
Asserts.assertEQ(gs.next(), "hello");
Asserts.assertEQ(gs.next(), "hello");
Asserts.assertEQ(gs.restricted("a", "q").next(), "hello");
var theObject = new Object();
var go = mockGS.single(theObject);
Asserts.assertEQ(go.next(), theObject);
Asserts.assertEQ(go.next(), theObject);
}
static void testUniformInts() {
mockSource.checkEmpty().enqueueInteger(0, 11, 1).enqueueInteger(0, 11, 4);
var g0 = mockGS.uniformInts(0, 10);
Asserts.assertEQ(g0.next(), 1);
Asserts.assertEQ(g0.next(), 4);
mockSource.checkEmpty().enqueueInteger(0, 1, 0).enqueueInteger(0, 1, 0);
var g1 = mockGS.uniformInts(0, 0);
Asserts.assertEQ(g1.next(), 0);
Asserts.assertEQ(g1.next(), 0);
mockSource.checkEmpty().enqueueInteger(-1, Integer.MAX_VALUE, 10);
Asserts.assertEQ(mockGS.uniformInts(0, Integer.MAX_VALUE).next(), 11);
mockSource.checkEmpty().enqueueInteger(Integer.MIN_VALUE, 13, -33);
Asserts.assertEQ(mockGS.uniformInts(Integer.MIN_VALUE, 12).next(), -33);
mockSource.checkEmpty().enqueueInteger(11);
Asserts.assertEQ(mockGS.uniformInts(Integer.MIN_VALUE, Integer.MAX_VALUE).next(), 11);
mockSource.checkEmpty().enqueueInteger(10, 29, 17);
Asserts.assertEQ(mockGS.uniformInts(Integer.MIN_VALUE, Integer.MAX_VALUE).restricted(10, 28).next(), 17);
mockSource.checkEmpty().enqueueInteger(19, 29, 17);
Asserts.assertEQ(mockGS.uniformInts(Integer.MIN_VALUE, Integer.MAX_VALUE).restricted(10, 28).restricted(19, 33).next(), 17);
// inside interval positive
mockSource.checkEmpty().enqueueInteger(12, 19, 17);
Asserts.assertEQ(mockGS.uniformInts(10, 20).restricted(12, 18).next(), 17);
// inside interval negative
mockSource.checkEmpty().enqueueInteger(-18, -11, -17);
Asserts.assertEQ(mockGS.uniformInts(-20, -10).restricted(-18, -12).next(), -17);
// left interval positive
mockSource.checkEmpty().enqueueInteger(10, 13, 11);
Asserts.assertEQ(mockGS.uniformInts(10, 20).restricted(5, 12).next(), 11);
// left interval negative
mockSource.checkEmpty().enqueueInteger(-12, -9, -11);
Asserts.assertEQ(mockGS.uniformInts(-20, -10).restricted(-12, -5).next(), -11);
// right interval positive
mockSource.checkEmpty().enqueueInteger(17, 21, 19);
Asserts.assertEQ(mockGS.uniformInts(10, 20).restricted(17, 22).next(), 19);
// right interval negative
mockSource.checkEmpty().enqueueInteger(-20, -16, -19);
Asserts.assertEQ(mockGS.uniformInts(-20, -10).restricted(-22, -17).next(), -19);
mockSource.checkEmpty().enqueueInteger(144);
Asserts.assertEQ(mockGS.uniformInts().next(), 144);
mockSource.checkEmpty();
}
static void testUniformLongs() {
mockSource.checkEmpty().enqueueLong(0, 11, 1).enqueueLong(0, 11, 4);
var g0 = mockGS.uniformLongs(0, 10);
Asserts.assertEQ(g0.next(), 1L);
Asserts.assertEQ(g0.next(), 4L);
mockSource.checkEmpty().enqueueLong(0, 1, 0).enqueueLong(0, 1, 0);
var g1 = mockGS.uniformLongs(0, 0);
Asserts.assertEQ(g1.next(), 0L);
Asserts.assertEQ(g1.next(), 0L);
mockSource.checkEmpty().enqueueLong(-1, Long.MAX_VALUE, 10);
Asserts.assertEQ(mockGS.uniformLongs(0, Long.MAX_VALUE).next(), 11L);
mockSource.checkEmpty().enqueueLong(Long.MIN_VALUE, 13, -33);
Asserts.assertEQ(mockGS.uniformLongs(Long.MIN_VALUE, 12).next(), -33L);
mockSource.checkEmpty().enqueueLong(11);
Asserts.assertEQ(mockGS.uniformLongs(Long.MIN_VALUE, Long.MAX_VALUE).next(), 11L);
mockSource.checkEmpty().enqueueLong(10, 29, 17);
Asserts.assertEQ(mockGS.uniformLongs(Long.MIN_VALUE, Long.MAX_VALUE).restricted(10L, 28L).next(), 17L);
mockSource.checkEmpty().enqueueLong(19, 29, 17);
Asserts.assertEQ(mockGS.uniformLongs(Long.MIN_VALUE, Long.MAX_VALUE).restricted(10L, 28L).restricted(19L, 33L).next(), 17L);
mockSource.checkEmpty().enqueueLong(144);
Asserts.assertEQ(mockGS.uniformLongs().next(), 144L);
mockSource.checkEmpty();
}
static void testAnyBits() {
mockSource.checkEmpty().enqueueInteger(Float.floatToIntBits(3.14159f));
Asserts.assertEQ(mockGS.anyBitsFloats().next(), 3.14159f);
mockSource.checkEmpty().enqueueLong(Double.doubleToLongBits(3.14159d));
Asserts.assertEQ(mockGS.anyBitsDouble().next(), 3.14159d);
}
static void testEmptyGenerators() {
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformInts(1, 0));
Asserts.assertNotNull(G.uniformInts(0, 0));
Asserts.assertNotNull(G.uniformInts(0, 1));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformInts(0, 1).restricted(2, 5));
Asserts.assertNotNull(G.uniformInts(0, 1).restricted(0, 1));
Asserts.assertNotNull(G.uniformInts(0, 1).restricted(1, 5));
Asserts.assertNotNull(G.uniformInts(0, 10).restricted(1, 2));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformLongs(1, 0));
Asserts.assertNotNull(G.uniformLongs(0, 0));
Asserts.assertNotNull(G.uniformLongs(0, 1));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformLongs(0, 1).restricted(2L, 5L));
Asserts.assertNotNull(G.uniformLongs(0, 1).restricted(0L, 1L));
Asserts.assertNotNull(G.uniformLongs(0, 1).restricted(1L, 5L));
Asserts.assertNotNull(G.uniformLongs(0, 10).restricted(1L, 2L));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformDoubles(1, 0));
Asserts.assertNotNull(G.uniformDoubles(0, 1));
Asserts.assertNotNull(G.uniformDoubles(0, 0));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformDoubles(0, 1).restricted(1.1d, 2.4d));
Asserts.assertNotNull(G.uniformDoubles(0, 1).restricted(0.9d, 2.4d));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformFloats(1, 0));
Asserts.assertNotNull(G.uniformFloats(0, 1));
Asserts.assertNotNull(G.uniformFloats(0, 0));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.uniformFloats(0, 1).restricted(1.1f, 2.4f));
Asserts.assertNotNull(G.uniformFloats(0, 1).restricted(0.9f, 2.4f));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.randomElement(List.of()));
Asserts.assertNotNull(G.randomElement(List.of("a", "b", "c")));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.orderedRandomElement(new ArrayList<Integer>()));
Asserts.assertNotNull(G.orderedRandomElement(List.of(48, 29, 17)));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.orderedRandomElement(List.of(48, 29, 17)).restricted(-12, 10));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.single(10).restricted(0, 1));
Asserts.assertNotNull(G.single(10).restricted(9, 10));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.mixed(G.uniformInts(0, 10), G.uniformInts(15, 20), 5, 5).restricted(30, 34));
Asserts.assertNotNull(G.mixed(G.uniformInts(0, 10), G.uniformInts(15, 20), 5, 5).restricted(5, 18));
Asserts.assertNotNull(G.mixed(G.uniformInts(0, 10), G.uniformInts(15, 20), 5, 5).restricted(5, 7));
Asserts.assertNotNull(G.mixed(G.uniformInts(0, 10), G.uniformInts(15, 20), 5, 5).restricted(16, 18));
Asserts.assertThrows(EmptyGeneratorException.class, () -> G.mixed(
List.of(3, 4, 6),
G.uniformInts(0, 10), G.uniformInts(15, 20), G.uniformInts(30, 40)
).restricted(80, 83));
Asserts.assertNotNull(G.mixed(
List.of(3, 4, 6),
G.uniformInts(0, 10), G.uniformInts(15, 20), G.uniformInts(30, 40)
).restricted(10, 35));
Asserts.assertNotNull(G.mixed(
List.of(3, 4, 6),
G.uniformInts(0, 10), G.uniformInts(15, 20), G.uniformInts(30, 40)
).restricted(5, 8));
Asserts.assertNotNull(G.mixed(
List.of(3, 4, 6),
G.uniformInts(0, 10), G.uniformInts(15, 20), G.uniformInts(30, 40)
).restricted(17, 19));
Asserts.assertNotNull(G.mixed(
List.of(3, 4, 6),
G.uniformInts(0, 10), G.uniformInts(15, 20), G.uniformInts(30, 40)
).restricted(31, 38));
}
static void testSafeRestrict() {
// normal restrictions
mockSource.checkEmpty().enqueueInteger(4, 6, 4);
var g1 = mockGS.safeRestrict(mockGS.uniformInts(4, 5), 2, 5);
Asserts.assertEQ(g1.next(), 4);
mockSource.checkEmpty().enqueueLong(4, 6, 4);
var g2 = mockGS.safeRestrict(mockGS.uniformLongs(4, 5), 2, 5);
Asserts.assertEQ(g2.next(), 4L);
mockSource.checkEmpty().enqueueDouble(4, 5, 4);
var g3 = mockGS.safeRestrict(mockGS.uniformDoubles(4, 5), 2, 5);
Asserts.assertEQ(g3.next(), 4d);
mockSource.checkEmpty().enqueueFloat(4, 5, 4);
var g4 = mockGS.safeRestrict(mockGS.uniformFloats(4, 5), 2, 5);
Asserts.assertEQ(g4.next(), 4f);
// fallbacks
mockSource.checkEmpty().enqueueInteger(2, 6, 4);
var f1 = mockGS.safeRestrict(mockGS.uniformInts(0, 1), 2, 5);
Asserts.assertEQ(f1.next(), 4);
mockSource.checkEmpty().enqueueLong(2, 6, 4);
var f2 = mockGS.safeRestrict(mockGS.uniformLongs(0, 1), 2, 5);
Asserts.assertEQ(f2.next(), 4L);
mockSource.checkEmpty().enqueueDouble(2, 5, 4);
var f3 = mockGS.safeRestrict(mockGS.uniformDoubles(0, 1), 2, 5);
Asserts.assertEQ(f3.next(), 4d);
mockSource.checkEmpty().enqueueFloat(2, 5, 4);
var f4 = mockGS.safeRestrict(mockGS.uniformFloats(0, 1), 2, 5);
Asserts.assertEQ(f4.next(), 4f);
}
static void testFill() {
// All we need to test really is that fill calls the generators sequentially and correctly writes the values
// into the arrays. Since fill with arrays uses memory segments internally, these are also tested.
Generator<Double> doubleGen = new Generator<>() {
private double i = 1;
@Override
public Double next() {
i /= 2;
return i;
}
};
double[] doubles = new double[5];
mockGS.fill(doubleGen, doubles);
Asserts.assertTrue(Arrays.equals(doubles, (new double[] {0.5, 0.25, 0.125, 0.0625, 0.03125})));
Generator<Float> floatGen = new Generator<>() {
private float i = 1;
@Override
public Float next() {
i /= 2;
return i;
}
};
float[] floats = new float[5];
mockGS.fill(floatGen, floats);
Asserts.assertTrue(Arrays.equals(floats, (new float[] {0.5f, 0.25f, 0.125f, 0.0625f, 0.03125f})));
Generator<Long> longGen = new Generator<>() {
private long i = 1;
@Override
public Long next() {
return i++;
}
};
long[] longs = new long[5];
mockGS.fill(longGen, longs);
Asserts.assertTrue(Arrays.equals(longs, (new long[] {1, 2, 3, 4, 5})));
Generator<Integer> intGen = new Generator<>() {
private int i = 1;
@Override
public Integer next() {
return i++;
}
};
int[] ints = new int[5];
mockGS.fill(intGen, ints);
Asserts.assertTrue(Arrays.equals(ints, (new int[] {1, 2, 3, 4, 5})));
}
static void testFuzzy() {
var intBoundGen = G.uniformInts();
for (int j = 0; j < 500; j++) {
int a = intBoundGen.next(), b = intBoundGen.next();
int lo = Math.min(a, b), hi = Math.max(a, b);
RestrictableGenerator<Integer> gb;
try {
gb = G.ints().restricted(lo, hi);
} catch (EmptyGeneratorException e) {
continue;
}
for (int i = 0; i < 10_000; i++) {
int x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThanOrEqual(x, hi);
}
}
for (int j = 0; j < 500; j++) {
int a = intBoundGen.next(), b = intBoundGen.next();
int lo = Math.min(a, b), hi = Math.max(a, b);
var gb = G.uniformInts(lo, hi);
for (int i = 0; i < 10_000; i++) {
int x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThanOrEqual(x, hi);
}
}
var longBoundGen = G.uniformLongs();
for (int j = 0; j < 500; j++) {
long a = longBoundGen.next(), b = longBoundGen.next();
long lo = Math.min(a, b), hi = Math.max(a, b);
RestrictableGenerator<Long> gb;
try {
gb = G.longs().restricted(lo, hi);
} catch (EmptyGeneratorException e) {
continue;
}
for (int i = 0; i < 10_000; i++) {
long x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThanOrEqual(x, hi);
}
}
for (int j = 0; j < 500; j++) {
long a = longBoundGen.next(), b = longBoundGen.next();
long lo = Math.min(a, b), hi = Math.max(a, b);
var gb = G.uniformLongs(lo, hi);
for (int i = 0; i < 10_000; i++) {
long x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThanOrEqual(x, hi);
}
}
var floatBoundGen = G.uniformFloats();
for (int j = 0; j < 500; j++) {
float a = floatBoundGen.next(), b = floatBoundGen.next();
float lo = Math.min(a, b), hi = Math.max(a, b);
var gb = G.uniformFloats(lo, hi);
for (int i = 0; i < 10_000; i++) {
float x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThan(x, hi);
}
}
var doubleBoundGen = G.uniformDoubles();
for (int j = 0; j < 500; j++) {
double a = doubleBoundGen.next(), b = doubleBoundGen.next();
double lo = Math.min(a, b), hi = Math.max(a, b);
var gb = G.uniformDoubles(lo, hi);
for (int i = 0; i < 10_000; i++) {
double x = gb.next();
Asserts.assertGreaterThanOrEqual(x, lo);
Asserts.assertLessThan(x, hi);
}
}
}
}