com.carrotsearch.randomizedtesting.RandomizedTest Maven / Gradle / Ivy
Show all versions of randomizedtesting Show documentation
package com.carrotsearch.randomizedtesting;
import java.io.Closeable;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assume;
import org.junit.AssumptionViolatedException;
import org.junit.runner.RunWith;
import com.carrotsearch.randomizedtesting.annotations.Listeners;
import com.carrotsearch.randomizedtesting.annotations.Nightly;
import com.carrotsearch.randomizedtesting.annotations.SuppressForbidden;
import com.carrotsearch.randomizedtesting.generators.BiasedNumbers;
import com.carrotsearch.randomizedtesting.generators.RandomBytes;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
/**
* Common scaffolding for subclassing randomized tests.
*
* @see Listeners
* @see RandomizedContext
*/
@RunWith(RandomizedRunner.class)
public class RandomizedTest {
/**
* The global multiplier property (Double).
*
* @see #multiplier()
*/
public static final String SYSPROP_MULTIPLIER = "randomized.multiplier";
/* Commonly used charsets (these must be supported by every JVM). */
protected static final Charset UTF8 = Charset.forName("UTF-8");
protected static final Charset UTF16 = Charset.forName("UTF-16");
protected static final Charset ISO8859_1 = Charset.forName("ISO-8859-1");
protected static final Charset US_ASCII = Charset.forName("US-ASCII");
/* This charset does not need to be supported, but I don't know any JVM under which it wouldn't be. */
protected static final Charset UTF32 = Charset.forName("UTF-32");
/**
* Default multiplier.
*
* @see #SYSPROP_MULTIPLIER
*/
private static final double DEFAULT_MULTIPLIER = 1.0d;
/**
* Shortcut for {@link RandomizedContext#current()}.
*/
public static RandomizedContext getContext() {
return RandomizedContext.current();
}
/**
* Returns true if {@link Nightly} test group is enabled.
*
* @see Nightly
*/
public static boolean isNightly() {
return getContext().isNightly();
}
/**
* Shortcut for {@link RandomizedContext#getRandom()}. Even though this method
* is static, it returns per-thread {@link Random} instance, so no race conditions
* can occur.
*
* It is recommended that specific methods are used to pick random values.
*/
public static Random getRandom() {
return getContext().getRandom();
}
//
// Random value pickers. Shortcuts to methods in {@link #getRandom()} mostly.
//
public static boolean randomBoolean() { return getRandom().nextBoolean(); }
public static byte randomByte() { return (byte) getRandom().nextInt(); }
public static short randomShort() { return (short) getRandom().nextInt(); }
public static int randomInt() { return getRandom().nextInt(); }
public static float randomFloat() { return getRandom().nextFloat(); }
public static double randomDouble() { return getRandom().nextDouble(); }
public static long randomLong() { return getRandom().nextLong(); }
/** @see Random#nextGaussian() */
public static double randomGaussian() { return getRandom().nextGaussian(); }
//
// Biased value pickers.
//
/**
* A biased "evil" random float between min and max (inclusive).
*
* @see BiasedNumbers#randomFloatBetween(Random, float, float)
*/
public static float biasedFloatBetween(float min, float max) { return BiasedNumbers.randomFloatBetween(getRandom(), min, max); }
/**
* A biased "evil" random double between min and max (inclusive).
*
* @see BiasedNumbers#randomDoubleBetween(Random, double, double)
*/
public static double biasedDoubleBetween(double min, double max) { return BiasedNumbers.randomDoubleBetween(getRandom(), min, max); }
//
// Delegates to RandomBytes.
//
/**
* Returns a byte array with random content.
*
* @param length The length of the byte array. Can be zero.
* @return Returns a byte array with random content.
*/
public static byte[] randomBytesOfLength(int length) {
return RandomBytes.randomBytesOfLength(new Random(getRandom().nextLong()), length);
}
/**
* Returns a byte array with random content.
*
* @param minLength The minimum length of the byte array. Can be zero.
* @param maxLength The maximum length of the byte array. Can be zero.
* @return Returns a byte array with random content.
*/
public static byte[] randomBytesOfLength(int minLength, int maxLength) {
return RandomBytes.randomBytesOfLengthBetween(new Random(getRandom().nextLong()), minLength, maxLength);
}
//
// Delegates to RandomNumbers.
//
/**
* A random integer from 0..max (inclusive).
*/
@Deprecated
public static int randomInt(int max) {
return RandomNumbers.randomIntBetween(getRandom(), 0, max);
}
/**
* A random long from 0..max (inclusive).
*/
@Deprecated
public static long randomLong(long max) {
return RandomNumbers.randomLongBetween(getRandom(), 0, max);
}
/**
* A random integer from min to max (inclusive).
*
* @see #scaledRandomIntBetween(int, int)
*/
public static int randomIntBetween(int min, int max) {
return RandomNumbers.randomIntBetween(getRandom(), min, max);
}
/**
* An alias for {@link #randomIntBetween(int, int)}.
*
* @see #scaledRandomIntBetween(int, int)
*/
public static int between(int min, int max) {
return randomIntBetween(min, max);
}
/**
* A random long from min to max (inclusive).
*/
public static long randomLongBetween(long min, long max) {
return RandomNumbers.randomLongBetween(getRandom(), min, max);
}
/**
* An alias for {@link #randomLongBetween}.
*/
public static long between(long min, long max) {
return randomLongBetween(min, max);
}
/**
* Returns a random value greater or equal to min. The value
* picked is affected by {@link #isNightly()} and {@link #multiplier()}.
*
* @see #scaledRandomIntBetween(int, int)
*/
public static int atLeast(int min) {
if (min < 0) throw new IllegalArgumentException("atLeast requires non-negative argument: " + min);
return scaledRandomIntBetween(min, Integer.MAX_VALUE);
}
/**
* Returns a non-negative random value smaller or equal max. The value
* picked is affected by {@link #isNightly()} and {@link #multiplier()}.
*
*
This method is effectively an alias to:
*
* scaledRandomIntBetween(0, max)
*
*
* @see #scaledRandomIntBetween(int, int)
*/
public static int atMost(int max) {
if (max < 0) throw new IllegalArgumentException("atMost requires non-negative argument: " + max);
return scaledRandomIntBetween(0, max);
}
/**
* Rarely returns true in about 10% of all calls (regardless of the
* {@link #isNightly()} mode).
*/
public static boolean rarely() {
return randomInt(100) >= 90;
}
/**
* The exact opposite of {@link #rarely()}.
*/
public static boolean frequently() {
return !rarely();
}
//
// Delegates to RandomPicks
//
/**
* Pick a random object from the given array. The array must not be empty.
*/
public static T randomFrom(T [] array) {
return RandomPicks.randomFrom(getRandom(), array);
}
/**
* Pick a random object from the given list.
*/
public static T randomFrom(List list) {
return RandomPicks.randomFrom(getRandom(), list);
}
public static byte randomFrom(byte [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static short randomFrom(short [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static int randomFrom(int [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static char randomFrom(char [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static float randomFrom(float [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static long randomFrom(long [] array) { return RandomPicks.randomFrom(getRandom(), array); }
public static double randomFrom(double [] array) { return RandomPicks.randomFrom(getRandom(), array); }
//
// "multiplied" or scaled value pickers. These will be affected by global multiplier.
//
/**
* A multiplier can be used to linearly scale certain values. It can be used to make data
* or iterations of certain tests "heavier" for nightly runs, for example.
*
* The default multiplier value is 1.
*
* @see #SYSPROP_MULTIPLIER
*/
public static double multiplier() {
checkContext();
return systemPropertyAsDouble(SYSPROP_MULTIPLIER, DEFAULT_MULTIPLIER);
}
/**
* Returns a "scaled" number of iterations for loops which can have a variable
* iteration count. This method is effectively
* an alias to {@link #scaledRandomIntBetween(int, int)}.
*/
public static int iterations(int min, int max) {
return scaledRandomIntBetween(min, max);
}
/**
* Returns a "scaled" random number between min and max (inclusive). The number of
* iterations will fall between [min, max], but the selection will also try to
* achieve the points below:
*
* - the multiplier can be used to move the number of iterations closer to min
* (if it is smaller than 1) or closer to max (if it is larger than 1). Setting
* the multiplier to 0 will always result in picking min.
* - on normal runs, the number will be closer to min than to max.
* - on nightly runs, the number will be closer to max than to min.
*
*
* @see #multiplier()
*
* @param min Minimum (inclusive).
* @param max Maximum (inclusive).
* @return Returns a random number between min and max.
*/
public static int scaledRandomIntBetween(int min, int max) {
if (min < 0) throw new IllegalArgumentException("min must be >= 0: " + min);
if (min > max) throw new IllegalArgumentException("max must be >= min: " + min + ", " + max);
double point = Math.min(1, Math.abs(randomGaussian()) * 0.3) * multiplier();
double range = max - min;
int scaled = (int) Math.round(Math.min(point * range, range));
if (isNightly()) {
return max - scaled;
} else {
return min + scaled;
}
}
// Methods to help with I/O and environment.
/**
* @see #globalTempDir()
*/
private static Path globalTempDir;
/** */
private static AtomicInteger tempSubFileNameCount = new AtomicInteger(0);
/**
* Global temporary directory created for the duration of this class's lifespan. If
* multiple class loaders are used, there may be more global temp dirs, but it
* shouldn't really be the case in practice.
*/
public static Path globalTempDir() throws IOException {
checkContext();
synchronized (RandomizedTest.class) {
if (globalTempDir == null) {
String tempDirPath = System.getProperty("java.io.tmpdir");
if (tempDirPath == null)
throw new Error("No property java.io.tmpdir?");
Path tempDir = Paths.get(tempDirPath);
if (!Files.isDirectory(tempDir) || !Files.isWritable(tempDir)) {
throw new Error("Temporary folder not accessible: " + tempDir.toAbsolutePath());
}
SimpleDateFormat tsFormat = new SimpleDateFormat("'tests-'yyyyMMddHHmmss'-'SSS", Locale.ROOT);
String dirName = tsFormat.format(new Date());
final Path tmpFolder = tempDir.resolve(dirName);
// I assume mkdir is filesystem-atomic and only succeeds if the
// directory didn't previously exist?
Files.createDirectories(tmpFolder);
globalTempDir = tmpFolder;
Runtime.getRuntime().addShutdownHook(new Thread() {
@SuppressForbidden("Legitimate use of syserr.")
public void run() {
try {
rmDir(globalTempDir);
} catch (IOException e) {
// Not much else to do but to log and quit.
System.err.println("Could not delete temporary folder: "
+ globalTempDir.toAbsolutePath() + ". Cause: ");
e.printStackTrace(System.err);
}
}
});
}
return globalTempDir;
}
}
/**
* Creates a new temporary directory for the {@link LifecycleScope#TEST} duration.
*
* @see #globalTempDir()
*/
public Path newTempDir() throws IOException {
return newTempDir(LifecycleScope.TEST);
}
/**
* Creates a temporary directory, deleted after the given lifecycle phase.
* Temporary directory is created relative to a globally picked temporary directory.
*/
public static Path newTempDir(LifecycleScope scope) throws IOException {
checkContext();
synchronized (RandomizedTest.class) {
Path tempDir = globalTempDir().resolve(nextTempName());
Files.createDirectories(tempDir);
getContext().closeAtEnd(new TempPathResource(tempDir), scope);
return tempDir;
}
}
/**
* Registers a {@link Closeable} resource that should be closed after the test
* completes.
*
* @return resource (for call chaining).
*/
public T closeAfterTest(T resource) {
return getContext().closeAtEnd(resource, LifecycleScope.TEST);
}
/**
* Registers a {@link Closeable} resource that should be closed after the suite
* completes.
*
* @return resource (for call chaining).
*/
public static T closeAfterSuite(T resource) {
return getContext().closeAtEnd(resource, LifecycleScope.SUITE);
}
/**
* Creates a new temporary file for the {@link LifecycleScope#TEST} duration.
*/
public Path newTempFile() throws IOException {
return newTempFile(LifecycleScope.TEST);
}
/**
* Creates a new temporary file deleted after the given lifecycle phase completes.
* The file is physically created on disk, but is not locked or opened.
*/
public static Path newTempFile(LifecycleScope scope) throws IOException {
checkContext();
synchronized (RandomizedTest.class) {
Path tempFile = globalTempDir().resolve(nextTempName());
Files.createFile(tempFile);
getContext().closeAtEnd(new TempPathResource(tempFile), scope);
return tempFile;
}
}
/** Next temporary filename. */
protected static String nextTempName() {
return String.format(Locale.ROOT, "%04d has-space", tempSubFileNameCount.getAndIncrement());
}
/**
* Recursively delete a folder. Throws an exception if any failure occurs.
*
* @param path Path to the folder to be (recursively) deleted. The folder must
* exist.
*/
public static void rmDir(Path path) throws IOException {
if (!Files.isDirectory(path)) {
throw new IOException("Not a folder: " + path);
}
try {
Files.walkFileTree(path, new SimpleFileVisitor() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException iterationError) throws IOException {
if (iterationError != null) {
throw iterationError;
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException {
throw e;
}
});
} catch (IOException e) {
throw new IOException("Could not remove directory: " + path, e);
}
}
/**
* Assign a temporary server socket. If you need a temporary port one can
* assign a server socket and close it immediately, just to acquire its port
* number.
*
* @param scope
* The lifecycle scope to close the socket after. If the socket is
* closed earlier, nothing happens (silently dropped).
*/
public static ServerSocket newServerSocket(LifecycleScope scope) throws IOException {
final ServerSocket socket = new ServerSocket(0);
getContext().closeAtEnd(new Closeable() {
public void close() throws IOException {
if (!socket.isClosed())
socket.close();
}
}, scope);
return socket;
}
/**
* Return a random Locale from the available locales on the system.
*
* Warning: This test assumes the returned array of locales is repeatable from jvm execution
* to jvm execution. It _may_ be different from jvm to jvm and as such, it can render
* tests execute in a different way.
*/
public static Locale randomLocale() {
Locale[] availableLocales = Locale.getAvailableLocales();
Arrays.sort(availableLocales, new Comparator() {
public int compare(Locale o1, Locale o2) {
return o1.toString().compareTo(o2.toString());
}
});
return randomFrom(availableLocales);
}
/**
* Return a random TimeZone from the available timezones on the system.
*
* Warning: This test assumes the returned array of time zones is repeatable from jvm execution
* to jvm execution. It _may_ be different from jvm to jvm and as such, it can render
* tests execute in a different way.
*/
public static TimeZone randomTimeZone() {
final String[] availableIDs = TimeZone.getAvailableIDs();
Arrays.sort(availableIDs);
return TimeZone.getTimeZone(randomFrom(availableIDs));
}
//
// Characters and strings. Delegates to RandomStrings and that in turn to StringGenerators.
//
/**
* @deprecated Use {@link #randomAsciiLettersOfLengthBetween} instead.
*/
@Deprecated
public static String randomAsciiOfLengthBetween(int minCodeUnits, int maxCodeUnits) {
return randomAsciiLettersOfLengthBetween(minCodeUnits, maxCodeUnits);
}
/**
* @deprecated Use {@link #randomAsciiLettersOfLength} instead.
*/
@Deprecated
public static String randomAsciiOfLength(int codeUnits) {
return randomAsciiLettersOfLength(codeUnits);
}
/**
* @see RandomStrings#randomAsciiLettersOfLengthBetween
*/
public static String randomAsciiLettersOfLengthBetween(int minLetters, int maxLetters) {
return RandomStrings.randomAsciiLettersOfLengthBetween(getRandom(), minLetters, maxLetters);
}
/**
* @see RandomStrings#randomAsciiLettersOfLength
*/
public static String randomAsciiLettersOfLength(int codeUnits) {
return RandomStrings.randomAsciiLettersOfLength(getRandom(), codeUnits);
}
/**
* @see RandomStrings#randomAsciiAlphanumOfLengthBetween
*/
public static String randomAsciiAlphanumOfLengthBetween(int minCodeUnits, int maxCodeUnits) {
return RandomStrings.randomAsciiAlphanumOfLengthBetween(getRandom(), minCodeUnits, maxCodeUnits);
}
/**
* @see RandomStrings#randomAsciiAlphanumOfLength
*/
public static String randomAsciiAlphanumOfLength(int codeUnits) {
return RandomStrings.randomAsciiAlphanumOfLength(getRandom(), codeUnits);
}
/**
* @see RandomStrings#randomUnicodeOfLengthBetween
*/
public static String randomUnicodeOfLengthBetween(int minCodeUnits, int maxCodeUnits) {
return RandomStrings.randomUnicodeOfLengthBetween(getRandom(),
minCodeUnits, maxCodeUnits);
}
/**
* @see RandomStrings#randomUnicodeOfLength
*/
public static String randomUnicodeOfLength(int codeUnits) {
return RandomStrings.randomUnicodeOfLength(getRandom(), codeUnits);
}
/**
* @see RandomStrings#randomUnicodeOfCodepointLengthBetween
*/
public static String randomUnicodeOfCodepointLengthBetween(int minCodePoints, int maxCodePoints) {
return RandomStrings.randomUnicodeOfCodepointLengthBetween(getRandom(),
minCodePoints, maxCodePoints);
}
/**
* @see RandomStrings#randomUnicodeOfCodepointLength
*/
public static String randomUnicodeOfCodepointLength(int codePoints) {
return RandomStrings.randomUnicodeOfCodepointLength(getRandom(), codePoints);
}
/**
* @see RandomStrings#randomRealisticUnicodeOfLengthBetween
*/
public static String randomRealisticUnicodeOfLengthBetween(int minCodeUnits, int maxCodeUnits) {
return RandomStrings.randomRealisticUnicodeOfLengthBetween(getRandom(),
minCodeUnits, maxCodeUnits);
}
/**
* @see RandomStrings#randomRealisticUnicodeOfLength
*/
public static String randomRealisticUnicodeOfLength(int codeUnits) {
return RandomStrings.randomRealisticUnicodeOfLength(getRandom(), codeUnits);
}
/**
* @see RandomStrings#randomRealisticUnicodeOfCodepointLengthBetween
*/
public static String randomRealisticUnicodeOfCodepointLengthBetween(
int minCodePoints, int maxCodePoints) {
return RandomStrings.randomRealisticUnicodeOfCodepointLengthBetween(
getRandom(), minCodePoints, maxCodePoints);
}
/**
* @see RandomStrings#randomRealisticUnicodeOfCodepointLength
*/
public static String randomRealisticUnicodeOfCodepointLength(int codePoints) {
return RandomStrings.randomRealisticUnicodeOfCodepointLength(getRandom(),
codePoints);
}
/**
* This is an absolutely hacky utility to take a vararg as input and return the array
* of arguments as output. The name is a dollar for brevity, idea borrowed from
* http://code.google.com/p/junitparams/.
*/
public static Object [] $(Object... objects) {
return objects;
}
/**
* @see #$
*/
public static Object [][] $$(Object[]... objects) {
return objects;
}
//
// wrappers for utility methods elsewhere that don't require try..catch blocks
// and rethrow the original checked exception if needed. dirty a bit, but saves
// keystrokes...
//
/**
* Same as {@link Thread#sleep(long)}.
*/
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Rethrow.rethrow(e);
}
}
//
// Extensions of Assume (with a message).
//
/**
* Making {@link Assume#assumeTrue(boolean)} directly available.
*/
public static void assumeTrue(boolean condition) {
Assume.assumeTrue(condition);
}
/**
* Reverse of {@link #assumeTrue(boolean)}.
*/
public static void assumeFalse(boolean condition) {
assumeTrue(!condition);
}
/**
* Making {@link Assume#assumeNotNull(Object...)} directly available.
*/
public static void assumeNotNull(Object... objects) {
Assume.assumeNotNull(objects);
}
/**
* @param condition
* If false an {@link AssumptionViolatedException} is
* thrown by this method and the test case (should be) ignored (or
* rather technically, flagged as a failure not passing a certain
* assumption). Tests that are assumption-failures do not break
* builds (again: typically).
* @param message
* Message to be included in the exception's string.
*/
public static void assumeTrue(String message, boolean condition) {
if (!condition) {
// @see {@link Rants#RANT_2}.
throw new AssumptionViolatedException(message);
}
}
/**
* Reverse of {@link #assumeTrue(String, boolean)}.
*/
public static void assumeFalse(String message, boolean condition) {
assumeTrue(message, !condition);
}
/**
* Assume t is null.
*/
public static void assumeNoException(String msg, Throwable t) {
if (t != null) {
// This does chain the exception as the cause.
throw new AssumptionViolatedException(msg, t);
}
}
/**
* Making {@link Assume#assumeNoException(Throwable)} directly available.
*/
public static void assumeNoException(Throwable t) {
Assume.assumeNoException(t);
}
//
// System properties and their conversion to common types, with defaults.
//
/**
* Get a system property and convert it to a double, if defined. Otherwise, return the default value.
*/
public static double systemPropertyAsDouble(String propertyName, double defaultValue) {
String v = System.getProperty(propertyName);
if (v != null && !v.trim().isEmpty()) {
try {
return Double.parseDouble(v.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Double value expected for property " +
propertyName + ": " + v, e);
}
} else {
return defaultValue;
}
}
/**
* Get a system property and convert it to a float, if defined. Otherwise, return the default value.
*/
public static float systemPropertyAsFloat(String propertyName, float defaultValue) {
String v = System.getProperty(propertyName);
if (v != null && !v.trim().isEmpty()) {
try {
return Float.parseFloat(v.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Float value expected for property " +
propertyName + ": " + v, e);
}
} else {
return defaultValue;
}
}
/**
* Get a system property and convert it to an int, if defined. Otherwise, return the default value.
*/
public static int systemPropertyAsInt(String propertyName, int defaultValue) {
String v = System.getProperty(propertyName);
if (v != null && !v.trim().isEmpty()) {
try {
return Integer.parseInt(v.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Integer value expected for property " +
propertyName + ": " + v, e);
}
} else {
return defaultValue;
}
}
/**
* Get a system property and convert it to a long, if defined. Otherwise, return the default value.
*/
public static float systemPropertyAsLong(String propertyName, int defaultValue) {
String v = System.getProperty(propertyName);
if (v != null && !v.trim().isEmpty()) {
try {
return Long.parseLong(v.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Long value expected for property " +
propertyName + ": " + v, e);
}
} else {
return defaultValue;
}
}
/** Boolean constants mapping. */
@SuppressWarnings("serial")
private final static HashMap BOOLEANS = new HashMap() {{
put( "true", true); put( "false", false);
put( "on", true); put( "off", false);
put( "yes", true); put( "no", false);
put("enabled", true); put("disabled", false);
}};
/**
* Get a system property and convert it to a boolean, if defined. This method returns
* true if the property exists an is set to any of the following strings
* (case-insensitive): true, on, yes, enabled.
*
* false is returned if the property exists and is set to any of the
* following strings (case-insensitive):
* false, off, no, disabled.
*/
public static boolean systemPropertyAsBoolean(String propertyName, boolean defaultValue) {
String v = System.getProperty(propertyName);
if (v != null && !v.trim().isEmpty()) {
v = v.trim();
Boolean result = BOOLEANS.get(v);
if (result != null)
return result.booleanValue();
else
throw new IllegalArgumentException("Boolean value expected for property " +
propertyName + " " +
"(true/false, on/off, enabled/disabled, yes/no): " + v);
} else {
return defaultValue;
}
}
//
// Miscellaneous infrastructure.
//
/**
* Ensures we're running with an initialized {@link RandomizedContext}.
*/
private static void checkContext() {
// Will throw an exception if not available.
RandomizedContext.current();
}
}