diff --git a/test/hotspot/jtreg/compiler/lib/generators/AnyBitsDoubleGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/AnyBitsDoubleGenerator.java new file mode 100644 index 00000000000..2fcb01d69ff --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/AnyBitsDoubleGenerator.java @@ -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 { + /** + * Create a new {@link AnyBitsDoubleGenerator}. + */ + public AnyBitsDoubleGenerator(Generators g) { + super(g); + } + + @Override + public Double next() { + return Double.longBitsToDouble(g.random.nextLong()); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/AnyBitsFloatGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/AnyBitsFloatGenerator.java new file mode 100644 index 00000000000..f2f0b746284 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/AnyBitsFloatGenerator.java @@ -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 { + + /** + * Creates a new {@link AnyBitsFloatGenerator}. + */ + public AnyBitsFloatGenerator(Generators g) { + super(g); + } + + @Override + public Float next() { + return Float.intBitsToFloat(g.random.nextInt()); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/BoundGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/BoundGenerator.java new file mode 100644 index 00000000000..c4e6e9651e4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/BoundGenerator.java @@ -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 implements Generator { + Generators g; + + BoundGenerator(Generators g) { + this.g = g; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/EmptyGeneratorException.java b/test/hotspot/jtreg/compiler/lib/generators/EmptyGeneratorException.java new file mode 100644 index 00000000000..df9c0b47b71 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/EmptyGeneratorException.java @@ -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() {} +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/Generator.java b/test/hotspot/jtreg/compiler/lib/generators/Generator.java new file mode 100644 index 00000000000..e538d4b8925 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/Generator.java @@ -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 { + /** + * Returns the next value from the stream. + */ + T next(); +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/Generators.java b/test/hotspot/jtreg/compiler/lib/generators/Generators.java new file mode 100644 index 00000000000..16f1b6be9b1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/Generators.java @@ -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. + *

+ * Normally, clients get the default Generators instance by referring to the static variable {@link #G}. + *

+ * 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. + *

+ * Many distributions are restrictable. 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 h(int, int, int) 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: + * + *


+ * void test(Generator{@literal } g) {
+ *     h(g.restricted(1, 10).next(), g.next(), g.restricted(-10, 100).next());
+ * }
+ * 
+ * + * Then test can be called with different distributions, for example: + * + *

+ * test(G.uniformInts());
+ * test(G.specialInts(0));
+ * 
+ *

+ * 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: + * + *


+ * test(G.single(1));
+ * 
+ * + *

+ * 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. + *

+ * 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: + *

G.mixed(G.single(true), G.single(false), 60, 40)
+ *

+ * 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: + *

G.orderedRandomElement(List.of("Bob", "Alice", "Carol")).restricted("Al", "Bz")
+ * 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. + *

+ * 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. + *

+ * 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 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 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 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 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 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 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 anyBitsDouble() { + return new AnyBitsDoubleGenerator(this); + } + + /** + * Generates uniform doubles in the range of [lo, hi) (inclusive of lo, exclusive of hi). + */ + public RestrictableGenerator 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 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 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 Generator randomElement(Collection 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 > RestrictableGenerator orderedRandomElement(Collection list) { + NavigableSet set = list instanceof NavigableSet ? (NavigableSet) list : new TreeSet<>(list); + return new RestrictableRandomElementGenerator<>(this, set); + } + + /** + * Returns a generator that always generate the provided value. + */ + public Generator single(T value) { + return new SingleValueGenerator<>(value); + } + + /** + * Returns a restrictable generator that always generate the provided value. + */ + public > RestrictableGenerator 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 Generator mixed(Generator a, Generator 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 Generator mixed(List weights, Generator... 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 > RestrictableGenerator mixed(RestrictableGenerator a, RestrictableGenerator 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 > RestrictableGenerator mixed(List weights, RestrictableGenerator... generators) { + return new RestrictableMixedGenerator<>(this, Arrays.asList(generators), weights); + } + + /** + * Randomly pick an int generator. + * + * @return Random int generator. + */ + public RestrictableGenerator 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 powerOfTwoInts(int range) { + TreeSet 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 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 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 powerOfTwoLongs(int range) { + TreeSet 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 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 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 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 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 mixedWithSpecialDoubles(Generator 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 mixedWithSpecialDoubles(RestrictableGenerator 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 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 mixedWithSpecialFloats(Generator 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 mixedWithSpecialFloats(RestrictableGenerator 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 safeRestrict(RestrictableGenerator 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 safeRestrict(RestrictableGenerator 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 safeRestrict(RestrictableGenerator 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 safeRestrict(RestrictableGenerator 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 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 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 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 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 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 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 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 generator, long[] a) { + fillLong(generator, MemorySegment.ofArray(a)); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/MixedGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/MixedGenerator.java new file mode 100644 index 00000000000..4618964d1de --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/MixedGenerator.java @@ -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, T> extends BoundGenerator { + private final TreeMap 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 generators, List 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 other, Function 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(); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RandomElementGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/RandomElementGenerator.java new file mode 100644 index 00000000000..398e3f97888 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RandomElementGenerator.java @@ -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 extends BoundGenerator { + private final ArrayList elements; + private final Generator generator; + + RandomElementGenerator(Generators g, Collection 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()); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RandomnessSource.java b/test/hotspot/jtreg/compiler/lib/generators/RandomnessSource.java new file mode 100644 index 00000000000..2b09b92cf3d --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RandomnessSource.java @@ -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); +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RandomnessSourceAdapter.java b/test/hotspot/jtreg/compiler/lib/generators/RandomnessSourceAdapter.java new file mode 100644 index 00000000000..a8e62031cf6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RandomnessSourceAdapter.java @@ -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); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RestrictableGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/RestrictableGenerator.java new file mode 100644 index 00000000000..c1c9ef751f5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RestrictableGenerator.java @@ -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 extends Generator { + /** + * 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 restricted(T newLo, T newHi); +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RestrictableMixedGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/RestrictableMixedGenerator.java new file mode 100644 index 00000000000..066a5747213 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RestrictableMixedGenerator.java @@ -0,0 +1,24 @@ +package compiler.lib.generators; + +import java.util.List; + +final class RestrictableMixedGenerator> extends MixedGenerator, T> implements RestrictableGenerator { + RestrictableMixedGenerator(Generators g, List> generators, List weights) { + super(g, generators, weights); + } + + RestrictableMixedGenerator(RestrictableMixedGenerator other, T newLo, T newHi) { + super(other, (generator) -> { + try { + return generator.restricted(newLo, newHi); + } catch (EmptyGeneratorException e) { + return null; + } + }); + } + + @Override + public RestrictableGenerator restricted(T newLo, T newHi) { + return new RestrictableMixedGenerator<>(this, newLo, newHi); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RestrictableRandomElementGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/RestrictableRandomElementGenerator.java new file mode 100644 index 00000000000..4bbd9cdaf5b --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RestrictableRandomElementGenerator.java @@ -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> extends RandomElementGenerator implements RestrictableGenerator { + /* + * Pre-generated values we can choose from. Maintained for restriction. + */ + private final NavigableSet values; + + public RestrictableRandomElementGenerator(Generators g, NavigableSet values) { + super(g, values); + if (values.isEmpty()) throw new EmptyGeneratorException(); + this.values = values; + } + + @Override + public RestrictableGenerator restricted(T newLo, T newHi) { + return new RestrictableRandomElementGenerator<>(g, values.subSet(newLo, true, newHi, true)); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/RestrictableSingleValueGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/RestrictableSingleValueGenerator.java new file mode 100644 index 00000000000..4446788a850 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/RestrictableSingleValueGenerator.java @@ -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> implements RestrictableGenerator { + private final T value; + + RestrictableSingleValueGenerator(T value) { + this.value = value; + } + + @Override + public RestrictableGenerator 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; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/SingleValueGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/SingleValueGenerator.java new file mode 100644 index 00000000000..909ee806e7a --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/SingleValueGenerator.java @@ -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 implements Generator { + private final T value; + + SingleValueGenerator(T value) { + this.value = value; + } + + @Override + public T next() { + return this.value; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/UniformDoubleGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/UniformDoubleGenerator.java new file mode 100644 index 00000000000..d160bf319d8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/UniformDoubleGenerator.java @@ -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 { + /** + * 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 doRestrictionFromIntersection(Double lo, Double hi) { + return new UniformDoubleGenerator(g, lo, hi); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/UniformFloatGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/UniformFloatGenerator.java new file mode 100644 index 00000000000..1b72ad5adc9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/UniformFloatGenerator.java @@ -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 { + /** + * 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 doRestrictionFromIntersection(Float lo, Float hi) { + return new UniformFloatGenerator(g, lo, hi); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/UniformIntGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/UniformIntGenerator.java new file mode 100644 index 00000000000..86eb3224c4f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/UniformIntGenerator.java @@ -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 { + 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 doRestrictionFromIntersection(Integer lo, Integer hi) { + return new UniformIntGenerator(g, lo, hi); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/UniformIntersectionRestrictableGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/UniformIntersectionRestrictableGenerator.java new file mode 100644 index 00000000000..1554f90eb82 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/UniformIntersectionRestrictableGenerator.java @@ -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> extends BoundGenerator implements RestrictableGenerator { + 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 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 doRestrictionFromIntersection(T lo, T hi); + + T hi() { return hi; } + T lo() { return lo; } +} diff --git a/test/hotspot/jtreg/compiler/lib/generators/UniformLongGenerator.java b/test/hotspot/jtreg/compiler/lib/generators/UniformLongGenerator.java new file mode 100644 index 00000000000..1cfd513962f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/generators/UniformLongGenerator.java @@ -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 { + 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 doRestrictionFromIntersection(Long lo, Long hi) { + return new UniformLongGenerator(g, lo, hi); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/generators/tests/ExampleTest.java b/test/hotspot/jtreg/testlibrary_tests/generators/tests/ExampleTest.java new file mode 100644 index 00000000000..3609d6e3567 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/generators/tests/ExampleTest.java @@ -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 g) { + UnderTest underTest = new UnderTest(); + for (int i = 0; i < 70_000_000 * 3; i++) { + underTest.doIt(g.next()); + } + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/generators/tests/MockRandomnessSource.java b/test/hotspot/jtreg/testlibrary_tests/generators/tests/MockRandomnessSource.java new file mode 100644 index 00000000000..c8d5f325706 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/generators/tests/MockRandomnessSource.java @@ -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 lo, T hi, T value) {} + + private final Queue unboundedLongQueue = new ArrayDeque<>(); + private final Queue unboundedIntegerQueue = new ArrayDeque<>(); + private final Queue> boundedLongQueue = new ArrayDeque<>(); + private final Queue> boundedIntegerQueue = new ArrayDeque<>(); + private final Queue> boundedDoubleQueue = new ArrayDeque<>(); + private final Queue> boundedFloatQueue = new ArrayDeque<>(); + + private T dequeueBounded(Queue> queue, T lo, T hi) { + Bounded 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); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/generators/tests/TestGenerators.java b/test/hotspot/jtreg/testlibrary_tests/generators/tests/TestGenerators.java new file mode 100644 index 00000000000..203256ad7b8 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/generators/tests/TestGenerators.java @@ -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())); + 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 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 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 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 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 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 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); + } + } + } +}