LuceneTestCase finds test methods by name; plain JUnit 4 needs @Test. #16028 removed @Test on both sides of that line.
- Lucene's build runs every class whose file is named
Test*.class; on main, JUnit 4 classes run through the JUnit Platform's vintage engine. - Inside a class, the JUnit 4 runner decides which methods are tests. The default runner and
Parameterizedtake only methods annotated with@org.junit.Test. LuceneTestCaseadds a rule: every public, non-static, no-argument method whose name starts withtest(and is not overridden lower down) is a test too. In its subclasses@Testis redundant.- A few test classes avoid
LuceneTestCaseon purpose, for example so a forked JVM starts with no Lucene class initialized, or because the distribution tests must not load Lucene's test framework. They need@Test.
LuceneTestCase finds test* methods by name while plain JUnit 4 needs @Test, and what the six classes report on main after #16028; then what you expect and what you get. Select the still to open it at full size. Generated with AI, checked against the code.- On main, run
./gradlew :lucene:core.tests:test --tests org.apache.lucene.core.tests.TestRuntimeDependenciesSane. The class has 4test*methods that check the tests load JUnit, Lucene core and themselves as named modules. - You expect its 4 tests to run and pass, as they do on main with the annotations restored (
(SUCCESS): 4 tests); on 10.5.2 and on main before May 2026 all 4 methods are found. - Instead the build fails with "1 test task executed but no tests matched the provided --tests filters?". Without the filter,
:lucene:core.tests:testsucceeds, and the 4 methods never run. - Why: #16028 removed 1,334
@Testlines fromtest*methods.LuceneTestCaserunstest*methods by name, so for its subclasses that is harmless. These six classes do not extend it, and plain JUnit 4 runs only@Testmethods. - On main: 2 classes report 0 tests, 3 report one green entry named after the class (all of
distribution.testsis 2 such entries), and the opt-in Hunspell conformance run fails with "No runnable methods". Restoring the 18 annotations brings all 18 methods back.
Only Lucene developers and CI on main and branch_11x. Every default Gradle run no longer runs 16 methods in four classes: the test-module checks, the class-loading deadlock guard, distribution.tests' module-layer checks and monitor's visibility calls. The documented Hunspell conformance run fails. No jar changes, no release has it, and every restored test that ran passes.
Mechanism, terms and numbers573 words
#16028 ("Misc junit4 test cleanups", main, 2026-05-02) removed 1,334 @Test lines from test* methods in 203 test files. For LuceneTestCase subclasses that is harmless, because LuceneTestCase also runs test* methods by name. Six of the classes do not extend LuceneTestCase, on purpose, and for them JUnit runs only @Test methods. So 18 test methods never run on main, and the build reports success: TestRuntimeDependenciesSane and TestCandidateMatcherVisibility report no tests, TestClassloadingDeadlock, TestModularLayer and TestScripts each report one passing entry named after the class with no method run, and the opt-in TestHunspellRepositoryTestCases fails with "No runnable methods". No shipped code, API or index format changes and no release has it; main and the 11.0 branch branch_11x do. Restoring the 6 imports and 18 annotations (the reverse of #16028 in these files) brings the 18 methods back: 17 run and pass, and TestScripts' GUI test is reported as skipped, as it was before #16028, because the build never passes its tests.gui option to the test JVM.
LuceneTestCase is @RunWith(RandomizedRunner.class) with @TestMethodProviders({LuceneJUnit3MethodProvider.class, JUnit4MethodProvider.class}) (LuceneTestCase.java:113-114). LuceneJUnit3MethodProvider selects public, non-static, no-argument methods named test* (LuceneJUnit3MethodProvider.java:39-42), skipping methods overridden or shadowed lower in the class hierarchy (:35-36); JUnit4MethodProvider selects @Test methods. Every class #16028 touched apart from the six below reaches LuceneTestCase.
| class | how it runs | test methods | on main |
|---|---|---|---|
TestRuntimeDependenciesSane (core.tests) | plain JUnit 4; "Intentionally not a subclass of {@code LuceneTestCase}." (:22) | 4 | no tests |
TestCandidateMatcherVisibility (monitor) | plain JUnit 4 (:29) | 3 | no tests |
TestClassloadingDeadlock (core) | @RunWith(RandomizedRunner.class) extends Assert, no method providers (:54-55) | 1 | one green entry named after the class |
TestModularLayer (distribution.tests) | extends AbstractLuceneDistributionTest extends RandomizedTest | 8 | one green entry |
TestScripts (distribution.tests) | same; its test is @RequiresGUI, which the Gradle build skips on every run (see below) | 1 | one green entry |
TestHunspellRepositoryTestCases (analysis-common) | @RunWith(Parameterized.class); runs only with -Phunspell.repo.path | 1 | InvalidTestClassError: No runnable methods |
RandomizedRunner without a @TestMethodProviders annotation falls back to JUnit4MethodProvider, which takes only @Test methods (an independent verifier read this from the randomizedtesting-runner 2.9.2 bytecode; the Gradle runs show the effect). The distribution tests' base class says so itself: "the default {@code LuceneTestCase} configuration setup is not used (you have to annotate test for JUnit, for example)" (AbstractLuceneDistributionTest.java:39-42).
- The build selects classes by file name, not by scanning for tests:
setScanForTestClasses(false),include("**/Test*.class")(TestsAndRandomizationPlugin.java:545-546), and on main runs JUnit 4 through the vintage engine (:568-569, since #15949). - On that engine a plain JUnit 4 class with no
@Testyields no test; aRandomizedRunnerclass with no test method yields one passing entry named after the class (JUnit XML:<testcase name="TestClassloadingDeadlock" classname="Gradle Test Run :lucene:core:test" time="0.005"/>), and none of its methods run (nor its@BeforeClass: an independent check ranTestModularLayerwithout a distribution path, which its@BeforeClassrejects,TestModularLayer.java:69-74, and it still "passed");Parameterizedfails with "No runnable methods". - Lucene turns off Gradle's per-task no-match failure (
FailOnNoMatchingFilteredTestsPlugin.java:98) and fails only when a--testsfilter matched nothing anywhere, a case it describes as "usually caused by a typo in the filter pattern" (:36-38). An unfiltered run reports nothing.
In these six files bfa16d51c21 removed exactly 6 import org.junit.Test; lines and 18 @Test lines, and nothing else. The six files are unchanged since, apart from one comment in TestClassloadingDeadlock (#16027).
Exposure and likelihood386 words
- Who: Lucene developers and CI on main and
branch_11x. Users of Lucene are not affected: #16028 changed only test sources. - What is no longer checked on main:
TestRuntimeDependenciesSane(4): the tests of a modular project really load JUnit, Lucene core and the test and main source sets as named modules (added with LUCENE-10328, which fixed that module path).TestClassloadingDeadlock(1): a forked JVM whose 20 threads initialiseCodec,PostingsFormat,DocValuesFormat,TestSecrets,IndexWriterandSegmentReaderat once must exit within 60 s. #15864 (2026-03-25, "Fix circular dependency deadlock in TestSecrets initialization") extended it; 38 days later it stopped running on main.TestModularLayer(8): the binary distribution's module names, versions, exported and opened packages, service providers and multi-release jar. WithTestScripts(1, the Luke launch check) these are the only test classes oflucene/distribution.tests, so the GitHub workflow dedicated to that module (run on every pull request and push to main) runs no test method.TestCandidateMatcherVisibility(3): monitor'sCandidateMatchermethods are callable from another package. Its compile-time part still holds; its three runtime calls no longer run.TestHunspellRepositoryTestCases: the conformance run against Hunspell's own test data, documented as-Phunspell.repo.path(lucene/analysis/common/build.gradle:244), now fails whenever it is run with that property (without it, the class is skipped by its assumption, as in every version). Loud, not silent; no GitHub workflow sets the property.
- Default or opt-in: every default Gradle run, local or CI, no longer runs 16 methods in four classes (4 + 3 + 1 + 8).
TestScripts' one method is also no longer found, but the build skipped it on every run already before #16028: the build registerstests.gui(on by default for CI builds) but never passes it to the test JVM, so even-Ptests.gui=trueorCI=trueleaves it skipped (an older, separate matter, from #14764; not claimed here). The Hunspell runner only matters when someone opts in. - Scale: 18 methods; main since 2026-05-02, and
branch_11x, the 11.0 branch cut from main on 2026-10-28, which has the six files unchanged; no release. A fix needs a backport tobranch_11xto reach 11.0.0. - No hidden regression found: with the annotations restored, every method that ran passes (17 of 18; the build skips
TestScripts' GUI test), and the deadlock guard passed 20 of 20 forked runs earlier.
One JUnit 4 test in lucene/monitor. It runs main's unmodified TestCandidateMatcherVisibility (3 test* methods, no @Test, not a LuceneTestCase) through JUnitCore and asserts that its 3 methods run. Deterministic, single thread.
/*
* 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.monitor.outsidepackage;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
public class TestCandidateMatcherVisibilityRuns {
@Test
public void testItsThreeTestMethodsRun() {
Result result = JUnitCore.runClasses(TestCandidateMatcherVisibility.class);
// expected: 3 tests run, no failure; actual: initializationError "No runnable methods"
assertEquals(
"run=3 failures=[]",
"run=" + result.getRunCount() + " failures=" + result.getFailures());
}
}
cp TestCandidateMatcherVisibilityRuns.java $LUCENE/lucene/monitor/src/test/org/apache/lucene/monitor/outsidepackage/
cd $LUCENE && ./gradlew :lucene:monitor:test --tests org.apache.lucene.monitor.outsidepackage.TestCandidateMatcherVisibilityRuns -Ptests.seed=DEADBEEF
# -Ptests.asserts=true and =false give the same result
- Expected
JUnitCoreruns the 3 methods ofTestCandidateMatcherVisibilitywith no failure:run=3 failures=[].- Observed
run=1 failures=[initializationError(...TestCandidateMatcherVisibility): Invalid test class ... 1. No runnable methods], with assertions on and off.
More files, environment, full output278 words
TestCandidateMatcherVisibilityRuns sits next to the class it runs, so main's own test source is compiled unmodified. It shows the plain JUnit 4 case, one of the six; the other five show the same cause in the build (see "The finding in 30 seconds"). Nothing random; the seed is only for the build.
No new file is needed to see it: on main, ./gradlew :lucene:core.tests:test --tests org.apache.lucene.core.tests.TestRuntimeDependenciesSane fails with "no tests matched the provided --tests filters?", while with the annotations restored it runs 4 tests ((SUCCESS): 4 tests); 10.6.0 keeps all 4 @Test annotations.
As a regression test: this same test (google-java-format wants the two assertEquals arguments on one line).
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); upstream main f815e679bf6c has the same six files |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 |
cp TestCandidateMatcherVisibilityRuns.java $LUCENE/lucene/monitor/src/test/org/apache/lucene/monitor/outsidepackage/
cd $LUCENE && ./gradlew :lucene:monitor:test --tests org.apache.lucene.monitor.outsidepackage.TestCandidateMatcherVisibilityRuns -Ptests.seed=DEADBEEF
# -Ptests.asserts=true and =false give the same result
# gradle, main 645d4fe14a20, seed DEADBEEF, -Ptests.asserts=true (the same with =false)
TestCandidateMatcherVisibilityRuns > testItsThreeTestMethodsRun FAILED
org.junit.ComparisonFailure: expected:<run=[3 failures=[]]> but was:<run=[1 failures=[initializationError(
org.apache.lucene.monitor.outsidepackage.TestCandidateMatcherVisibility): Invalid test class
'org.apache.lucene.monitor.outsidepackage.TestCandidateMatcherVisibility':
1. No runnable methods]]>
:lucene:monitor:test (FAILURE): 1 test, 1 failure
# with the six classes' @Test restored: :lucene:monitor:test (SUCCESS): 1 test
# how the six classes look in the same build (not the repro):
# --tests ...core.tests.TestRuntimeDependenciesSane, or ...outsidepackage.TestCandidateMatcherVisibility:
> 1 test task executed but no tests matched the provided --tests filters?
# --tests org.apache.lucene.index.TestClassloadingDeadlock:
:lucene:core:test (SUCCESS): 1 test (the entry is named TestClassloadingDeadlock; testDeadlock never runs)
# :lucene:distribution.tests:test:
:lucene:distribution.tests:test (SUCCESS): 2 tests (entries TestModularLayer and TestScripts, no method runs)
# -Phunspell.repo.path=<a test directory> --tests ...hunspell.TestHunspellRepositoryTestCases:
TestHunspellRepositoryTestCases > initializationError FAILED
org.junit.runners.model.InvalidTestClassError: ... 1. No runnable methods- The classes avoid
LuceneTestCaseon purpose: "Intentionally not a subclass of {@code LuceneTestCase}."; the distribution tests' base class says "you have to annotate test for JUnit". MIGRATE.md: "LuceneTestCase(JUnit4) is unchanged." The build still documents-Phunspell.repo.pathfor the Hunspell runner.- #16028 called the annotations "spurious", true only under
LuceneTestCase. It had no review, and 10.6.0 keeps every annotation.
The full argument147 words
- The classes:
TestRuntimeDependenciesSane.java:22"Intentionally not a subclass of {@code LuceneTestCase}.";TestClassloadingDeadlock.java:51-52"This test does not extend LuceneTestCase to prevent static class initialization when spawned as a subprocess";AbstractLuceneDistributionTest.java:39-42"... (you have to annotate test for JUnit, for example)";TestHunspellRepositoryTestCases.java:32-34"checks all Hunspell's test data". - The build:
lucene/analysis/common/build.gradle:244registershunspell.repo.pathas "Path to hunspell repo path for TestHunspellRepositoryTestCases". - The migration notes:
lucene/MIGRATE.md:45-46"These apply if you extendLuceneTestCaseJupiter.LuceneTestCase(JUnit4) is unchanged."; for Jupiter,:48-50"Methods must be annotated with@Test. Method prefixtest*is not sufficient." - The PR: body "This removes spurious Test annotations and some junit4-specific syntax."; commit message "Drop @Test annotation on methods that are test* prefixed." Merged 13 minutes after it was opened, with no review or comment. No
CHANGES.txtorMIGRATE.mdentry retires these tests; 10.6.0,branch_10_6andbranch_10_5keep every annotation, as didbranch_10xuntil it was decommissioned (#16911, 2026-10-29).
The devil's-advocate check149 words
The strongest case (built on purpose by an intended-behaviour checker): the removal was deliberate, as the commit message says; nothing user-facing changes, so strictly it is not a Lucene bug; TestCandidateMatcherVisibility is a compile-time check that still compiles; the Hunspell runner is opt-in, run by no workflow, and fails loudly; and Lucene's no-match check is meant to catch filter typos, not empty classes.
Why it fails: the deliberate step is "drop @Test where test* already runs", which is false for exactly these six classes, and nobody chose to retire their tests. Their own comments, MIGRATE.md and the build option say they should run, and with the 18 annotations back all 18 are found again and 17 run and pass. For five of the six the loss is silent, including the whole distribution test module. It is a test-coverage regression and a small follow-up to #16028, not a product bug.
Recommended A. Restore the 6 imports and 18 @Test annotations (all six classes)
Restore import org.junit.Test; and @Test on the 18 methods of all six classes (the exact reverse of #16028 in these files, +24 lines), on main and branch_11x. With it the repro passes, 17 of the 18 methods run and pass in Gradle (the GUI test is skipped, as before), and the monitor, core.tests and Hunspell suites pass.
--- a/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java
+++ b/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java
import org.apache.lucene.index.IndexWriter;
+import org.junit.Test;
/** Intentionally not a subclass of {@code LuceneTestCase}. */
public class TestRuntimeDependenciesSane {
+ @Test
public void testExternalDependenciesAreModules() {
# ... and the same for the other 17 test* methods of the six classesAll options and trade-offs4 options · 455 words
Option A was validated with Lucene's Gradle build in a scratch worktree of 645d4fe14a20. B and D are alternatives that change more; C is an optional guard against the same mistake recurring. The hunt's first fix sketch covered only three classes; the fix must cover all six.
A. Restore the 6 imports and 18 @Test annotations (all six classes) (validated by the checks) (recommended)
git diff bfa16d51c21 bfa16d51c21^ -- the six files: TestRuntimeDependenciesSane +5, TestModularLayer +9, TestCandidateMatcherVisibility +4, TestClassloadingDeadlock, TestScripts and TestHunspellRepositoryTestCases +2 each. It applies cleanly to 645d4fe14a20.
--- a/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java
+++ b/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java
import org.apache.lucene.index.IndexWriter;
+import org.junit.Test;
/** Intentionally not a subclass of {@code LuceneTestCase}. */
public class TestRuntimeDependenciesSane {
+ @Test
public void testExternalDependenciesAreModules() {
# ... and the same for the other 17 test* methods of the six classesTrade-offs
None for production code: test sources only.
What the checks validated
- Gradle, seed DEADBEEF: the repro passes with assertions on and off.
- Selected by name:
TestCandidateMatcherVisibility3 tests,TestRuntimeDependenciesSane4,TestClassloadingDeadlock1 (testDeadlock),TestHunspellRepositoryTestCases1 (a one-case test directory); all pass. :lucene:distribution.tests:teston the assembled distribution: 9 tests, 0 failures, 1 skipped (8 of 8TestModularLayerpass;testLukeCanBeLaunchedis@RequiresGUIand is skipped on every run, also with-Ptests.gui=trueand withCI=true, because the build does not passtests.guito the test JVM).- Unfiltered
:lucene:monitor:test(185 tests, 1 skipped) and:lucene:core.tests:test(7), and theorg.apache.lucene.analysis.hunspell.*tests (158, 13 skipped): 0 failures. checkGoogleJavaFormatpasses for core, core.tests, distribution.tests and analysis-common; monitor fails only on the repro file (one line to join).
Add @TestMethodProviders({LuceneJUnit3MethodProvider.class, JUnit4MethodProvider.class}) to the RandomizedRunner classes, or make the plain classes extend LuceneTestCase.
Trade-offs
Works against why the classes exist: they avoid LuceneTestCase on purpose, and the distribution tests must not depend on Lucene classes. It also does nothing for the plain and Parameterized classes unless they change runner.
What the checks validated
Not run.
A build or test-framework check that fails when a Test* class yields no test method, or that flags public void test*() methods with neither @Test nor LuceneTestCase.
Trade-offs
More build machinery; it must allow abstract bases, the Hunspell runner's assumption skip and the RandomizedRunner placeholder entry. Would have caught #16028 at once; not needed to fix this bug.
What the checks validated
Not run.
Use @org.junit.jupiter.api.Test, the direction main is taking with #16027.
Trade-offs
A larger change: Parameterized becomes @ParameterizedTest, and the RandomizedRunner features these classes use (RandomizedContext, the @RequiresGUI test group, @ThreadLeakScope) need Jupiter equivalents.
What the checks validated
Not run.
No downstream impact: only Lucene's own test sources on main changed, and no release jar contains them.
Not formal. An AI tool ran Lucene's opt-in Hunspell conformance test against Hunspell's own test data; it failed with "No runnable methods". Git history led to #16028, and a scan of every class it touched found the classes outside LuceneTestCase. An independent verifier widened the set from three to six.
The method in full83 words
- Trigger: running
TestHunspellRepositoryTestCaseswithhunspell.repo.pathpointing at Hunspell 1.7.2's test data (hunt unit on Hunspell conformance) gaveInvalidTestClassError: No runnable methods. - Cause:
git logon the class led tobfa16d51c21(#16028). The finder scanned every class the commit touched for classes with neitherLuceneTestCaseancestry nor@Testand found two plain JUnit 4 classes. - Widening: an independent reproducer ran each touched class through the JUnit Platform launcher, as the build does, and found the three
RandomizedRunnerclasses that also lost their tests.
No prior report: scripted searches (GitHub, commits, downstream trackers, four mailing lists, JIRA archive), 67 GitHub queries and all 410 open PRs by file (2026-10-26), and a GitHub refresh on 2026-11-01. Upstream main still has the change.
Searches5 searches
Closest items, none this bug: #16028 itself (no review or comment); #16027 (JUnit 5 infrastructure; "Method prefix test* is not sufficient" is said only for the Jupiter base class); #15949 (vintage engine switch); #16121 / #16140 / #16149 ("no tests matched" under -Dtests.iters, a different, fixed defect). The only open PR touching one of the files, #13949, does not restore @Test. Limits: keyword search; private channels not searched; one 2026-11-01 listing was incomplete.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues and PRs (incl. migrated JIRA), commits, downstream trackers, dev@, issues@, java-user@, commits@, JIRA archive | 2026-10-26 | two scripted search runs (89 API queries): the six class names, the method names, 16028, "No runnable methods", "no tests matched", the method-provider names, "missing @Test", "hunspell.repo.path" | 175 and 198 distinct hits, 0 high; none this bug |
| GitHub (GraphQL) | 2026-10-26 | 67 queries: class names, symptoms, @Test wording, the mechanism, JUnit migration items since 2026-04-01 read in full, people, open items, downstream forks | no report |
| Open PRs | 2026-10-26 | the changed-file lists of all 410 open apache/lucene PRs | only #13949 touches one file (TestModularLayer's module list); it does not restore @Test |
| GitHub search refresh | 2026-11-01 | TestRuntimeDependenciesSane, TestCandidateMatcherVisibility, TestClassloadingDeadlock, TestModularLayer (since 2026-10-20), "No runnable methods", hunspell.repo.path, 16028 (since 2026-05-03), junit and "@Test" (since 2026-10-26) | only old or unrelated items (#571, #15864, #4875, #16953); #16937, a general tracking issue, does not mention it |
| Upstream main | 2026-11-01 | last commit and @Test count of the six files at f815e679bf6c | not fixed: 0 @Test in all six |
Introduced on main by #16028 (bfa16d51c21, 2026-05-02); at its parent all 18 methods are found. branch_11x, cut from main on 2026-10-28, inherited it. The plain classes are silent rather than loud because main had moved to the JUnit vintage engine (#15949). No release has it: 10.6.0, branch_10_6, branch_10_5 and every release tag keep the annotations in the files they have.
Versions tested8 rows
Introduced by #16028 "Misc junit4 test cleanups" (bfa16d51c21, committed 2026-05-02, main only, milestone 11.0.0; parent 41ebd23821a); branch_11x inherited it when it was cut from main on 2026-10-28. git blame at the parent attributes every removed line to the commit that created the class or method, from LUCENE-6482 (2015) to #13632 (2024). The zero-test symptom of the plain classes comes from main's earlier switch to the JUnit Platform vintage engine (5d7f8bd46fb, #15949, 2026-04-10), which is not itself a defect: the parent already used it and found every method. Commit-level runs compiled each commit's test sources against main's jars; the change between them is annotation-only.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | Gradle: the repro fails (assertions on and off); two classes "no tests matched", three green placeholders, Hunspell runner "No runnable methods"; with option A all 18 methods are found and 17 run and pass |
main ea57360912ed, a9779ef3c193 | affected | six-class discovery fails 6 of 6 (-ea, -da); the earlier repro fails 2 of 2; the intended-behaviour check's Gradle runs show the same symptoms |
bfa16d51c21 vs parent 41ebd23821a | introducing commit | parent: all 18 methods found (6 of 6 discovery checks pass); bfa16d51c21: none (6 of 6 fail) |
upstream main f815e679bf6c (2026-11-01) | read | 0 @Test lines in all six files; not fixed |
branch_11x 603af870375 (2026-10-29) | read | the 11.0 branch, cut from main on 2026-10-28 (c5ab3e57b9b): the six files byte-identical to 645d4fe14a20, 0 @Test; needs the backport |
| 10.6.0 | read | 3/4/1/8/1/1 @Test lines; the tag does not contain bfa16d51c21 |
| 9.12.3, 10.4.0, 10.5.0, 10.5.2 | not affected | each release's own test sources and jars: a discovery run finds all methods (classes absent in a release skipped) |
9.0.0 to 10.5.2 tags, branch_10_6, branch_10_5 | read | each of the six files that exists there has all its annotations (9.0.0 has only the Hunspell runner); branch_10x too until its decommissioning (#16911, 2026-10-29) |
Confirmed by an independent reproducer (who widened it to six classes), an intended-behaviour devil's advocate with a real Gradle build, an adjudication, reviews by OpenAI GPT-5.6 (one a procedural "not real": it could not run code), a credibility audit, this doc's Gradle validation of the fix, and fact checks.
Checks and verdicts399 words
No check disputed the root cause; the one "not real" verdict came from a reviewer that could not run code; a separate re-run of the repro saw it fail.
| Check | Verdict | What it found |
|---|---|---|
| finder | reproduced | Its two-class JUnitCore repro fails 2 of 2 with assertions on and off and passes with @Test restored. |
| cross-model review, refute lens (OpenAI GPT-5.6) | not real, low (procedural) | It could not run code; root cause agreed and the intended-behaviour argument rated weak; a separate run re-ran the repro: 2 of 2 fail. |
| Independent reproducer | real, widened to six classes | Its own JUnit Platform launcher run fails 6 of 6 classes (on and off); 10.5.2 and 9.12.3 pass; the annotation-only control passes 6 of 6. |
| Cross-model adversary, intended lens (OpenAI GPT-5.6) | real, medium | Intended-behaviour argument weak; a separate run re-ran the repro (2 of 2 fail) and the fix (2 of 2 pass). |
| Intended-behaviour devil's advocate | real, high | Real Gradle build: "no tests matched" for the plain classes, "No runnable methods" for the Hunspell runner; with the fix 3, 4 and 1 tests pass. |
| adjudication | confirmed, high | Console launcher on verbatim main sources: 0 / 0 tests, one class-named entry per RandomizedRunner class, initializationError for the Hunspell runner; annotation-only sources 3/4/1/8/1/1. |
| security screen | none | Test sources only; the restored deadlock guard passes 20 of 20 forked runs. |
| credibility audit | reproduces | Re-ran the six-class check and the control; noted that TestScripts is GUI-gated, so "always-on" is loose for it. |
| minimal repro | verified | One test; fails on main 645d4fe14a20 with assertions on and off; passes with the class's part of the fix. |
| doc validation (Gradle) | fix A validated | On 645d4fe14a20 the repro fails and four classes show the symptom; with fix A all six classes find their methods, 17 run and pass (the build skips the GUI test), and the closest suites pass. Closes two earlier gaps: RandomizedRunner classes in a real Gradle run, and restored TestModularLayer on the assembled distribution. |
| fact checks (2026-11-01) | real; corrections applied | Independent fact checks (one by OpenAI GPT-5.6) and a diagram check: bug and repro confirmed, a checker re-ran the Gradle repro and fix in its own scratch worktree. Corrected: the CI/GUI claim (the build skips the GUI test everywhere), branch_11x is affected, branch_10x is decommissioned, older-release runs show discovery, and wording details. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 458 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,080 words
- Registry title
- Test suite on main: #16028 removed @Test from six test classes that do not extend LuceneTestCase, so their 18 test methods never run (TestRuntimeDependenciesSane and TestCandidateMatcherVisibility report no tests; TestClassloadingDeadlock, TestModularLayer and TestScripts each report one green placeholder; the opt-in TestHunspellRepositoryTestCases fails with 'No runnable methods')
- Area
- test infrastructure / JUnit 4 test discovery (core, core.tests, monitor, distribution.tests, analysis-common hunspell)
- Priority
- Low, rank 1218: It affects only Lucene's own test suite on unreleased main (18 test methods silently do not run) and changes no shipped behaviour, but it loses regression coverage.
- Impact class
- minor
- Impact note
- Only Lucene's own test suite on unreleased main and branch_11x, since bfa16d51c21 (2026-05-02): every default main test run skips, without any error, the 16 test methods of four always-on classes (module-path sanity checks, the class-initialization deadlock guard, the distribution module-layer checks, monitor API visibility calls), no longer finds the Luke launch check (which the build skips on every run anyway), and the documented -Phunspell.repo.path conformance run fails with 'No runnable methods'; no shipped jar, API or index format changes and no release or 10.x branch is affected, so the cost is lost regression coverage, not wrong behaviour.
- Summary
- You run Lucene's test suite on main and expect TestRuntimeDependenciesSane, TestCandidateMatcherVisibility, TestClassloadingDeadlock, TestModularLayer and TestScripts to run their 17 test methods, and TestHunspellRepositoryTestCases to check Hunspell's test data when -Phunspell.repo.path is set. Instead the build passes without running any of those methods: #16028 removed their @Test annotations, which is harmless in LuceneTestCase subclasses (LuceneTestCase finds test* methods by name), but these six classes do not extend LuceneTestCase, so JUnit 4 finds only @Test methods and there are none. Two classes report zero tests, three report one green placeholder named after the class, and the Hunspell runner fails with 'No runnable methods'.
- Introduced
- bfa16d51c21b323e7ba5d9ca06f9150774ce4c7d, apache/lucene#16028 'Misc junit4 test cleanups' (by dweiss, merged to main 2026-05-02, milestone 11.0.0, 0 reviews, 0 comments; parent 41ebd23821a) [exec: the six classes' test sources at the parent discover all 18 methods (6 of 6 pass) and at bfa16d51c21 none (6 of 6 fail), both compiled against the main jars; the finder's JUnitCore repro passes at the parent and fails at bfa16d51c21] [read: the commit removes exactly 6 'import org.junit.Test;' and 18 '@Test' lines from these six classes, git blame at the parent attributes each removed line to the class's creation, and no later commit adds or removes an @Test line in them; 227255fdb08 (#16027, 2026-05-14) only edited a comment in TestClassloadingDeadlock]. The zero-test (rather than 'No runnable methods') symptom of the two plain JUnit 4 classes comes from main's earlier switch to the JUnit Platform vintage engine (5d7f8bd46fb, #15949, 2026-04-10, main only), which is not itself a defect. (2026-05-02)
- First affected version
- unreleased (main / 11.0.0-SNAPSHOT from bfa16d51c21 on; no release tag contains the commit, and releases 10.5.0-10.5.2, tagged later, come from branch_10_5, which never received it) [read]
- Affected versions
- main from bfa16d51c21 (2026-05-02) through 645d4fe14a20 [exec: six-class discovery fails 6 of 6 with -ea and -da at bfa16d51c21 and ea57360912ed; the finder's repro fails 2 of 2; the minimal repro fails through Gradle on 645d4fe14a20 with -ea and -da]; branch_11x (603af870375), cut from main on 2026-10-28, has the six files unchanged [read]; upstream main as of 2026-10-26 (head cf398bfb142c) still has no @Test in any of the six files [read]. Not affected: 9.12.3, 10.4.0, 10.5.0 and 10.5.2, each release's own test sources on its own jars [exec: all methods discovered; classes absent in a release skipped]; every release tag 9.0.0-10.5.2 and branch_10x, branch_10_6 and branch_10_5 have the annotations [read, by file content]. The defect travels with the test sources, not the jars (main's test sources on 10.5.2 jars fail 2 of 2) [exec].
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- non-formal
- Upstream and prior reports
- none found (2026-10-26: two scripted search runs (89 API queries over GitHub issues and PRs, commits, downstream repositories, the dev@, issues@, java-user@ and commits@ lists and the JIRA archive; 175 and 198 distinct hits, 0 high, 7 medium, all unrelated), 67 manual GitHub GraphQL queries, the changed-file lists of all 410 open apache/lucene PRs, list-archive and JIRA queries and 13 web queries; the independent reproduction's and the intended-behaviour check's own searches agree). Related items, none the same bug: #16028 (the change itself; no review or comment), #16027 (JUnit 5 infrastructure; says 'Method prefix test* is not sufficient' only for the new Jupiter base class), #15949 (vintage engine switch), #16121 / #16140 / #16149 ('no tests matched' under -Dtests.iters, a different defect). The only open PR touching one of the files, #13949, edits TestModularLayer's expected module list and does not restore @Test. Not fixed upstream (main head cf398bfb142c).
- Lucene commit tested
- main
645d4fe14a20with Lucene's Gradle build in a scratch worktree, stock and with the fix, assertions on and off, and standalone; earlier mainsea57360912edanda9779ef3c193; the parent41ebd23821aandbfa16d51c21(their test sources on main's jars); 9.12.3, 10.4.0, 10.5.0 and 10.5.2 (own test sources and jars). 10.6.0 and upstream mainf815e679bf6c(2026-11-01) were read, not run.
- Finding record
Lucene main ea57360912ed: lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java:114 (@TestMethodProviders with LuceneJUnit3MethodProvider); lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java:22-23; lucene/monitor/src/test/org/apache/lucene/monitor/outsidepackage/TestCandidateMatcherVisibility.java:29; lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/TestHunspellRepositoryTestCases.java:32-37, 76; lucene/core/src/test/org/apache/lucene/index/TestClassloadingDeadlock.java:51-59; lucene/distribution.tests/src/test/org/apache/lucene/distribution/AbstractLuceneDistributionTest.java:36-45; TestModularLayer.java:54; TestScripts.java:38-40; build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/TestsAndRandomizationPlugin.java:544-546, 568-569; lucene/analysis/common/build.gradle:244; lucene/MIGRATE.md:45-50
- The finding in 30 seconds
lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java:113-114lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneJUnit3MethodProvider.java:35-42lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java:22-41lucene/monitor/src/test/org/apache/lucene/monitor/outsidepackage/TestCandidateMatcherVisibility.java:29-60lucene/core/src/test/org/apache/lucene/index/TestClassloadingDeadlock.java:51-59lucene/distribution.tests/src/test/org/apache/lucene/distribution/AbstractLuceneDistributionTest.java:39-45lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java:54, 114-372lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestScripts.java:38-40lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/TestHunspellRepositoryTestCases.java:32-37, 55-76build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/TestsAndRandomizationPlugin.java:292-295, 545-546, 568-569build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/FailOnNoMatchingFilteredTestsPlugin.java:36-38, 98lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java:69-74
- Who is affected
lucene/core/src/test/org/apache/lucene/index/TestClassloadingDeadlock.java:79, 85, 90, 108-120.github/workflows/run-checks-mod-distribution.tests.yml:6-14, 48-49.github/workflows/run-checks-mod-analysis-common-hunspell.yml:49; run-checks-all.yml:148lucene/analysis/common/build.gradle:244a Lucene clone: origin/branch_11x 603af870375 (six files byte-identical to 645d4fe14a20, 0 @Test); merge-base with main c5ab3e57b9b (2026-10-28)
- Why it looks unintended
lucene/core.tests/src/test/org/apache/lucene/core/tests/TestRuntimeDependenciesSane.java:22lucene/core/src/test/org/apache/lucene/index/TestClassloadingDeadlock.java:51-52lucene/distribution.tests/src/test/org/apache/lucene/distribution/AbstractLuceneDistributionTest.java:39-42lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/TestHunspellRepositoryTestCases.java:32-34lucene/analysis/common/build.gradle:244lucene/MIGRATE.md:45-50
- Suggested fix
lucene/distribution.tests/src/test/org/apache/lucene/distribution/AbstractLuceneDistributionTest.java:39-42, 44lucene/core/src/test/org/apache/lucene/index/TestClassloadingDeadlock.java:20, 61
- History
a Lucene clone: origin/branch_11x 603af870375, merge-base c5ab3e57b9b; origin/branch_10x 48cf3f515d6 (#16911, README.md and .gitignore only); 657014b3c57 (last development commit, annotations present)
- TestCandidateMatcherVisibilityRuns.java (JUnit 4, one test):
repro/LFV-389/src/org/apache/lucene/monitor/outsidepackage/TestCandidateMatcherVisibilityRuns.java(35 lines)
Made in parallel with this doc from the mechanism in the brief; the PNG is the summary frame.
assets/LFV-389/bug.png(458 KB)
