SOLR-9367: Improved TestInjection's randomization logic to use LuceneTestCase.random()

This commit is contained in:
Chris Hostetter 2016-08-09 14:46:51 -07:00
parent dbe6f6a108
commit 84a8c098fc
3 changed files with 81 additions and 20 deletions

View File

@ -247,6 +247,8 @@ Other Changes
* SOLR-9385: Add QParser.getParser(String,SolrQueryRequest) variant. (Christine Poerschke)
* SOLR-9367: Improved TestInjection's randomization logic to use LuceneTestCase.random() (hossman)
================== 6.1.0 ==================
Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

View File

@ -17,6 +17,7 @@
package org.apache.solr.util;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
@ -39,6 +40,11 @@ import org.slf4j.LoggerFactory;
* Allows random faults to be injected in running code during test runs.
*
* Set static strings to "true" or "false" or "true:60" for true 60% of the time.
*
* All methods are No-Ops unless <code>LuceneTestCase</code> is loadable via the ClassLoader used
* to load this class. <code>LuceneTestCase.random()</code> is used as the source of all entropy.
*
* @lucene.internal
*/
public class TestInjection {
@ -53,16 +59,42 @@ public class TestInjection {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Pattern ENABLED_PERCENT = Pattern.compile("(true|false)(?:\\:(\\d+))?$", Pattern.CASE_INSENSITIVE);
private static final Random RANDOM;
private static final String LUCENE_TEST_CASE_FQN = "org.apache.lucene.util.LuceneTestCase";
/**
* If null, then we are not being run as part of a test, and all TestInjection events should be No-Ops.
* If non-null, then this class should be used for accessing random entropy
* @see #random
*/
private static final Class LUCENE_TEST_CASE;
static {
// We try to make things reproducible in the context of our tests by initializing the random instance
// based on the current seed
String seed = System.getProperty("tests.seed");
if (seed == null) {
RANDOM = new Random();
Class nonFinalTemp = null;
try {
ClassLoader classLoader = MethodHandles.lookup().lookupClass().getClassLoader();
nonFinalTemp = classLoader.loadClass(LUCENE_TEST_CASE_FQN);
} catch (ClassNotFoundException e) {
log.debug("TestInjection methods will all be No-Ops since LuceneTestCase not found");
}
LUCENE_TEST_CASE = nonFinalTemp;
}
/**
* Returns a random to be used by the current thread if available, otherwise
* returns null.
* @see #LUCENE_TEST_CASE
*/
static Random random() { // non-private for testing
if (null == LUCENE_TEST_CASE) {
return null;
} else {
RANDOM = new Random(seed.hashCode());
try {
Method randomMethod = LUCENE_TEST_CASE.getMethod("random");
return (Random) randomMethod.invoke(null);
} catch (Exception e) {
throw new IllegalStateException("Unable to use reflection to invoke LuceneTestCase.random()", e);
}
}
}
@ -100,11 +132,14 @@ public class TestInjection {
public static boolean injectRandomDelayInCoreCreation() {
if (randomDelayInCoreCreation != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(randomDelayInCoreCreation);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
int delay = RANDOM.nextInt(randomDelayMaxInCoreCreationInSec);
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
int delay = rand.nextInt(randomDelayMaxInCoreCreationInSec);
log.info("Inject random core creation delay of {}s", delay);
try {
Thread.sleep(delay * 1000);
@ -118,11 +153,14 @@ public class TestInjection {
public static boolean injectNonGracefullClose(CoreContainer cc) {
if (cc.isShutDown() && nonGracefullClose != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(nonGracefullClose);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
if (RANDOM.nextBoolean()) {
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
if (rand.nextBoolean()) {
throw new TestShutdownFailError("Test exception for non graceful close");
} else {
@ -135,7 +173,9 @@ public class TestInjection {
// we should only need to do it once
try {
Thread.sleep(RANDOM.nextInt(1000));
// call random() again to get the correct one for this thread
Random taskRand = random();
Thread.sleep(taskRand.nextInt(1000));
} catch (InterruptedException e) {
}
@ -147,7 +187,7 @@ public class TestInjection {
};
Timer timer = new Timer();
timers.add(timer);
timer.schedule(task, RANDOM.nextInt(500));
timer.schedule(task, rand.nextInt(500));
}
}
}
@ -156,10 +196,13 @@ public class TestInjection {
public static boolean injectFailReplicaRequests() {
if (failReplicaRequests != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(failReplicaRequests);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Random test update fail");
}
}
@ -169,10 +212,13 @@ public class TestInjection {
public static boolean injectFailUpdateRequests() {
if (failUpdateRequests != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(failUpdateRequests);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Random test update fail");
}
}
@ -182,10 +228,13 @@ public class TestInjection {
public static boolean injectNonExistentCoreExceptionAfterUnload(String cname) {
if (nonExistentCoreExceptionAfterUnload != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(nonExistentCoreExceptionAfterUnload);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
throw new NonExistentCoreException("Core not found to unload: " + cname);
}
}
@ -195,11 +244,14 @@ public class TestInjection {
public static boolean injectUpdateLogReplayRandomPause() {
if (updateLogReplayRandomPause != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(updateLogReplayRandomPause);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
long rndTime = RANDOM.nextInt(1000);
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
long rndTime = rand.nextInt(1000);
log.info("inject random log replay delay of {}ms", rndTime);
try {
Thread.sleep(rndTime);
@ -214,11 +266,14 @@ public class TestInjection {
public static boolean injectUpdateRandomPause() {
if (updateRandomPause != null) {
Random rand = random();
if (null == rand) return true;
Pair<Boolean,Integer> pair = parseValue(updateRandomPause);
boolean enabled = pair.first();
int chanceIn100 = pair.second();
if (enabled && RANDOM.nextInt(100) >= (100 - chanceIn100)) {
long rndTime = RANDOM.nextInt(1000);
if (enabled && rand.nextInt(100) >= (100 - chanceIn100)) {
long rndTime = rand.nextInt(1000);
log.info("inject random update delay of {}ms", rndTime);
try {
Thread.sleep(rndTime);

View File

@ -98,4 +98,8 @@ public class TestTestInjection extends LuceneTestCase {
assertFalse(e.getMessage().toLowerCase(Locale.ENGLISH).contains("bad syntax"));
}
}
public void testUsingConsistentRandomization() {
assertSame(random(), TestInjection.random());
}
}