You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2026/10/06 17:23:22 UTC
(lucene) branch main updated: Clean up parent test class references to junit. (#16763)
This is an automated email from the ASF dual-hosted git repository.
dweiss pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/lucene.git
The following commit(s) were added to refs/heads/main by this push:
new 7787f8bc1a4 Clean up parent test class references to junit. (#16763)
7787f8bc1a4 is described below
commit 7787f8bc1a43fb3baf8b2ccd8a7173c073add19e
Author: Dawid Weiss <da...@carrotsearch.com>
AuthorDate: Tue Oct 6 19:23:14 2026 +0200
Clean up parent test class references to junit. (#16763)
* Clean up parent test class references to junit. Make executor setup lazy.
* Remove comment.
---
.../apache/lucene/tests/util/LuceneTestCase.java | 248 +++++++++++----------
.../lucene/tests/util/LuceneTestCaseJupiter.java | 25 ++-
.../lucene/tests/util/LuceneTestCaseParent.java | 118 ++--------
.../tests/util/SetupAndRestoreCpuCoreCount.java | 38 ++++
.../tests/util/SetupAndRestoreQueryCache.java | 56 +++++
.../lucene/tests/util/SharedExecutorService.java | 72 ++++++
.../tests/util/TestLuceneTestCaseParentSanity.java | 83 +++++++
7 files changed, 414 insertions(+), 226 deletions(-)
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java
index dbe21490e5f..65e2406119e 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java
@@ -49,6 +49,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
@@ -355,121 +356,121 @@ public abstract non-sealed class LuceneTestCase extends LuceneTestCaseParent {
LuceneTestCase::random,
() -> RandomizedContext.current().getTargetClass());
- classRules =
- RuleChain.outerRule(new TestRuleIgnoreTestSuites())
- .around(
- new TestRuleAdapter() {
- @SuppressWarnings("NonFinalStaticField")
- private static TestFrameworkInfra testFrameworkInfra;
-
- @Override
- protected void before() throws Throwable {
- int maxCalls =
- Integer.parseInt(System.getProperty(SYSPROP_RANDOM_MAXCALLS, "0"));
- Supplier<Random> supplier = () -> RandomizedContext.current().getRandom();
- if (maxCalls > 0) {
- var finalizedSupplier = supplier;
- supplier = () -> new MaxCallCountRandom(finalizedSupplier.get(), maxCalls);
+ List<TestRule> orderedClassRules = new ArrayList<>();
+ orderedClassRules.add(new TestRuleIgnoreTestSuites());
+ orderedClassRules.add(
+ new TestRuleAdapter() {
+ @SuppressWarnings("NonFinalStaticField")
+ private static TestFrameworkInfra testFrameworkInfra;
+
+ @Override
+ protected void before() throws Throwable {
+ int maxCalls = Integer.parseInt(System.getProperty(SYSPROP_RANDOM_MAXCALLS, "0"));
+ Supplier<Random> supplier = () -> RandomizedContext.current().getRandom();
+ if (maxCalls > 0) {
+ var finalizedSupplier = supplier;
+ supplier = () -> new MaxCallCountRandom(finalizedSupplier.get(), maxCalls);
+ }
+
+ int maxAcquires = Integer.parseInt(System.getProperty(SYSPROP_RANDOM_MAXACQUIRES, "0"));
+ if (maxAcquires > 0) {
+ var finalizedSupplier = supplier;
+ supplier =
+ () -> {
+ if (randomCalls.incrementAndGet() > maxAcquires) {
+ throw new RuntimeException(
+ "Too many random() calls. Consider using LuceneTestCase.nonAssertingRandom for"
+ + " large loops or data generation.");
}
+ return finalizedSupplier.get();
+ };
+ }
+
+ var finalizedSupplier = supplier;
+ setTestFrameworkInfra(
+ null,
+ testFrameworkInfra =
+ new TestFrameworkInfra() {
+ @Override
+ public Random threadRandom() {
+ return finalizedSupplier.get();
+ }
- int maxAcquires =
- Integer.parseInt(System.getProperty(SYSPROP_RANDOM_MAXACQUIRES, "0"));
- if (maxAcquires > 0) {
- var finalizedSupplier = supplier;
- supplier =
- () -> {
- if (randomCalls.incrementAndGet() > maxAcquires) {
- throw new RuntimeException(
- "Too many random() calls. Consider using LuceneTestCase.nonAssertingRandom for"
- + " large loops or data generation.");
- }
- return finalizedSupplier.get();
- };
- }
+ @Override
+ public SetupAndRestoreStaticEnv getClassEnv() {
+ return setupAndRestoreClassEnv;
+ }
+
+ @Override
+ public TemporaryFilesSupplier getTempFilesSupplier() {
+ return tempFilesSupplier;
+ }
- var finalizedSupplier = supplier;
- setTestFrameworkInfra(
- null,
- testFrameworkInfra =
- new TestFrameworkInfra() {
- @Override
- public Random threadRandom() {
- return finalizedSupplier.get();
- }
-
- @Override
- public SetupAndRestoreStaticEnv getClassEnv() {
- return setupAndRestoreClassEnv;
- }
-
- @Override
- public TemporaryFilesSupplier getTempFilesSupplier() {
- return tempFilesSupplier;
- }
-
- @Override
- public SuiteFailureState getSuiteFailureState() {
- return suiteFailureMarker;
- }
-
- @Override
- public Field newField(
- Random random, String name, Object value, FieldType type) {
- return fieldToType.newField(random, name, value, type);
- }
- });
- }
-
- @Override
- protected void afterAlways(List<Throwable> errors) {
- setTestFrameworkInfra(testFrameworkInfra, null);
- }
- })
- .around(ignoreAfterMaxFailures)
- .around(suiteFailureMarker)
- .around(
- new VerifyTestClassNamingConvention(
- "org.apache.lucene", Pattern.compile("(.+\\.)(Test)([^.]+)")))
- .around(new TestRuleAssertionsRequired())
- .around(new TestRuleLimitSysouts(suiteFailureMarker))
- .around(new CallbacksToRuleAdapter(tempFilesSupplier))
- .around(new NoClassHooksShadowingRule())
- .around(
- new NoInstanceHooksOverridesRule() {
- @Override
- protected boolean verify(Method key) {
- String name = key.getName();
- return !(name.equals("setUp") || name.equals("tearDown"));
- }
- })
- .around(classNameRule = new TestRuleStoreClassName())
- .around(
- new TestRuleRestoreSystemProperties(
- // Enlist all properties to which we have write access (security manager);
- // these should be restored to previous state, no matter what the outcome of the
- // test.
-
- // We reset the default locale and timezone; these properties change as a
- // side-effect
- "user.language", "user.timezone"))
- .around(new CallbacksToRuleAdapter(setupAndRestoreClassEnv))
- .around(new CallbacksToRuleAdapter(fieldToType = new FieldToType()))
- .around(
- new CallbacksToRuleAdapter(
- new BeforeAfterCallback() {
@Override
- public void before() {
- // Save environment information to reproduce-info listener.
- // This listener can be invoked after all the tests and other callbacks have
- // completed; I don't see any clean way to pass it there.
- RunListenerPrintReproduceInfo.envInfoJunit4 =
- new TestEnvInfo(
- setupAndRestoreClassEnv.codec,
- setupAndRestoreClassEnv.similarity,
- setupAndRestoreClassEnv.locale,
- setupAndRestoreClassEnv.timeZone);
+ public SuiteFailureState getSuiteFailureState() {
+ return suiteFailureMarker;
}
- }));
+
+ @Override
+ public Field newField(
+ Random random, String name, Object value, FieldType type) {
+ return fieldToType.newField(random, name, value, type);
+ }
+ });
+ }
+
+ @Override
+ protected void afterAlways(List<Throwable> errors) {
+ setTestFrameworkInfra(testFrameworkInfra, null);
+ }
+ });
+ orderedClassRules.add(ignoreAfterMaxFailures);
+ orderedClassRules.add(suiteFailureMarker);
+ orderedClassRules.add(
+ new VerifyTestClassNamingConvention(
+ "org.apache.lucene", Pattern.compile("(.+\\.)(Test)([^.]+)")));
+ orderedClassRules.add(new TestRuleAssertionsRequired());
+ orderedClassRules.add(new TestRuleLimitSysouts(suiteFailureMarker));
+ orderedClassRules.add(new CallbacksToRuleAdapter(tempFilesSupplier));
+ orderedClassRules.add(new NoClassHooksShadowingRule());
+ orderedClassRules.add(
+ new NoInstanceHooksOverridesRule() {
+ @Override
+ protected boolean verify(Method key) {
+ String name = key.getName();
+ return !(name.equals("setUp") || name.equals("tearDown"));
+ }
+ });
+ orderedClassRules.add(classNameRule = new TestRuleStoreClassName());
+ orderedClassRules.add(
+ new TestRuleRestoreSystemProperties(
+ // Enlist all properties to which we have write access (security manager);
+ // these should be restored to previous state, no matter what the outcome of the
+ // test.
+
+ // We reset the default locale and timezone; these properties change as a
+ // side-effect
+ "user.language", "user.timezone"));
+ orderedClassRules.add(new CallbacksToRuleAdapter(setupAndRestoreClassEnv));
+ orderedClassRules.add(new CallbacksToRuleAdapter(fieldToType = new FieldToType()));
+ orderedClassRules.add(
+ new CallbacksToRuleAdapter(
+ new BeforeAfterCallback() {
+ @Override
+ public void before() {
+ // Save environment information to reproduce-info listener.
+ // This listener can be invoked after all the tests and other callbacks have
+ // completed; I don't see any clean way to pass it there.
+ RunListenerPrintReproduceInfo.envInfoJunit4 =
+ new TestEnvInfo(
+ setupAndRestoreClassEnv.codec,
+ setupAndRestoreClassEnv.similarity,
+ setupAndRestoreClassEnv.locale,
+ setupAndRestoreClassEnv.timeZone);
+ }
+ }));
+ orderedClassRules.addAll(suiteCallbacks().stream().map(CallbacksToRuleAdapter::new).toList());
+ classRules = asRuleChain(orderedClassRules);
}
// -----------------------------------------------------------------
@@ -490,13 +491,26 @@ public abstract non-sealed class LuceneTestCase extends LuceneTestCaseParent {
* This controls how individual test rules are nested. It is important that _all_ rules declared
* in {@link LuceneTestCase} are executed in proper order if they depend on each other.
*/
- @Rule
- public final TestRule ruleChain =
- RuleChain.outerRule(testFailureMarker)
- .around(ignoreAfterMaxFailures)
- .around(threadAndTestNameRule)
- .around(new CallbacksToRuleAdapter(new TestRuleSetupAndRestoreInstanceEnv()))
- .around(parentChainCallRule);
+ @Rule public final TestRule ruleChain;
+
+ {
+ List<TestRule> orderedTestRules = new ArrayList<>();
+ orderedTestRules.add(testFailureMarker);
+ orderedTestRules.add(ignoreAfterMaxFailures);
+ orderedTestRules.add(threadAndTestNameRule);
+ orderedTestRules.add(new CallbacksToRuleAdapter(new TestRuleSetupAndRestoreInstanceEnv()));
+ orderedTestRules.add(parentChainCallRule);
+ orderedTestRules.addAll(testCallbacks().stream().map(CallbacksToRuleAdapter::new).toList());
+ ruleChain = asRuleChain(orderedTestRules);
+ }
+
+ private static RuleChain asRuleChain(List<? extends TestRule> rules) {
+ RuleChain chain = RuleChain.emptyRuleChain();
+ for (TestRule rule : rules) {
+ chain = chain.around(rule);
+ }
+ return chain;
+ }
/** A counter of calls to {@link #random()} if {@link #SYSPROP_RANDOM_MAXACQUIRES} is defined. */
@SuppressWarnings("NonFinalStaticField")
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java
index ebd6865444d..fa627f7fcaa 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java
@@ -24,6 +24,7 @@ import com.carrotsearch.randomizedtesting.jupiter.SystemThreadFilter;
import java.io.Closeable;
import java.io.PrintStream;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -360,15 +361,15 @@ public abstract non-sealed class LuceneTestCaseJupiter extends LuceneTestCasePar
// This is the chain of before-after callbacks that must be called in the right order for
// compatibility
// with junit4 implementation.
- this.beforeAfters =
- new OrderedBeforeAfterCallbacks(
- List.of(
- perThreadRandom,
- installFrameworkInfraSupport,
- classEnvRule,
- fieldToType,
- installEnvInfo,
- tempFileSupplier));
+ List<BeforeAfterCallback> callbacks = new ArrayList<>();
+ callbacks.add(perThreadRandom);
+ callbacks.add(installFrameworkInfraSupport);
+ callbacks.add(classEnvRule);
+ callbacks.add(fieldToType);
+ callbacks.add(installEnvInfo);
+ callbacks.add(tempFileSupplier);
+ callbacks.addAll(LuceneTestCaseParent.suiteCallbacks());
+ this.beforeAfters = new OrderedBeforeAfterCallbacks(callbacks);
beforeAfters.before();
}
@@ -526,8 +527,10 @@ public abstract non-sealed class LuceneTestCaseJupiter extends LuceneTestCasePar
@Override
public void beforeEach(ExtensionContext context) throws Exception {
- this.callbacks =
- new OrderedBeforeAfterCallbacks(List.of(new TestRuleSetupAndRestoreInstanceEnv()));
+ List<BeforeAfterCallback> testCallbacks = new ArrayList<>();
+ testCallbacks.add(new TestRuleSetupAndRestoreInstanceEnv());
+ testCallbacks.addAll(LuceneTestCaseParent.testCallbacks());
+ this.callbacks = new OrderedBeforeAfterCallbacks(testCallbacks);
callbacks.before();
}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseParent.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseParent.java
index b1cee33890f..f43e085990d 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseParent.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseParent.java
@@ -16,7 +16,6 @@
*/
package org.apache.lucene.tests.util;
-import static com.carrotsearch.randomizedtesting.RandomizedTest.frequently;
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsInt;
import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
@@ -57,9 +56,6 @@ import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.AssertionFailedError;
import org.apache.lucene.analysis.Analyzer;
@@ -121,7 +117,6 @@ import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LRUQueryCache;
import org.apache.lucene.search.Query;
-import org.apache.lucene.search.QueryCache;
import org.apache.lucene.search.QueryCachingPolicy;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
@@ -157,7 +152,6 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CommandLineUtil;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.InfoStream;
-import org.apache.lucene.util.NamedThreadFactory;
import org.apache.lucene.util.SuppressForbidden;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CompiledAutomaton;
@@ -165,11 +159,7 @@ import org.apache.lucene.util.automaton.Operations;
import org.apache.lucene.util.automaton.RegExp;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
-import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.internal.AssumptionViolatedException;
/**
@@ -1156,7 +1146,7 @@ public abstract sealed class LuceneTestCaseParent extends Assert
leaves,
maxDocsPerSlice,
maxSegmentsPerSlice,
- concurrency == Concurrency.INTRA_SEGMENT && frequently());
+ concurrency == Concurrency.INTRA_SEGMENT && TestUtil.nextInt(random(), 0, 100) < 90);
}
/**
@@ -2534,95 +2524,27 @@ public abstract sealed class LuceneTestCaseParent extends Assert
}
}
- private static final QueryCache DEFAULT_QUERY_CACHE = IndexSearcher.getDefaultQueryCache();
- private static final QueryCachingPolicy DEFAULT_CACHING_POLICY =
- IndexSearcher.getDefaultQueryCachingPolicy();
- private static LRUQueryCache classQueryCache;
+ /** A before-after hook providing {@link ExecutorService}. */
+ private static final SharedExecutorService sharedExecutor = new SharedExecutorService();
- private static LRUQueryCache createAndOverrideDefaultQueryCache() {
- LRUQueryCache lruQueryCache =
- new LRUQueryCache(10000, 1 << 25, _ -> true, Float.POSITIVE_INFINITY);
- IndexSearcher.setDefaultQueryCache(lruQueryCache);
- IndexSearcher.setDefaultQueryCachingPolicy(MAYBE_CACHE_POLICY);
- return lruQueryCache;
- }
-
- @BeforeClass
- public static void overrideClassDefaultQueryCache() {
- // we need to reset the query cache in an @BeforeClass so that tests that
- // instantiate an IndexSearcher in an @BeforeClass method use a fresh new cache
- classQueryCache = createAndOverrideDefaultQueryCache();
- }
-
- @AfterClass
- public static void resetClassDefaultQueryCache() throws IOException {
- IndexSearcher.setDefaultQueryCache(DEFAULT_QUERY_CACHE);
- IndexSearcher.setDefaultQueryCachingPolicy(DEFAULT_CACHING_POLICY);
- if (classQueryCache != null) {
- classQueryCache.clear();
- }
- IOUtils.close(classQueryCache);
- classQueryCache = null;
- }
-
- private LRUQueryCache methodQueryCache;
-
- @Before
- public void overrideTestDefaultQueryCache() {
- methodQueryCache = createAndOverrideDefaultQueryCache();
- }
-
- @After
- public void resetTestDefaultQueryCache() throws IOException {
- // Restore the class-level cache so TestRules and @AfterClass still see a
- // test-scoped cache rather than the process-wide production default.
- IndexSearcher.setDefaultQueryCache(classQueryCache);
- IndexSearcher.setDefaultQueryCachingPolicy(MAYBE_CACHE_POLICY);
- if (methodQueryCache != null) {
- methodQueryCache.clear();
- }
- IOUtils.close(methodQueryCache);
- methodQueryCache = null;
- }
-
- @BeforeClass
- public static void setupCPUCoreCount() {
- // Randomize core count so CMS varies its dynamic defaults, and this also "fixes" core
- // count from the master seed so it will always be the same on reproduce:
- int numCores = TestUtil.nextInt(random(), 1, 4);
- System.setProperty(
- ConcurrentMergeScheduler.DEFAULT_CPU_CORE_COUNT_PROPERTY, Integer.toString(numCores));
- }
-
- @AfterClass
- public static void restoreCPUCoreCount() {
- System.clearProperty(ConcurrentMergeScheduler.DEFAULT_CPU_CORE_COUNT_PROPERTY);
- }
-
- private static ExecutorService executor;
-
- @BeforeClass
- public static void setUpExecutorService() {
- int threads = TestUtil.nextInt(random(), 1, 2);
- executor =
- new ThreadPoolExecutor(
- threads,
- threads,
- 0L,
- TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<>(),
- new NamedThreadFactory("LuceneTestCase"));
- // uncomment to intensify LUCENE-3840
- // executor.prestartAllCoreThreads();
- if (VERBOSE) {
- System.out.println("NOTE: Created shared ExecutorService with " + threads + " threads");
- }
+ /**
+ * Suite-level setup and cleanup of the shared state used by this class. Test framework-specific
+ * subclasses must call these, in order, from their suite-level before/after hooks, after {@link
+ * #setTestFrameworkInfra} has been called.
+ */
+ static List<BeforeAfterCallback> suiteCallbacks() {
+ // we need to reset the query cache at suite level so that tests that instantiate an
+ // IndexSearcher in suite-level hooks use a fresh new cache
+ return List.of(
+ new SetupAndRestoreQueryCache(), new SetupAndRestoreCpuCoreCount(), sharedExecutor);
}
- @AfterClass
- public static void shutdownExecutorService() {
- TestUtil.shutdownExecutorService(executor);
- executor = null;
+ /**
+ * Test-level setup and cleanup of the shared state used by this class. Test framework-specific
+ * subclasses must call these, in order, from their test-level before/after hooks.
+ */
+ static List<BeforeAfterCallback> testCallbacks() {
+ return List.of(new SetupAndRestoreQueryCache());
}
/** Create a new searcher over the reader. This searcher might randomly use threads. */
@@ -2720,7 +2642,7 @@ public abstract sealed class LuceneTestCaseParent extends Assert
if (random.nextBoolean()) {
ex = null;
} else {
- ex = executor;
+ ex = sharedExecutor.get();
if (VERBOSE) {
System.out.println("NOTE: newSearcher using shared ExecutorService");
}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreCpuCoreCount.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreCpuCoreCount.java
new file mode 100644
index 00000000000..c1a934b3683
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreCpuCoreCount.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.tests.util;
+
+import org.apache.lucene.index.ConcurrentMergeScheduler;
+
+/**
+ * Randomizes the CPU core count seen by {@link ConcurrentMergeScheduler} so that it varies its
+ * dynamic defaults. This also "fixes" the core count from the master seed so it will always be the
+ * same on reproduce.
+ */
+final class SetupAndRestoreCpuCoreCount implements BeforeAfterCallback {
+ @Override
+ public void before() {
+ int numCores = TestUtil.nextInt(LuceneTestCaseParent.random(), 1, 4);
+ System.setProperty(
+ ConcurrentMergeScheduler.DEFAULT_CPU_CORE_COUNT_PROPERTY, Integer.toString(numCores));
+ }
+
+ @Override
+ public void after() {
+ System.clearProperty(ConcurrentMergeScheduler.DEFAULT_CPU_CORE_COUNT_PROPERTY);
+ }
+}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreQueryCache.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreQueryCache.java
new file mode 100644
index 00000000000..d72d028eefb
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SetupAndRestoreQueryCache.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.tests.util;
+
+import java.io.IOException;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LRUQueryCache;
+import org.apache.lucene.search.QueryCache;
+import org.apache.lucene.search.QueryCachingPolicy;
+import org.apache.lucene.util.IOUtils;
+
+/**
+ * Replaces {@link IndexSearcher}'s default query cache and caching policy with a fresh cache and a
+ * policy that randomly caches. The previous defaults are restored afterwards, so this callback can
+ * be nested (suite level and test level).
+ */
+final class SetupAndRestoreQueryCache implements BeforeAfterCallback {
+ private QueryCache previousCache;
+ private QueryCachingPolicy previousPolicy;
+ private LRUQueryCache cache;
+
+ @Override
+ public void before() {
+ previousCache = IndexSearcher.getDefaultQueryCache();
+ previousPolicy = IndexSearcher.getDefaultQueryCachingPolicy();
+
+ cache = new LRUQueryCache(10000, 1 << 25, _ -> true, Float.POSITIVE_INFINITY);
+ IndexSearcher.setDefaultQueryCache(cache);
+ IndexSearcher.setDefaultQueryCachingPolicy(LuceneTestCaseParent.MAYBE_CACHE_POLICY);
+ }
+
+ @Override
+ public void after() throws IOException {
+ IndexSearcher.setDefaultQueryCache(previousCache);
+ IndexSearcher.setDefaultQueryCachingPolicy(previousPolicy);
+ if (cache != null) {
+ cache.clear();
+ }
+ IOUtils.close(cache);
+ cache = null;
+ }
+}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/SharedExecutorService.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SharedExecutorService.java
new file mode 100644
index 00000000000..037e46c0ced
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/SharedExecutorService.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.tests.util;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import org.apache.lucene.util.NamedThreadFactory;
+
+/** Lazily sets up and shuts down an {@link ExecutorService} shared by all tests in a suite. */
+final class SharedExecutorService implements BeforeAfterCallback, Supplier<ExecutorService> {
+ private int threadCount;
+ // Just for sanity checks; we don't need full thread coordination here.
+ private volatile boolean active;
+ private ExecutorService executor;
+
+ @Override
+ public void before() {
+ // Pick the thread count at start, for consistency.
+ threadCount = TestUtil.nextInt(LuceneTestCaseParent.random(), 1, 2);
+ active = true;
+ }
+
+ @Override
+ public void after() {
+ if (executor != null) {
+ TestUtil.shutdownExecutorService(executor);
+ }
+ executor = null;
+ active = false;
+ }
+
+ /** Returns the shared executor. Must be called within the before-after scope. */
+ @Override
+ public ExecutorService get() {
+ synchronized (this) {
+ if (!active) {
+ throw new AssertionError("Shared executor is not available outside of the suite scope.");
+ }
+
+ if (executor == null) {
+ executor =
+ new ThreadPoolExecutor(
+ threadCount,
+ threadCount,
+ 0L,
+ TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(),
+ new NamedThreadFactory("LuceneTestCase"));
+ // uncomment to intensify LUCENE-3840
+ // executor.prestartAllCoreThreads();
+ }
+ return executor;
+ }
+ }
+}
diff --git a/lucene/test-framework/src/test/org/apache/lucene/tests/util/TestLuceneTestCaseParentSanity.java b/lucene/test-framework/src/test/org/apache/lucene/tests/util/TestLuceneTestCaseParentSanity.java
new file mode 100644
index 00000000000..7142a2b0db3
--- /dev/null
+++ b/lucene/test-framework/src/test/org/apache/lucene/tests/util/TestLuceneTestCaseParentSanity.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.tests.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.classfile.Attributes;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.ClassModel;
+import java.lang.classfile.constantpool.ClassEntry;
+import java.lang.classfile.constantpool.Utf8Entry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Ensures {@link LuceneTestCaseParent} remains (mostly) independent of the test framework used: any
+ * junit4 or junit-jupiter specific code should live in {@link LuceneTestCase} or {@link
+ * LuceneTestCaseJupiter}.
+ */
+public class TestLuceneTestCaseParentSanity extends LuceneTestCase {
+ /** The only classes {@link LuceneTestCaseParent} is allowed to reference (as bytecode refs). */
+ private static final Set<String> ALLOWED =
+ Set.of("org/junit/Assert", "org/junit/internal/AssumptionViolatedException");
+
+ private static final String JUNIT_PREFIX = "org/junit/";
+
+ public void testNoJUnitSpecificReferences() throws IOException {
+ Set<String> referenced = new TreeSet<>();
+
+ // Parent test class and its nested classes.
+ List<String> classFiles = new ArrayList<>();
+ ClassModel host = parse(LuceneTestCaseParent.class.getSimpleName() + ".class");
+ classFiles.add(host.thisClass().asInternalName());
+ host.findAttribute(Attributes.nestMembers())
+ .ifPresent(
+ attr ->
+ attr.nestMembers().stream()
+ .map(ClassEntry::asInternalName)
+ .forEach(classFiles::add));
+
+ for (String internalName : classFiles) {
+ ClassModel model =
+ parse(internalName.substring(internalName.lastIndexOf('/') + 1) + ".class");
+ for (var entry : model.constantPool()) {
+ if (entry instanceof Utf8Entry utf8) {
+ var internalClassRef = utf8.stringValue();
+ if (internalClassRef.startsWith(JUNIT_PREFIX) && !ALLOWED.contains(internalClassRef)) {
+ referenced.add(utf8.stringValue());
+ }
+ }
+ }
+ }
+
+ assertEquals(
+ "LuceneTestCaseParent must not reference test framework-specific classes, move such code to"
+ + " LuceneTestCase or LuceneTestCaseJupiter.",
+ Set.of(),
+ referenced);
+ }
+
+ private static ClassModel parse(String resource) throws IOException {
+ try (InputStream is = LuceneTestCaseParent.class.getResourceAsStream(resource)) {
+ assertNotNull("Class file not found: " + resource, is);
+ return ClassFile.of().parse(is.readAllBytes());
+ }
+ }
+}