diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 6bac627ee80..68834576ad4 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -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.
diff --git a/solr/core/src/java/org/apache/solr/util/TestInjection.java b/solr/core/src/java/org/apache/solr/util/TestInjection.java
index cc3f85d4472..03de74d02f1 100644
--- a/solr/core/src/java/org/apache/solr/util/TestInjection.java
+++ b/solr/core/src/java/org/apache/solr/util/TestInjection.java
@@ -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 LuceneTestCase
is loadable via the ClassLoader used
+ * to load this class. LuceneTestCase.random()
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 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 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 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 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 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 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 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);
diff --git a/solr/core/src/test/org/apache/solr/util/TestTestInjection.java b/solr/core/src/test/org/apache/solr/util/TestTestInjection.java
index 418b4a48ee7..c4269ccbbf2 100644
--- a/solr/core/src/test/org/apache/solr/util/TestTestInjection.java
+++ b/solr/core/src/test/org/apache/solr/util/TestTestInjection.java
@@ -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());
+ }
}