Skip to content

Commit 0448df4

Browse files
committed
Add utility generator classes.
1 parent 5bf7cf4 commit 0448df4

24 files changed

Lines changed: 1186 additions & 10 deletions

etc/junit4-missing-features.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
11. Nightly / scaled tests
44
- @Nightly marks a test that only runs when nightly mode is active
55
(-Dtests.nightly=true)
6-
- scaledRandomIntBetween() and multiplier() scale input sizes based on
6+
- scaledrandomIntInRange() and multiplier() scale input sizes based on
77
nightly vs. daily mode
88

99
2. RandomizedTest base class and RandomizedContext
1010
- Extend RandomizedTest for convenient access to a per-test Random instance
1111
- Access the context directly via RandomizedContext.current()
1212

1313
3. Randomized input generation
14-
- Utility methods on RandomizedTest: randomInt(), randomIntBetween(),
14+
- Utility methods on RandomizedTest: randomInt(), randomIntInRange(),
1515
randomBoolean(), randomFloat(), etc.
1616
- Encourages testing over a broad input domain rather than fixed values
1717

1818
* utility rules (require assertions, etc.)
1919

20+
* reproduce-failure line info listener?
21+
2022
[possibly doable with a custom test engine]
2123

2224
- predictably shuffled test execution order
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.carrotsearch.randomizedtesting.jupiter;
2+
3+
public final class Constants {
4+
private Constants() {}
5+
6+
/**
7+
* Synthetic class used to augment stack traces and expose the seed in exceptions thrown while
8+
* running randomized tests.
9+
*/
10+
public static final String AUGMENTED_SEED_CLASS = "__randomizedtesting.SeedChain";
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.carrotsearch.randomizedtesting.jupiter.generators;
2+
3+
/**
4+
* A generator emitting simple ASCII alphanumeric letters and numbers from the set (newlines not
5+
* counted):
6+
*
7+
* <pre>
8+
* abcdefghijklmnopqrstuvwxyz
9+
* ABCDEFGHIJKLMNOPQRSTUVWXYZ
10+
* 0123456789
11+
* </pre>
12+
*/
13+
public class AsciiAlphanumGenerator extends CodepointSetGenerator {
14+
private static final char[] CHARS =
15+
("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789").toCharArray();
16+
17+
public AsciiAlphanumGenerator() {
18+
super(CHARS);
19+
}
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.carrotsearch.randomizedtesting.jupiter.generators;
2+
3+
/**
4+
* A generator emitting simple ASCII characters from the set (newlines not counted):
5+
*
6+
* <pre>
7+
* abcdefghijklmnopqrstuvwxyz
8+
* ABCDEFGHIJKLMNOPQRSTUVWXYZ
9+
* </pre>
10+
*/
11+
public class AsciiLettersGenerator extends CodepointSetGenerator {
12+
private static final char[] CHARS =
13+
("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
14+
15+
public AsciiLettersGenerator() {
16+
super(CHARS);
17+
}
18+
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package com.carrotsearch.randomizedtesting.jupiter.generators;
2+
3+
import java.util.Random;
4+
5+
/**
6+
* Utility classes for selecting numbers at random, but not necessarily in a uniform way. The
7+
* implementation will try to pick "evil" numbers more often than uniform selection would. This
8+
* includes exact range boundaries, numbers very close to range boundaries, numbers very close (or
9+
* equal) to zero, etc.
10+
*
11+
* <p>The exact method of selection is implementation-dependent and may change (if we find even more
12+
* evil ways).
13+
*/
14+
public final class BiasedNumbers {
15+
private static final int EVIL_RANGE_LEFT = 1;
16+
private static final int EVIL_RANGE_RIGHT = 1;
17+
private static final int EVIL_VERY_CLOSE_RANGE_ENDS = 20;
18+
private static final int EVIL_ZERO_OR_NEAR = 5;
19+
private static final int EVIL_SIMPLE_PROPORTION = 10;
20+
private static final int EVIL_RANDOM_REPRESENTATION_BITS = 10;
21+
22+
/**
23+
* A random double between <code>min</code> (inclusive) and <code>max</code> (inclusive). If you
24+
* wish to have an exclusive range, use {@link Math#nextAfter(double, double)} to adjust the
25+
* range.
26+
*
27+
* <p>The code was inspired by GeoTestUtil from Apache Lucene.
28+
*
29+
* @param min Left range boundary, inclusive. May be {@link Double#NEGATIVE_INFINITY}, but not
30+
* NaN.
31+
* @param max Right range boundary, inclusive. May be {@link Double#POSITIVE_INFINITY}, but not
32+
* NaN.
33+
*/
34+
public static double randomDoubleInRange(Random r, double min, double max) {
35+
assert max >= min : "max must be >= min: " + min + ", " + max;
36+
assert !Double.isNaN(min) && !Double.isNaN(max);
37+
38+
boolean hasZero = min <= 0 && max >= 0;
39+
40+
int pick =
41+
r.nextInt(
42+
EVIL_RANGE_LEFT
43+
+ EVIL_RANGE_RIGHT
44+
+ EVIL_VERY_CLOSE_RANGE_ENDS
45+
+ (hasZero ? EVIL_ZERO_OR_NEAR : 0)
46+
+ EVIL_SIMPLE_PROPORTION
47+
+ EVIL_RANDOM_REPRESENTATION_BITS);
48+
49+
// Exact range ends
50+
pick -= EVIL_RANGE_LEFT;
51+
if (pick < 0 || min == max) {
52+
return min;
53+
}
54+
55+
pick -= EVIL_RANGE_RIGHT;
56+
if (pick < 0) {
57+
return max;
58+
}
59+
60+
// If we're dealing with infinities, adjust them to discrete values.
61+
assert min != max;
62+
if (Double.isInfinite(min)) {
63+
min = Math.nextUp(min);
64+
}
65+
if (Double.isInfinite(max)) {
66+
max = Math.nextAfter(max, Double.NEGATIVE_INFINITY);
67+
}
68+
69+
// Numbers "very" close to range ends. "very" means a few floating point
70+
// representation steps (ulps) away.
71+
pick -= EVIL_VERY_CLOSE_RANGE_ENDS;
72+
if (pick < 0) {
73+
if (r.nextBoolean()) {
74+
return fuzzUp(r, min, max);
75+
} else {
76+
return fuzzDown(r, max, min);
77+
}
78+
}
79+
80+
// Zero or near-zero values, if within the range.
81+
if (hasZero) {
82+
pick -= EVIL_ZERO_OR_NEAR;
83+
if (pick < 0) {
84+
int v = r.nextInt(4);
85+
if (v == 0) {
86+
return 0d;
87+
} else if (v == 1) {
88+
return -0.0d;
89+
} else if (v == 2) {
90+
return fuzzDown(r, 0d, min);
91+
} else if (v == 3) {
92+
return fuzzUp(r, 0d, max);
93+
}
94+
}
95+
}
96+
97+
// Simple proportional selection.
98+
pick -= EVIL_SIMPLE_PROPORTION;
99+
if (pick < 0) {
100+
return min + (max - min) * r.nextDouble();
101+
}
102+
103+
// Random representation space selection. This will be heavily biased
104+
// and overselect from the set of tiny values, if they're allowed.
105+
pick -= EVIL_RANDOM_REPRESENTATION_BITS;
106+
if (pick < 0) {
107+
long from = toSortable(min);
108+
long to = toSortable(max);
109+
return fromSortable(RandomNumbers.randomLongInRange(r, from, to));
110+
}
111+
112+
throw new RuntimeException("Unreachable.");
113+
}
114+
115+
/** Fuzzify the input value by decreasing it by a few ulps, but never past min. */
116+
public static double fuzzDown(Random r, double v, double min) {
117+
assert v >= min;
118+
for (int steps = RandomNumbers.randomIntInRange(r, 1, 10); steps > 0 && v > min; steps--) {
119+
v = Math.nextAfter(v, Double.NEGATIVE_INFINITY);
120+
}
121+
return v;
122+
}
123+
124+
/** Fuzzify the input value by increasing it by a few ulps, but never past max. */
125+
public static double fuzzUp(Random r, double v, double max) {
126+
assert v <= max;
127+
for (int steps = RandomNumbers.randomIntInRange(r, 1, 10); steps > 0 && v < max; steps--) {
128+
v = Math.nextUp(v);
129+
}
130+
return v;
131+
}
132+
133+
private static double fromSortable(long sortable) {
134+
return Double.longBitsToDouble(flip(sortable));
135+
}
136+
137+
private static long toSortable(double value) {
138+
return flip(Double.doubleToLongBits(value));
139+
}
140+
141+
private static long flip(long bits) {
142+
return bits ^ (bits >> 63) & 0x7fffffffffffffffL;
143+
}
144+
145+
/**
146+
* A random float between <code>min</code> (inclusive) and <code>max</code> (inclusive). If you
147+
* wish to have an exclusive range, use {@link Math#nextAfter(float, double)} to adjust the range.
148+
*
149+
* <p>The code was inspired by GeoTestUtil from Apache Lucene.
150+
*
151+
* @param min Left range boundary, inclusive. May be {@link Float#NEGATIVE_INFINITY}, but not NaN.
152+
* @param max Right range boundary, inclusive. May be {@link Float#POSITIVE_INFINITY}, but not
153+
* NaN.
154+
*/
155+
public static float randomFloatInRange(Random r, float min, float max) {
156+
assert max >= min : "max must be >= min: " + min + ", " + max;
157+
assert !Float.isNaN(min) && !Float.isNaN(max);
158+
159+
boolean hasZero = min <= 0 && max >= 0;
160+
161+
int pick =
162+
r.nextInt(
163+
EVIL_RANGE_LEFT
164+
+ EVIL_RANGE_RIGHT
165+
+ EVIL_VERY_CLOSE_RANGE_ENDS
166+
+ (hasZero ? EVIL_ZERO_OR_NEAR : 0)
167+
+ EVIL_SIMPLE_PROPORTION
168+
+ EVIL_RANDOM_REPRESENTATION_BITS);
169+
170+
// Exact range ends
171+
pick -= EVIL_RANGE_LEFT;
172+
if (pick < 0 || min == max) {
173+
return min;
174+
}
175+
176+
pick -= EVIL_RANGE_RIGHT;
177+
if (pick < 0) {
178+
return max;
179+
}
180+
181+
// If we're dealing with infinities, adjust them to discrete values.
182+
assert min != max;
183+
if (Float.isInfinite(min)) {
184+
min = Math.nextUp(min);
185+
}
186+
if (Float.isInfinite(max)) {
187+
max = Math.nextAfter(max, Double.NEGATIVE_INFINITY);
188+
}
189+
190+
// Numbers "very" close to range ends. "very" means a few floating point
191+
// representation steps (ulps) away.
192+
pick -= EVIL_VERY_CLOSE_RANGE_ENDS;
193+
if (pick < 0) {
194+
if (r.nextBoolean()) {
195+
return fuzzUp(r, min, max);
196+
} else {
197+
return fuzzDown(r, max, min);
198+
}
199+
}
200+
201+
// Zero or near-zero values, if within the range.
202+
if (hasZero) {
203+
pick -= EVIL_ZERO_OR_NEAR;
204+
if (pick < 0) {
205+
int v = r.nextInt(4);
206+
if (v == 0) {
207+
return 0f;
208+
} else if (v == 1) {
209+
return -0.0f;
210+
} else if (v == 2) {
211+
return fuzzDown(r, 0f, min);
212+
} else if (v == 3) {
213+
return fuzzUp(r, 0f, max);
214+
}
215+
}
216+
}
217+
218+
// Simple proportional selection.
219+
pick -= EVIL_SIMPLE_PROPORTION;
220+
if (pick < 0) {
221+
return (float) (min + (((double) max - min) * r.nextDouble()));
222+
}
223+
224+
// Random representation space selection. This will be heavily biased
225+
// and overselect from the set of tiny values, if they're allowed.
226+
pick -= EVIL_RANDOM_REPRESENTATION_BITS;
227+
if (pick < 0) {
228+
int from = toSortable(min);
229+
int to = toSortable(max);
230+
return fromSortable(RandomNumbers.randomIntInRange(r, from, to));
231+
}
232+
233+
throw new RuntimeException("Unreachable.");
234+
}
235+
236+
/** Fuzzify the input value by decreasing it by a few ulps, but never past min. */
237+
public static float fuzzDown(Random r, float v, float min) {
238+
assert v >= min;
239+
for (int steps = RandomNumbers.randomIntInRange(r, 1, 10); steps > 0 && v > min; steps--) {
240+
v = Math.nextAfter(v, Double.NEGATIVE_INFINITY);
241+
}
242+
return v;
243+
}
244+
245+
/** Fuzzify the input value by increasing it by a few ulps, but never past max. */
246+
public static float fuzzUp(Random r, float v, float max) {
247+
assert v <= max;
248+
for (int steps = RandomNumbers.randomIntInRange(r, 1, 10); steps > 0 && v < max; steps--) {
249+
v = Math.nextUp(v);
250+
}
251+
return v;
252+
}
253+
254+
private static float fromSortable(int sortable) {
255+
return Float.intBitsToFloat(flip(sortable));
256+
}
257+
258+
private static int toSortable(float value) {
259+
return flip(Float.floatToIntBits(value));
260+
}
261+
262+
private static int flip(int floatBits) {
263+
return floatBits ^ (floatBits >> 31) & 0x7fffffff;
264+
}
265+
}

0 commit comments

Comments
 (0)