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()); + } }