Feature/randomutils secure() and insecure() (#1250)

* Add testLang1641()

* Rename some test methods

* Refactor RandomUtils and RandomStringUtils with secure() and insecure()
instances
This commit is contained in:
Gary Gregory 2024-07-25 19:38:15 -04:00 committed by GitHub
parent 12c71a019f
commit c95ee37dff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1468 additions and 470 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,23 +16,78 @@
*/
package org.apache.commons.lang3;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
import org.apache.commons.lang3.exception.UncheckedException;
/**
* Utility library that supplements the standard {@link Random} class.
* Supplements the standard {@link Random} class.
* <p>
* Use {@link #secure()} to get the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an
* algorithms/providers specified in the {@code securerandom.strongAlgorithms} {@link Security} property.
* </p>
* <p>
* Use {@link #insecure()} to get the singleton instance based on {@link ThreadLocalRandom#current()}; <b>which is not
* cryptographically secure</b>.
* </p>
* <p>
* Starting in version 3.15.0, this class uses {@link SecureRandom#getInstanceStrong()} for static methods.
* </p>
* <p>
* Starting in version 3.16.0, this class uses {@link #secure()} for static methods and adds {@link #insecure()}.
* </p>
* <p>
* Before version 3.15.0, this class used {@link ThreadLocalRandom#current()} for static methods, which is not
* cryptographically secure.
* </p>
* <p>
* Please note that the Apache Commons project provides a component dedicated to pseudo-random number generation, namely
* <a href="https://commons.apache.org/proper/commons-rng/">Commons RNG</a>, that may be a better choice for
* applications with more stringent requirements (performance and/or correctness).
* </p>
*
* <p>Please note that the Apache Commons project provides a component
* dedicated to pseudo-random number generation, namely
* <a href="https://commons.apache.org/proper/commons-rng/">Commons RNG</a>, that may be
* a better choice for applications with more stringent requirements
* (performance and/or correctness).</p>
*
* @deprecated Use Apache Commons RNG's optimized <a href="https://commons.apache.org/proper/commons-rng/commons-rng-client-api/apidocs/org/apache/commons/rng/UniformRandomProvider.html">UniformRandomProvider</a>
* @see RandomStringUtils
* @since 3.3
*/
@Deprecated
public class RandomUtils {
private static RandomUtils INSECURE = new RandomUtils(ThreadLocalRandom::current);
private static final Supplier<Random> SECURE_SUPPLIER = () -> RandomUtils.SECURE_RANDOM.get();
private static RandomUtils SECURE = new RandomUtils(SECURE_SUPPLIER);
private static final ThreadLocal<SecureRandom> SECURE_RANDOM = ThreadLocal.withInitial(() -> {
try {
return SecureRandom.getInstanceStrong();
} catch (final NoSuchAlgorithmException e) {
throw new UncheckedException(e);
}
});
/**
* Gets the singleton instance based on {@link ThreadLocalRandom#current()}; <b>which is not cryptographically
* secure</b>; use {@link #secure()} to use an algorithms/providers specified in the
* {@code securerandom.strongAlgorithms} {@link Security} property.
* </p>
* <p>
* The method {@link ThreadLocalRandom#current()} is called on-demand.
* </p>
*
* @return the singleton instance based on {@link ThreadLocalRandom#current()}.
* @see ThreadLocalRandom#current()
* @see #secure()
* @since 3.16.0
*/
static RandomUtils insecure() {
return INSECURE;
}
/**
* Generates a random boolean value.
*
@ -40,22 +95,18 @@ public class RandomUtils {
* @since 3.5
*/
public static boolean nextBoolean() {
return RandomStringUtils.random().nextBoolean();
return secure().randomBoolean();
}
/**
* Generates an array of random bytes.
*
* @param count
* the size of the returned array
* @param count the size of the returned array
* @return the random byte array
* @throws IllegalArgumentException if {@code count} is negative
*/
public static byte[] nextBytes(final int count) {
Validate.isTrue(count >= 0, "Count cannot be negative.");
final byte[] result = new byte[count];
RandomStringUtils.random().nextBytes(result);
return result;
return secure().randomBytes(count);
}
/**
@ -66,29 +117,20 @@ public class RandomUtils {
* @since 3.5
*/
public static double nextDouble() {
return nextDouble(0, Double.MAX_VALUE);
return secure().randomDouble();
}
/**
* Generates a random double within the specified range.
*
* @param startInclusive
* the smallest value that can be returned, must be non-negative
* @param endExclusive
* the upper bound (not included)
* @throws IllegalArgumentException
* if {@code startInclusive > endExclusive} or if
* {@code startInclusive} is negative
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random double
*/
public static double nextDouble(final double startInclusive, final double endExclusive) {
Validate.isTrue(endExclusive >= startInclusive,
"Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + (endExclusive - startInclusive) * RandomStringUtils.random().nextDouble();
return secure().randomDouble(startInclusive, endExclusive);
}
/**
@ -99,29 +141,20 @@ public class RandomUtils {
* @since 3.5
*/
public static float nextFloat() {
return nextFloat(0, Float.MAX_VALUE);
return secure().randomFloat();
}
/**
* Generates a random float within the specified range.
*
* @param startInclusive
* the smallest value that can be returned, must be non-negative
* @param endExclusive
* the upper bound (not included)
* @throws IllegalArgumentException
* if {@code startInclusive > endExclusive} or if
* {@code startInclusive} is negative
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random float
*/
public static float nextFloat(final float startInclusive, final float endExclusive) {
Validate.isTrue(endExclusive >= startInclusive,
"Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + (endExclusive - startInclusive) * RandomStringUtils.random().nextFloat();
return secure().randomFloat(startInclusive, endExclusive);
}
/**
@ -132,29 +165,20 @@ public class RandomUtils {
* @since 3.5
*/
public static int nextInt() {
return nextInt(0, Integer.MAX_VALUE);
return secure().randomInt();
}
/**
* Generates a random integer within the specified range.
*
* @param startInclusive
* the smallest value that can be returned, must be non-negative
* @param endExclusive
* the upper bound (not included)
* @throws IllegalArgumentException
* if {@code startInclusive > endExclusive} or if
* {@code startInclusive} is negative
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random integer
*/
public static int nextInt(final int startInclusive, final int endExclusive) {
Validate.isTrue(endExclusive >= startInclusive,
"Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + RandomStringUtils.random().nextInt(endExclusive - startInclusive);
return secure().randomInt(startInclusive, endExclusive);
}
/**
@ -165,24 +189,213 @@ public class RandomUtils {
* @since 3.5
*/
public static long nextLong() {
return secure().randomLong();
}
/**
* Generates a {@code long} value between 0 (inclusive) and the specified value (exclusive).
*
* @param n Bound on the random number to be returned. Must be positive.
* @return a random {@code long} value between 0 (inclusive) and {@code n} (exclusive).
*/
private static long nextLong(final long n) {
return secure().randomLong(n);
}
/**
* Generates a random long within the specified range.
*
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random long
*/
public static long nextLong(final long startInclusive, final long endExclusive) {
return secure().randomLong(startInclusive, endExclusive);
}
/**
* Gets the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an algorithms/providers
* specified in the {@code securerandom.strongAlgorithms} {@link Security} property.
* <p>
* The method {@link SecureRandom#getInstanceStrong()} is called on-demand.
* </p>
*
* @return the singleton instance based on {@link SecureRandom#getInstanceStrong()}.
* @see SecureRandom#getInstanceStrong()
* @since 3.16.0
*/
public static RandomUtils secure() {
return SECURE;
}
static SecureRandom secureRandom() {
return SECURE_RANDOM.get();
}
private final Supplier<Random> random;
/**
* {@link RandomUtils} instances should NOT be constructed in standard programming. Instead, the class should be
* used as {@code RandomUtils.nextBytes(5);}.
* <p>
* This constructor is public to permit tools that require a JavaBean instance to operate.
* </p>
*
* @deprecated TODO Make private in 4.0.
*/
@Deprecated
public RandomUtils() {
this(SECURE_SUPPLIER);
}
private RandomUtils(final Supplier<Random> random) {
this.random = random;
}
Random random() {
return random.get();
}
/**
* Generates a random boolean value.
*
* @return the random boolean
* @since 3.16.0
*/
public boolean randomBoolean() {
return random().nextBoolean();
}
/**
* Generates an array of random bytes.
*
* @param count the size of the returned array
* @return the random byte array
* @throws IllegalArgumentException if {@code count} is negative
* @since 3.16.0
*/
public byte[] randomBytes(final int count) {
Validate.isTrue(count >= 0, "Count cannot be negative.");
final byte[] result = new byte[count];
random().nextBytes(result);
return result;
}
/**
* Generates a random double between 0 (inclusive) and Double.MAX_VALUE (exclusive).
*
* @return the random double
* @see #nextDouble(double, double)
* @since 3.16.0
*/
public double randomDouble() {
return nextDouble(0, Double.MAX_VALUE);
}
/**
* Generates a random double within the specified range.
*
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random double
* @since 3.16.0
*/
public double randomDouble(final double startInclusive, final double endExclusive) {
Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + (endExclusive - startInclusive) * random().nextDouble();
}
/**
* Generates a random float between 0 (inclusive) and Float.MAX_VALUE (exclusive).
*
* @return the random float
* @see #nextFloat(float, float)
* @since 3.16.0
*/
public float randomFloat() {
return nextFloat(0, Float.MAX_VALUE);
}
/**
* Generates a random float within the specified range.
*
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random float
*/
public float randomFloat(final float startInclusive, final float endExclusive) {
Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + (endExclusive - startInclusive) * random().nextFloat();
}
/**
* Generates a random int between 0 (inclusive) and Integer.MAX_VALUE (exclusive).
*
* @return the random integer
* @see #nextInt(int, int)
* @since 3.16.0
*/
public int randomInt() {
return nextInt(0, Integer.MAX_VALUE);
}
/**
* Generates a random integer within the specified range.
*
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random integer
* @since 3.16.0
*/
public int randomInt(final int startInclusive, final int endExclusive) {
Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
}
return startInclusive + random().nextInt(endExclusive - startInclusive);
}
/**
* Generates a random long between 0 (inclusive) and Long.MAX_VALUE (exclusive).
*
* @return the random long
* @see #nextLong(long, long)
* @since 3.16.0
*/
public long randomLong() {
return nextLong(Long.MAX_VALUE);
}
/**
* Generates a {@code long} value between 0 (inclusive) and the specified
* value (exclusive).
* Generates a {@code long} value between 0 (inclusive) and the specified value (exclusive).
*
* @param n Bound on the random number to be returned. Must be positive.
* @return a random {@code long} value between 0 (inclusive) and {@code n}
* (exclusive).
* @param n Bound on the random number to be returned. Must be positive.
* @return a random {@code long} value between 0 (inclusive) and {@code n} (exclusive).
*/
private static long nextLong(final long n) {
private long randomLong(final long n) {
// Extracted from o.a.c.rng.core.BaseProvider.nextLong(long)
long bits;
long val;
do {
bits = RandomStringUtils.random().nextLong() >>> 1;
val = bits % n;
bits = random().nextLong() >>> 1;
val = bits % n;
} while (bits - val + n - 1 < 0);
return val;
}
@ -190,18 +403,15 @@ public class RandomUtils {
/**
* Generates a random long within the specified range.
*
* @param startInclusive
* the smallest value that can be returned, must be non-negative
* @param endExclusive
* the upper bound (not included)
* @throws IllegalArgumentException
* if {@code startInclusive > endExclusive} or if
* {@code startInclusive} is negative
* @param startInclusive the smallest value that can be returned, must be non-negative
* @param endExclusive the upper bound (not included)
* @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is
* negative
* @return the random long
* @since 3.16.0
*/
public static long nextLong(final long startInclusive, final long endExclusive) {
Validate.isTrue(endExclusive >= startInclusive,
"Start value must be smaller or equal to end value.");
public long randomLong(final long startInclusive, final long endExclusive) {
Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
if (startInclusive == endExclusive) {
return startInclusive;
@ -209,19 +419,9 @@ public class RandomUtils {
return startInclusive + nextLong(endExclusive - startInclusive);
}
/**
* {@link RandomUtils} instances should NOT be constructed in standard
* programming. Instead, the class should be used as
* {@code RandomUtils.nextBytes(5);}.
* <p>
* This constructor is public to permit tools that require a JavaBean
* instance to operate.
* </p>
*
* @deprecated TODO Make private in 4.0.
*/
@Deprecated
public RandomUtils() {
// empty
@Override
public String toString() {
return "RandomUtils [random=" + random() + "]";
}
}

View File

@ -24,19 +24,20 @@ import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests {@link RandomStringUtils}.
@ -45,6 +46,10 @@ public class RandomStringUtilsTest extends AbstractLangTest {
private static final int LOOP_COUNT = 1_000;
static Stream<RandomStringUtils> randomProvider() {
return Stream.of(RandomStringUtils.secure(), RandomStringUtils.insecure());
}
/**
* Computes Chi-Square statistic given observed and expected counts
*
@ -85,11 +90,6 @@ public class RandomStringUtilsTest extends AbstractLangTest {
@Test
public void testConstructor() {
assertNotNull(new RandomStringUtils());
final Constructor<?>[] cons = RandomStringUtils.class.getDeclaredConstructors();
assertEquals(1, cons.length);
assertTrue(Modifier.isPublic(cons[0].getModifiers()));
assertTrue(Modifier.isPublic(RandomStringUtils.class.getModifiers()));
assertFalse(Modifier.isFinal(RandomStringUtils.class.getModifiers()));
}
@Test
@ -108,31 +108,118 @@ public class RandomStringUtilsTest extends AbstractLangTest {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, Integer.MIN_VALUE, -10, false, false, null));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandom(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, true, true));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, new char[] { 'a' }));
assertThrows(IllegalArgumentException.class, () -> rsu.next(1, new char[0]));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, ""));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, (String) null));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, 'a', 'z', false, false));
assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, 'a', 'z', false, false, new char[] { 'a' }));
assertThrows(IllegalArgumentException.class, () -> rsu.next(8, 32, 48, false, true));
assertThrows(IllegalArgumentException.class, () -> rsu.next(8, 32, 65, true, false));
assertThrows(IllegalArgumentException.class, () -> rsu.next(1, Integer.MIN_VALUE, -10, false, false, null));
}
@Test
public void testExceptionsRandomAlphabetic() {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAlphabetic(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandomAlphabetic(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.nextAlphabetic(-1));
}
@Test
public void testExceptionsRandomAscii() {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAscii(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandomAscii(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.nextAscii(-1));
}
@Test
public void testExceptionsRandomGraph() {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomGraph(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandomGraph(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.nextGraph(-1));
}
@Test
public void testExceptionsRandomNumeric() {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomNumeric(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandomNumeric(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.nextNumeric(-1));
}
@Test
public void testExceptionsRandomPrint() {
assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomPrint(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExceptionsRandomPrint(final RandomStringUtils rsu) {
assertThrows(IllegalArgumentException.class, () -> rsu.nextPrint(-1));
}
/**
* Test homogeneity of random strings generated -- i.e., test that characters show up with expected frequencies in generated strings. Will fail randomly
* about 1 in 100,000 times. Repeated failures indicate a problem.
*
* @param rsu the instance to test.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testHomogeneity(final RandomStringUtils rsu) {
final String set = "abc";
final char[] chars = set.toCharArray();
final int[] counts = { 0, 0, 0 };
final int[] expected = { 200, 200, 200 };
for (int i = 0; i < 100; i++) {
final String gen = rsu.next(6, chars);
for (int j = 0; j < 6; j++) {
switch (gen.charAt(j)) {
case 'a': {
counts[0]++;
break;
}
case 'b': {
counts[1]++;
break;
}
case 'c': {
counts[2]++;
break;
}
default: {
fail("generated character not in set");
}
}
}
}
// Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level.
// This expects a failure rate of 1 in 100,000.
// critical value: from scipy.stats import chi2; chi2(2).isf(1e-5)
assertThat("test homogeneity -- will fail about 1 in 100,000 times", chiSquare(expected, counts), lessThan(23.025850929940457d));
}
/**
* Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss.
*
@ -159,15 +246,44 @@ public class RandomStringUtilsTest extends AbstractLangTest {
assertEquals(orig, copy);
}
/**
* Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss.
*
* @param rsu the instance to test
* @see <a href="https://issues.apache.org/jira/browse/LANG-100">LANG-100</a>
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testLang100(final RandomStringUtils rsu) {
final int size = 5000;
final Charset charset = StandardCharsets.UTF_8;
final String orig = rsu.next(size);
final byte[] bytes = orig.getBytes(charset);
final String copy = new String(bytes, charset);
// for a verbose compare:
for (int i = 0; i < orig.length() && i < copy.length(); i++) {
final char o = orig.charAt(i);
final char c = copy.charAt(i);
assertEquals(o, c, "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + ","
+ Integer.toHexString(Character.valueOf(c).hashCode()) + ")");
}
// compare length also
assertEquals(orig.length(), copy.length());
// just to be complete
assertEquals(orig, copy);
}
@Test
public void testLANG805() {
final long seedMillis = System.currentTimeMillis();
assertEquals("aaa", RandomStringUtils.random(3, 0, 0, false, false, new char[] { 'a' }, new Random(seedMillis)));
}
@Test
public void testLANG807() {
final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(3, 5, 5, false, false));
@ParameterizedTest
@MethodSource("randomProvider")
public void testLANG807(final RandomStringUtils rsu) {
final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> rsu.next(3, 5, 5, false, false));
final String msg = ex.getMessage();
assertTrue(msg.contains("start"), "Message (" + msg + ") must contain 'start'");
assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'");
@ -193,6 +309,29 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
}
/**
* Make sure boundary alpha characters are generated by randomAlphabetic This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
*
* @param rsu the instance to test
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomAlphabetic(final RandomStringUtils rsu) {
final char[] testChars = { 'a', 'z', 'A', 'Z' };
final boolean[] found = { false, false, false, false };
for (int i = 0; i < LOOP_COUNT; i++) {
final String randString = rsu.nextAlphabetic(10);
for (int j = 0; j < testChars.length; j++) {
if (randString.indexOf(testChars[j]) > 0) {
found[j] = true;
}
}
}
for (int i = 0; i < testChars.length; i++) {
assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
}
}
@Test
public void testRandomAlphabeticRange() {
final int expectedMinLengthInclusive = 1;
@ -218,6 +357,32 @@ public class RandomStringUtilsTest extends AbstractLangTest {
assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomAlphabeticRange(final RandomStringUtils rsu) {
final int expectedMinLengthInclusive = 1;
final int expectedMaxLengthExclusive = 11;
final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
int maxCreatedLength = expectedMinLengthInclusive;
int minCreatedLength = expectedMaxLengthExclusive - 1;
for (int i = 0; i < LOOP_COUNT; i++) {
final String s = rsu.nextAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive);
assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
assertTrue(s.matches(pattern), s);
if (s.length() < minCreatedLength) {
minCreatedLength = s.length();
}
if (s.length() > maxCreatedLength) {
maxCreatedLength = s.length();
}
}
assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive));
assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
}
/**
* Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
*/
@ -238,6 +403,29 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
}
/**
* Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7
*
* @param rsu the instance to test
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomAlphaNumeric(final RandomStringUtils rsu) {
final char[] testChars = { 'a', 'z', 'A', 'Z', '0', '9' };
final boolean[] found = { false, false, false, false, false, false };
for (int i = 0; i < LOOP_COUNT; i++) {
final String randString = rsu.nextAlphanumeric(10);
for (int j = 0; j < testChars.length; j++) {
if (randString.indexOf(testChars[j]) > 0) {
found[j] = true;
}
}
}
for (int i = 0; i < testChars.length; i++) {
assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem ");
}
}
@Test
public void testRandomAlphanumericRange() {
final int expectedMinLengthInclusive = 1;
@ -264,15 +452,114 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
/**
* Make sure 32 and 127 are generated by randomNumeric This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5
* Test the implementation
*
* @param rsu the instance to test.
*/
@Test
public void testRandomAscii() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomApis(final RandomStringUtils rsu) {
String r1 = rsu.next(50);
assertEquals(50, r1.length(), "random(50) length");
String r2 = rsu.next(50);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextAscii(50);
assertEquals(50, r1.length(), "randomAscii(50) length");
for (int i = 0; i < r1.length(); i++) {
assertThat("char >= 32 && <= 127", (int) r1.charAt(i), allOf(greaterThanOrEqualTo(32), lessThanOrEqualTo(127)));
}
r2 = rsu.nextAscii(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextAlphabetic(50);
assertEquals(50, r1.length(), "randomAlphabetic(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic");
}
r2 = rsu.nextAlphabetic(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextAlphanumeric(50);
assertEquals(50, r1.length(), "randomAlphanumeric(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric");
}
r2 = rsu.nextAlphabetic(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextGraph(50);
assertEquals(50, r1.length(), "randomGraph(50) length");
for (int i = 0; i < r1.length(); i++) {
assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126");
}
r2 = rsu.nextGraph(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextNumeric(50);
assertEquals(50, r1.length(), "randomNumeric(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric");
}
r2 = rsu.nextNumeric(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.nextPrint(50);
assertEquals(50, r1.length(), "randomPrint(50) length");
for (int i = 0; i < r1.length(); i++) {
assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126");
}
r2 = rsu.nextPrint(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
String set = "abcdefg";
r1 = rsu.next(50, set);
assertEquals(50, r1.length(), "random(50, \"abcdefg\")");
for (int i = 0; i < r1.length(); i++) {
assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
}
r2 = rsu.next(50, set);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.next(50, (String) null);
assertEquals(50, r1.length(), "random(50) length");
r2 = rsu.next(50, (String) null);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
set = "stuvwxyz";
r1 = rsu.next(50, set.toCharArray());
assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")");
for (int i = 0; i < r1.length(); i++) {
assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
}
r2 = rsu.next(50, set);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.next(50, (char[]) null);
assertEquals(50, r1.length(), "random(50) length");
r2 = rsu.next(50, (char[]) null);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = rsu.next(0);
assertEquals("", r1, "random(0).equals(\"\")");
}
/**
* Make sure 32 and 127 are generated by randomNumeric This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5
*
* @param rsu the instance to test
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomAscii(final RandomStringUtils rsu) {
final char[] testChars = { (char) 32, (char) 126 };
final boolean[] found = { false, false };
// Test failures have been observed on GitHub builds with a 100 limit.
for (int i = 0; i < LOOP_COUNT; i++) {
final String randString = RandomStringUtils.randomAscii(10);
final String randString = rsu.nextAscii(10);
for (int j = 0; j < testChars.length; j++) {
if (randString.indexOf(testChars[j]) > 0) {
found[j] = true;
@ -284,8 +571,9 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
}
@Test
public void testRandomAsciiRange() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomAsciiRange(final RandomStringUtils rsu) {
final int expectedMinLengthInclusive = 1;
final int expectedMaxLengthExclusive = 11;
final String pattern = "^\\p{ASCII}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
@ -293,7 +581,7 @@ public class RandomStringUtilsTest extends AbstractLangTest {
int maxCreatedLength = expectedMinLengthInclusive;
int minCreatedLength = expectedMaxLengthExclusive - 1;
for (int i = 0; i < LOOP_COUNT; i++) {
final String s = RandomStringUtils.randomAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive);
final String s = rsu.nextAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive);
assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
assertTrue(s.matches(pattern), s);
@ -309,8 +597,9 @@ public class RandomStringUtilsTest extends AbstractLangTest {
assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
}
@Test
public void testRandomGraphRange() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomGraphRange(final RandomStringUtils rsu) {
final int expectedMinLengthInclusive = 1;
final int expectedMaxLengthExclusive = 11;
final String pattern = "^\\p{Graph}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
@ -318,7 +607,7 @@ public class RandomStringUtilsTest extends AbstractLangTest {
int maxCreatedLength = expectedMinLengthInclusive;
int minCreatedLength = expectedMaxLengthExclusive - 1;
for (int i = 0; i < LOOP_COUNT; i++) {
final String s = RandomStringUtils.randomGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive);
final String s = rsu.nextGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive);
assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
assertTrue(s.matches(pattern), s);
@ -336,13 +625,16 @@ public class RandomStringUtilsTest extends AbstractLangTest {
/**
* Make sure '0' and '9' are generated by randomNumeric This test will fail randomly with probability = 2 * (9/10)**1000 ~ 3.5E-46
*
* @param rsu the instance to test
*/
@Test
public void testRandomNumeric() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomNumeric(final RandomStringUtils rsu) {
final char[] testChars = { '0', '9' };
final boolean[] found = { false, false };
for (int i = 0; i < LOOP_COUNT; i++) {
final String randString = RandomStringUtils.randomNumeric(10);
final String randString = rsu.nextNumeric(10);
for (int j = 0; j < testChars.length; j++) {
if (randString.indexOf(testChars[j]) > 0) {
found[j] = true;
@ -354,8 +646,9 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
}
@Test
public void testRandomNumericRange() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomNumericRange(final RandomStringUtils rsu) {
final int expectedMinLengthInclusive = 1;
final int expectedMaxLengthExclusive = 11;
final String pattern = "^\\p{Digit}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
@ -363,7 +656,7 @@ public class RandomStringUtilsTest extends AbstractLangTest {
int maxCreatedLength = expectedMinLengthInclusive;
int minCreatedLength = expectedMaxLengthExclusive - 1;
for (int i = 0; i < LOOP_COUNT; i++) {
final String s = RandomStringUtils.randomNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
final String s = rsu.nextNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive);
assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
assertTrue(s.matches(pattern), s);
@ -380,7 +673,16 @@ public class RandomStringUtilsTest extends AbstractLangTest {
}
@Test
public void testRandomPrintRange() {
public void testRandomParameter() {
final long seedMillis = System.currentTimeMillis();
final String r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
final String r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
assertEquals(r1, r2, "r1.equals(r2)");
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomPrintRange(final RandomStringUtils rsu) {
final int expectedMinLengthInclusive = 1;
final int expectedMaxLengthExclusive = 11;
final String pattern = "^\\p{Print}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$";
@ -388,7 +690,7 @@ public class RandomStringUtilsTest extends AbstractLangTest {
int maxCreatedLength = expectedMinLengthInclusive;
int minCreatedLength = expectedMaxLengthExclusive - 1;
for (int i = 0; i < LOOP_COUNT; i++) {
final String s = RandomStringUtils.randomPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive);
final String s = rsu.nextPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive);
assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1)));
assertTrue(s.matches(pattern), s);
@ -404,162 +706,29 @@ public class RandomStringUtilsTest extends AbstractLangTest {
assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1));
}
/**
* Test the implementation
*/
@Test
public void testRandomStringUtils() {
String r1 = RandomStringUtils.random(50);
assertEquals(50, r1.length(), "random(50) length");
String r2 = RandomStringUtils.random(50);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomAscii(50);
assertEquals(50, r1.length(), "randomAscii(50) length");
for (int i = 0; i < r1.length(); i++) {
assertThat("char >= 32 && <= 127", (int) r1.charAt(i), allOf(greaterThanOrEqualTo(32), lessThanOrEqualTo(127)));
}
r2 = RandomStringUtils.randomAscii(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomAlphabetic(50);
assertEquals(50, r1.length(), "randomAlphabetic(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic");
}
r2 = RandomStringUtils.randomAlphabetic(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomAlphanumeric(50);
assertEquals(50, r1.length(), "randomAlphanumeric(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric");
}
r2 = RandomStringUtils.randomAlphabetic(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomGraph(50);
assertEquals(50, r1.length(), "randomGraph(50) length");
for (int i = 0; i < r1.length(); i++) {
assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126");
}
r2 = RandomStringUtils.randomGraph(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomNumeric(50);
assertEquals(50, r1.length(), "randomNumeric(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric");
}
r2 = RandomStringUtils.randomNumeric(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.randomPrint(50);
assertEquals(50, r1.length(), "randomPrint(50) length");
for (int i = 0; i < r1.length(); i++) {
assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126");
}
r2 = RandomStringUtils.randomPrint(50);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
String set = "abcdefg";
r1 = RandomStringUtils.random(50, set);
assertEquals(50, r1.length(), "random(50, \"abcdefg\")");
for (int i = 0; i < r1.length(); i++) {
assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
}
r2 = RandomStringUtils.random(50, set);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.random(50, (String) null);
assertEquals(50, r1.length(), "random(50) length");
r2 = RandomStringUtils.random(50, (String) null);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
set = "stuvwxyz";
r1 = RandomStringUtils.random(50, set.toCharArray());
assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")");
for (int i = 0; i < r1.length(); i++) {
assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set");
}
r2 = RandomStringUtils.random(50, set);
assertFalse(r1.equals(r2), "!r1.equals(r2)");
r1 = RandomStringUtils.random(50, (char[]) null);
assertEquals(50, r1.length(), "random(50) length");
r2 = RandomStringUtils.random(50, (char[]) null);
assertEquals(50, r2.length(), "random(50) length");
assertFalse(r1.equals(r2), "!r1.equals(r2)");
final long seedMillis = System.currentTimeMillis();
r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis));
assertEquals(r1, r2, "r1.equals(r2)");
r1 = RandomStringUtils.random(0);
assertEquals("", r1, "random(0).equals(\"\")");
}
/**
* Test homogeneity of random strings generated -- i.e., test that characters show up with expected frequencies in generated strings. Will fail randomly
* about 1 in 100,000 times. Repeated failures indicate a problem.
*/
@Test
public void testRandomStringUtilsHomog() {
final String set = "abc";
final char[] chars = set.toCharArray();
final int[] counts = { 0, 0, 0 };
final int[] expected = { 200, 200, 200 };
for (int i = 0; i < 100; i++) {
final String gen = RandomStringUtils.random(6, chars);
for (int j = 0; j < 6; j++) {
switch (gen.charAt(j)) {
case 'a': {
counts[0]++;
break;
}
case 'b': {
counts[1]++;
break;
}
case 'c': {
counts[2]++;
break;
}
default: {
fail("generated character not in set");
}
}
}
}
// Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level.
// This expects a failure rate of 1 in 100,000.
// critical value: from scipy.stats import chi2; chi2(2).isf(1e-5)
assertThat("test homogeneity -- will fail about 1 in 100,000 times", chiSquare(expected, counts), lessThan(23.025850929940457d));
}
/**
* Test {@code RandomStringUtils.random} works appropriately when chars specified.
*
* @param rsu the instance to test.
*/
@Test
void testRandomWithChars() {
@ParameterizedTest
@MethodSource("randomProvider")
public void testRandomWithChars(final RandomStringUtils rsu) {
final char[] digitChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
String r1, r2, r3;
r1 = RandomStringUtils.random(50, 0, 0, true, true, digitChars);
r1 = rsu.next(50, 0, 0, true, true, digitChars);
assertEquals(50, r1.length(), "randomNumeric(50)");
for (int i = 0; i < r1.length(); i++) {
assertTrue(
Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)),
"r1 contains numeric");
}
r2 = RandomStringUtils.randomNumeric(50);
r2 = rsu.nextNumeric(50);
assertNotEquals(r1, r2);
r3 = RandomStringUtils.random(50, 0, 0, true, true, digitChars);
r3 = rsu.next(50, 0, 0, true, true, digitChars);
assertNotEquals(r1, r3);
assertNotEquals(r2, r3);
}

View File

@ -22,16 +22,16 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for {@link RandomUtils}
@ -43,6 +43,10 @@ public class RandomUtilsTest extends AbstractLangTest {
*/
private static final double DELTA = 1e-5;
static Stream<RandomUtils> randomProvider() {
return Stream.of(RandomUtils.secure(), RandomUtils.insecure());
}
/**
* Tests next boolean
*/
@ -52,14 +56,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertTrue(result || !result);
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testBoolean(final RandomUtils ru) {
final boolean result = ru.randomBoolean();
assertTrue(result || !result);
}
@Test
public void testConstructor() {
assertNotNull(new RandomUtils());
final Constructor<?>[] cons = RandomUtils.class.getDeclaredConstructors();
assertEquals(1, cons.length);
assertTrue(Modifier.isPublic(cons[0].getModifiers()));
assertTrue(Modifier.isPublic(RandomUtils.class.getModifiers()));
assertFalse(Modifier.isFinal(RandomUtils.class.getModifiers()));
}
/**
@ -71,6 +77,13 @@ public class RandomUtilsTest extends AbstractLangTest {
assertTrue(result >= 0 && result <= Double.MAX_VALUE); // TODO: should be <max?
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testExtremeRangeDouble(final RandomUtils ru) {
final double result = ru.randomDouble(0, Double.MAX_VALUE);
assertTrue(result >= 0 && result <= Double.MAX_VALUE); // TODO: should be <max?
}
/**
* Tests extreme range.
*/
@ -80,6 +93,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertTrue(result >= 0f && result <= Float.MAX_VALUE); // TODO: should be <max?
}
/**
* Tests extreme range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testExtremeRangeFloat(final RandomUtils ru) {
final float result = ru.randomFloat(0, Float.MAX_VALUE);
assertTrue(result >= 0f && result <= Float.MAX_VALUE); // TODO: should be <max?
}
/**
* Tests extreme range.
*/
@ -89,6 +112,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 0 && result < Integer.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0), lessThan(Integer.MAX_VALUE)));
}
/**
* Tests extreme range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testExtremeRangeInt(final RandomUtils ru) {
final int result = ru.randomInt(0, Integer.MAX_VALUE);
assertThat("result >= 0 && result < Integer.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0), lessThan(Integer.MAX_VALUE)));
}
/**
* Tests extreme range.
*/
@ -98,6 +131,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 0 && result < Long.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE)));
}
/**
* Tests extreme range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testExtremeRangeLong(final RandomUtils ru) {
final long result = ru.randomLong(0, Long.MAX_VALUE);
assertThat("result >= 0 && result < Long.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE)));
}
/**
* Test a large value for long. A previous implementation using
* {@link RandomUtils#nextDouble(double, double)} could generate a value equal
@ -122,6 +165,31 @@ public class RandomUtilsTest extends AbstractLangTest {
}
}
/**
* Test a large value for long. A previous implementation using
* {@link RandomUtils#nextDouble(double, double)} could generate a value equal
* to the upper limit.
*
* <pre>
* return (long) nextDouble(startInclusive, endExclusive);
* </pre>
*
* <p>See LANG-1592.</p>
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testLargeValueRangeLong(final RandomUtils ru) {
final long startInclusive = 12900000000001L;
final long endExclusive = 12900000000016L;
// Note: The method using 'return (long) nextDouble(startInclusive, endExclusive)'
// takes thousands of calls to generate an error. This size loop fails most
// of the time with the previous method.
final int n = (int) (endExclusive - startInclusive) * 1000;
for (int i = 0; i < n; i++) {
assertNotEquals(endExclusive, ru.randomLong(startInclusive, endExclusive));
}
}
/**
* Tests random byte array.
*/
@ -131,11 +199,27 @@ public class RandomUtilsTest extends AbstractLangTest {
assertEquals(20, result.length);
}
/**
* Tests random byte array.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextBytes(final RandomUtils ru) {
final byte[] result = ru.randomBytes(20);
assertEquals(20, result.length);
}
@Test
public void testNextBytesNegative() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextBytes(-1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextBytesNegative(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomBytes(-1));
}
/**
* Tests next double range.
*/
@ -145,11 +229,27 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 33d && result < 42d", result, allOf(greaterThanOrEqualTo(33d), lessThan(42d)));
}
/**
* Tests next double range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextDouble(final RandomUtils ru) {
final double result = ru.randomDouble(33d, 42d);
assertThat("result >= 33d && result < 42d", result, allOf(greaterThanOrEqualTo(33d), lessThan(42d)));
}
@Test
public void testNextDoubleLowerGreaterUpper() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(2, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextDoubleLowerGreaterUpper(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomDouble(2, 1));
}
/**
* Test next double range with minimal range.
*/
@ -158,11 +258,26 @@ public class RandomUtilsTest extends AbstractLangTest {
assertEquals(42.1, RandomUtils.nextDouble(42.1, 42.1), DELTA);
}
/**
* Test next double range with minimal range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextDoubleMinimalRange(final RandomUtils ru) {
assertEquals(42.1, ru.randomDouble(42.1, 42.1), DELTA);
}
@Test
public void testNextDoubleNegative() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(-1, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextDoubleNegative(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomDouble(-1, 1));
}
/**
* Tests next double range, random result.
*/
@ -172,6 +287,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("randomResult >= 0 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0d), lessThan(Double.MAX_VALUE)));
}
/**
* Tests next double range, random result.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextDoubleRandomResult(final RandomUtils ru) {
final double randomResult = ru.randomDouble();
assertThat("randomResult >= 0 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0d), lessThan(Double.MAX_VALUE)));
}
/**
* Tests next float range.
*/
@ -181,11 +306,27 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 33f && result < 42f", result, allOf(greaterThanOrEqualTo(33f), lessThan(42f)));
}
/**
* Tests next float range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextFloat(final RandomUtils ru) {
final float result = ru.randomFloat(33f, 42f);
assertThat("result >= 33f && result < 42f", result, allOf(greaterThanOrEqualTo(33f), lessThan(42f)));
}
@Test
public void testNextFloatLowerGreaterUpper() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(2, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextFloatLowerGreaterUpper(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomFloat(2, 1));
}
/**
* Test next float range with minimal range.
*/
@ -194,11 +335,26 @@ public class RandomUtilsTest extends AbstractLangTest {
assertEquals(42.1f, RandomUtils.nextFloat(42.1f, 42.1f), DELTA);
}
/**
* Test next float range with minimal range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextFloatMinimalRange(final RandomUtils ru) {
assertEquals(42.1f, ru.randomFloat(42.1f, 42.1f), DELTA);
}
@Test
public void testNextFloatNegative() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(-1, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextFloatNegative(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomFloat(-1, 1));
}
/**
* Tests next float range, random result.
*/
@ -208,6 +364,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("randomResult >= 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0f), lessThan(Float.MAX_VALUE)));
}
/**
* Tests next float range, random result.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextFloatRandomResult(final RandomUtils ru) {
final float randomResult = ru.randomFloat();
assertThat("randomResult >= 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0f), lessThan(Float.MAX_VALUE)));
}
/**
* Tests next int range.
*/
@ -217,11 +383,27 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 33 && result < 42", result, allOf(greaterThanOrEqualTo(33), lessThan(42)));
}
/**
* Tests next int range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextInt(final RandomUtils ru) {
final int result = ru.randomInt(33, 42);
assertThat("result >= 33 && result < 42", result, allOf(greaterThanOrEqualTo(33), lessThan(42)));
}
@Test
public void testNextIntLowerGreaterUpper() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(2, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextIntLowerGreaterUpper(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomInt(2, 1));
}
/**
* Test next int range with minimal range.
*/
@ -230,11 +412,26 @@ public class RandomUtilsTest extends AbstractLangTest {
assertEquals(42, RandomUtils.nextInt(42, 42));
}
/**
* Test next int range with minimal range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextIntMinimalRange(final RandomUtils ru) {
assertEquals(42, ru.randomInt(42, 42));
}
@Test
public void testNextIntNegative() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(-1, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextIntNegative(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomInt(-1, 1));
}
/**
* Tests next int range, random result.
*/
@ -245,6 +442,17 @@ public class RandomUtilsTest extends AbstractLangTest {
assertTrue(randomResult < Integer.MAX_VALUE);
}
/**
* Tests next int range, random result.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextIntRandomResult(final RandomUtils ru) {
final int randomResult = ru.randomInt();
assertTrue(randomResult > 0);
assertTrue(randomResult < Integer.MAX_VALUE);
}
/**
* Tests next long range.
*/
@ -254,11 +462,27 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("result >= 33L && result < 42L", result, allOf(greaterThanOrEqualTo(33L), lessThan(42L)));
}
/**
* Tests next long range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextLong(final RandomUtils ru) {
final long result = ru.randomLong(33L, 42L);
assertThat("result >= 33L && result < 42L", result, allOf(greaterThanOrEqualTo(33L), lessThan(42L)));
}
@Test
public void testNextLongLowerGreaterUpper() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(2, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextLongLowerGreaterUpper(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomLong(2, 1));
}
/**
* Test next long range with minimal range.
*/
@ -267,11 +491,26 @@ public class RandomUtilsTest extends AbstractLangTest {
assertEquals(42L, RandomUtils.nextLong(42L, 42L));
}
/**
* Test next long range with minimal range.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextLongMinimalRange(final RandomUtils ru) {
assertEquals(42L, ru.randomLong(42L, 42L));
}
@Test
public void testNextLongNegative() {
assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(-1, 1));
}
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextLongNegative(final RandomUtils ru) {
assertThrows(IllegalArgumentException.class, () -> ru.randomLong(-1, 1));
}
/**
* Tests next long range, random result.
*/
@ -281,6 +520,16 @@ public class RandomUtilsTest extends AbstractLangTest {
assertThat("randomResult >= 0 && randomResult < Long.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE)));
}
/**
* Tests next long range, random result.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testNextLongRandomResult(final RandomUtils ru) {
final long randomResult = ru.randomLong();
assertThat("randomResult >= 0 && randomResult < Long.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE)));
}
/**
* Tests a zero byte array length.
*/
@ -288,4 +537,13 @@ public class RandomUtilsTest extends AbstractLangTest {
public void testZeroLengthNextBytes() {
assertArrayEquals(new byte[0], RandomUtils.nextBytes(0));
}
/**
* Tests a zero byte array length.
*/
@ParameterizedTest
@MethodSource("randomProvider")
public void testZeroLengthNextBytes(final RandomUtils ru) {
assertArrayEquals(new byte[0], ru.randomBytes(0));
}
}