All findings · Lucene finding · LFV-1013 · test-methods-misnamed-never-run

Four Lucene test methods never run: their names start with "tes" or "Test" instead of "test"

Four methods in Lucene's own tests are spelled tes... or Test... instead of test..., so the runner silently skips them (the TestXYRectangle pair in every release since 8.5.0). Two would fail if run: they expect 5 > 4, but XYRectangle says 5.0 > 4.0, so its reversed-bounds check has no running test.

Summary

Priority
Low, rank 1219It only affects Lucene's own test suite, where 4 misnamed test methods (in practice one untested XYRectangle reversed-bounds check) silently never run.
Impact
minor
Since
8.5.0 (the TestXYRectangle pair); 4.0.0-alpha (oldest method)introduced 2020-02-14 · about 6 years
Affected
main, 10.6.0 and every release since 8.5.0 (Lucene's test sources only)
Status
documented
Found by
code reading and a source scan (non-formal)

AI-assisted page: this finding was first signalled by code reading or testing and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

Lucene's test runner decides what is a test from the method name alone.

Diagram of the bug. Lucene's test runner keeps only public no-arg methods whose names start with lowercase "test" (or that carry @Test). Of TestXYRectangle's 7 test-shaped methods it runs 5: tesInvalidMinMaxX and tesInvalidMinMaxY are skipped, as are TestOperations.TestOptionalOptionalAcceptsState0 and TestCommonGramsFilter.TestFirstAndLastStopWord. Renamed, the two XYRectangle methods fail: they look for "5 > 4" while new XYRectangle(5, 4, 3, 4) reports "minX must be lower than maxX, got 5.0 > 4.0". Expected: 7, 24 and 11 tests run; got 5, 23 and 10.
How the runner picks test methods by name, which four it skips, and why the two XYRectangle checks would fail if they ran. Select the still to open it at full size. Generated with AI, checked against the code.
  1. TestXYRectangle declares 7 public no-arg test methods, all with assertions. Two are spelled tesInvalidMinMaxX and tesInvalidMinMaxY: one t short.
  2. Run ./gradlew :lucene:core:test --tests org.apache.lucene.geo.TestXYRectangle. You expect 7 tests; gradle runs 5. Asking for TestXYRectangle.tesInvalidMinMaxX by name ends with "no tests matched the provided --tests filters".
  3. Why: Lucene's runner takes only public no-arg methods whose names start with lowercase test, or that carry @Test. Two more methods miss it: TestOperations.TestOptionalOptionalAcceptsState0 (23 of 24 run) and TestCommonGramsFilter.TestFirstAndLastStopWord (10 of 11).
  4. Rename the two XYRectangle methods and both fail: they check the message for 5 > 4, but new XYRectangle(5, 4, 3, 4) reports minX must be lower than maxX, got 5.0 > 4.0 because the bounds are floats. The library is right; the checks were stale from the day they were written (2020).
  5. The cost: no running test builds an XYRectangle with reversed bounds, so a regression in that check would go unnoticed. The other two dead methods repeat checks that running tests already make.

Who is affected

Only Lucene's own test suite: normal test runs, local or CI, skip the four methods: the TestXYRectangle pair in every release since 8.5.0, TestOptionalOptionalAcceptsState0 since 10.2.0 and TestFirstAndLastStopWord since 4.0.0-alpha. No shipped jar, API or index is affected. The real loss is coverage of XYRectangle's reversed-bounds IllegalArgumentException, which no running test checks.

Mechanism, terms and numbers462 words

Lucene's test runner treats a method as a test only if it is public, non-static, takes no arguments and its name starts with lowercase test, or if it carries @Test. Four methods in Lucene's own test sources miss that by one letter: TestXYRectangle.tesInvalidMinMaxX and tesInvalidMinMaxY, TestOperations.TestOptionalOptionalAcceptsState0 and TestCommonGramsFilter.TestFirstAndLastStopWord. Every test run skips them silently: gradle runs 5, 23 and 10 tests for the three classes instead of 7, 24 and 11. Renamed, the two TestXYRectangle methods fail, because they look for 5 > 4 in a message that reads minX must be lower than maxX, got 5.0 > 4.0. No shipped code is wrong; the real loss is that XYRectangle's reversed-bounds IllegalArgumentException has had no running test since it was added in 2020.

How Lucene finds tests

LuceneTestCase registers two method providers, @TestMethodProviders({LuceneJUnit3MethodProvider.class, JUnit4MethodProvider.class}) (LuceneTestCase.java:114). The first keeps a method only if m.getName().startsWith("test"), it is public and non-static, and it takes no parameters (LuceneJUnit3MethodProvider.java:39-42); the second keeps @Test methods. The class javadoc says the same: "Any test method with a testXXX prefix is considered a test case. Any test method annotated with [org.junit.Test] is considered a test case." (LuceneTestCase.java:90-91). Anything else is silently ignored.

The four methods (main 645d4fe14a20)

methodwherewhat it checks
tesInvalidMinMaxXTestXYRectangle.java:26new XYRectangle(5, 4, 3, 4) throws IllegalArgumentException whose message contains 5 > 4 (:33)
tesInvalidMinMaxYTestXYRectangle.java:37new XYRectangle(4, 5, 5, 4) throws, message contains 5 > 4 (:44)
TestOptionalOptionalAcceptsState0TestOperations.java:574optional(optional(a)) for a(ba)* is a minimal DFA with the same language
TestFirstAndLastStopWordTestCommonGramsFilter.java:276"the of" through CommonGramsQueryFilter gives ["the_of"]

None has @Test, @Ignore, @AwaitsFix, @Nightly or a comment saying it is disabled, and no code calls them.

Why the two XYRectangle checks are stale

XYRectangle(float minX, float maxX, float minY, float maxY) throws new IllegalArgumentException("minX must be lower than maxX, got " + minX + " > " + maxX) (and the minY twin) from a plain if (XYRectangle.java:36-44). Java prints the float 5 as 5.0, so the message is got 5.0 > 4.0 and contains("5 > 4") is false. LUCENE-9218 made the bounds floats in the same commit that added the exception and these two tests, so the checks never matched in any version.

Why nothing catches it

Lucene's build turns off error-prone's JUnit3TestNotRun ("we don't use junit3") and JUnit4TestNotRun ("noisy") (ErrorPronePlugin.java:542, :548); per error-prone's documentation the former looks only at junit.framework.TestCase subclasses anyway, and whether the latter would flag these methods under Lucene's RandomizedRunner is unknown (neither was tested). The JUnit 5 base class checks test* methods for @Test (LuceneTestCaseJupiter.java:608-634), but only for Jupiter tests and only for names that already start with test.

Exposure and likelihood236 words
  • Who. Lucene developers and CI. No user configuration or API is involved, and test sources are not in the released jars.
  • What is lost. The two TestXYRectangle methods are the only tests that build an XYRectangle with reversed bounds; every other new XYRectangle(...) in the test trees passes ordered bounds. If that check regressed (for example back to an assert, as it was before 8.5.0), no running test would notice.
  • What is not lost. TestFirstAndLastStopWord ("the of" gives ["the_of"]) repeats the case that the running testCommonGramsQueryFilter already checks with two common words in the other order ("of the" gives ["of_the"], TestCommonGramsFilter.java:105). TestOptionalOptionalAcceptsState0 adds nothing beyond the running testOptionalAcceptsState0 and testOptionalOptional. Both dead bodies pass when run under new names, so they hide no product bug.
  • Scale. Two name scans find exactly these four: the finder's over every lucene/**/src/test tree, sandbox included (public no-arg void methods whose names start with tes, tset, tets, tst, Test, TEST or Tset but not with test), and an independent verifier's over the non-sandbox trees (Test[A-Z]*, tes[^t]*, tset*, tets*, tst*, est[A-Z]*). They look for near-miss spellings only. Public no-arg methods that never run on purpose do exist, for example the compile-only TestAnalyzers._testStandardConstants (LUCENE-1150 comment, @SuppressWarnings("unused")).
  • Library behaviour. Correct: XYRectangle rejects reversed bounds with assertions on and off; Operations.optional and CommonGramsQueryFilter work.

Minimal repro

One JUnit class with one test. It asks JUnit for the runner of each of the three unmodified classes, reads which tests that runner would run, and lists every public no-arg instance method that is missing. Nothing in the three classes is executed, and nothing is random.

TestMisnamedTestMethodsNeverRun.java (one test, test sources only)53 lines
/*
 * 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;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.commongrams.TestCommonGramsFilter;
import org.apache.lucene.geo.TestXYRectangle;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.util.automaton.TestOperations;
import org.junit.runner.Description;
import org.junit.runner.Request;

public class TestMisnamedTestMethodsNeverRun extends LuceneTestCase {

  public void testEveryPublicNoArgMethodIsRunAsATest() {
    List<String> notRun = new ArrayList<>();
    for (Class<?> c :
        List.of(TestXYRectangle.class, TestOperations.class, TestCommonGramsFilter.class)) {
      List<String> run = new ArrayList<>();
      for (Description d : Request.aClass(c).getRunner().getDescription().getChildren()) {
        run.add(d.isSuite() ? d.getDisplayName() : d.getMethodName());
      }
      for (Method m : c.getDeclaredMethods()) {
        if (Modifier.isPublic(m.getModifiers())
            && Modifier.isStatic(m.getModifiers()) == false
            && m.getParameterCount() == 0
            && run.contains(m.getName()) == false) {
          notRun.add(c.getSimpleName() + "#" + m.getName());
        }
      }
    }
    notRun.sort(null);
    // expected: every public no-arg test method runs; actual: the four misnamed ones are skipped
    assertEquals(List.of(), notRun);
  }
}
Command
# $LUCENE: a built checkout; $REPRO: the directory holding the test; $DEPS: junit 4.13.2, randomizedtesting-runner 2.9.2, hamcrest 3.0
CP=$LUCENE/lucene/core/build/libs/lucene-core-11.0.0-SNAPSHOT.jar:$LUCENE/lucene/codecs/build/libs/lucene-codecs-11.0.0-SNAPSHOT.jar:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-11.0.0-SNAPSHOT.jar:$LUCENE/lucene/analysis/common/build/libs/lucene-analysis-common-11.0.0-SNAPSHOT.jar:$DEPS
javac -nowarn -d out -cp "$CP" $REPRO/org/apache/lucene/TestMisnamedTestMethodsNeverRun.java \
  $LUCENE/lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java \
  $LUCENE/lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java \
  $LUCENE/lucene/analysis/common/src/test/org/apache/lucene/analysis/commongrams/TestCommonGramsFilter.java
java -ea -Dtests.seed=DEADBEEF -cp "out:$CP" org.junit.runner.JUnitCore org.apache.lucene.TestMisnamedTestMethodsNeverRun
# assertions off, same result: java -da -Dtests.asserts=false ...
Expected
An empty list: every public no-arg method of the three classes is run as a test.
Observed
[TestCommonGramsFilter#TestFirstAndLastStopWord, TestOperations#TestOptionalOptionalAcceptsState0, TestXYRectangle#tesInvalidMinMaxX, TestXYRectangle#tesInvalidMinMaxY], with assertions on and off, on main and on 10.6.0.

All files of this repro: repro/LFV-1013/

More files, environment, full output359 words

The test compiles the checkout's own, unmodified TestXYRectangle, TestOperations and TestCommonGramsFilter and calls Request.aClass(c).getRunner().getDescription() for each: that builds Lucene's RandomizedRunner (from LuceneTestCase's @RunWith) and lists the tests it would run, without running them. Every declared public, non-static, no-arg method that is not in that list is reported. Apart from their test methods the three classes declare no public no-arg instance methods, so the list should be empty (true for these three classes; it is not a general rule). d.isSuite() covers -Dtests.iters > 1, where the runner groups each method's repetitions under a suite named after the method (checked with 5 iterations).

The test classes of two modules are compiled together, so the command runs outside gradle, against the checkout's built jars of lucene-core, -codecs, -test-framework and -analysis-common plus junit 4.13.2, randomizedtesting-runner 2.9.2 and hamcrest 3.0 ($DEPS, the versions in gradle/libs.versions.toml).

Without the test: on a stock checkout, ./gradlew :lucene:core:test --tests org.apache.lucene.geo.TestXYRectangle reports 5 tests for a class with 7 test-shaped methods, and --tests 'org.apache.lucene.geo.TestXYRectangle.tesInvalidMinMaxX' fails the build with "1 test task executed but no tests matched the provided --tests filters?".

No separate regression test is suggested: once renamed, the four methods are running tests themselves. The minimal test passes with the fix.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); release 10.6.0 fails the same way with its own test sources
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (the only platform run)

Observed output

# the command above, main 645d4fe14a20, seed DEADBEEF, -ea (-da identical)
java.lang.AssertionError: expected:<[]> but was:<[TestCommonGramsFilter#TestFirstAndLastStopWord, TestOperations#TestOptionalOptionalAcceptsState0, TestXYRectangle#tesInvalidMinMaxX, TestXYRectangle#tesInvalidMinMaxY]>
FAILURES!!!
Tests run: 1,  Failures: 1

# gradle, stock checkout
<testsuite name="org.apache.lucene.geo.TestXYRectangle" tests="5" skipped="0" failures="0" errors="0"
<testsuite name="org.apache.lucene.util.automaton.TestOperations" tests="23" skipped="0" failures="0" errors="0"
<testsuite name="org.apache.lucene.analysis.commongrams.TestCommonGramsFilter" tests="10" skipped="0" failures="0" errors="0"
$ ./gradlew :lucene:core:test --tests 'org.apache.lucene.geo.TestXYRectangle.tesInvalidMinMaxX'
> 1 test task executed but no tests matched the provided --tests filters?

# gradle, tes -> test only (message checks unchanged)
TestXYRectangle > testInvalidMinMaxX FAILED
    java.lang.AssertionError
        at org.apache.lucene.geo.TestXYRectangle.testInvalidMinMaxX(TestXYRectangle.java:33)
TestXYRectangle > testInvalidMinMaxY FAILED
    java.lang.AssertionError
        at org.apache.lucene.geo.TestXYRectangle.testInvalidMinMaxY(TestXYRectangle.java:44)
7 tests completed, 2 failed
# the messages those checks look at: minX must be lower than maxX, got 5.0 > 4.0 / minY must be lower than maxY, got 5.0 > 4.0

Why it looks unintended

  • Written as tests: javadoc such as "maxX must be gte minX", assertion-only bodies, no callers, no @Ignore or @AwaitsFix.
  • Misnamed at creation, next to correctly named siblings: #14218 split a running testOptional into four methods and named one TestOptional....
  • Lucene's JUnit 5 base class checks test* methods "so that they're not silently ignored".
The full argument216 words
  • Written as tests. tesInvalidMinMaxX / Y carry the javadoc "maxX must be gte minX" / "maxY must be gte minY"; TestFirstAndLastStopWord carries "Test CommonGramsQueryFilter when first and last words are stopwords."; all four bodies are assertions only (expectThrows, assertMinimalDFA, assertTokenStreamContents), and no code calls them. Lucene's test code disables tests on purpose with 49 active @AwaitsFix / @Ignore annotations; none of the four has a marker.
  • History shows typos. LUCENE-9218 (4a54ffb553ad, apache/lucene-solr#1252, 2020) created TestXYRectangle with the two tes methods next to four correctly named siblings (testNaN, testPositiveInf, testNegativeInf, testEqualsAndHashCode). #14218 (de1ed71261d5, 2025, "Clean up more dead states creation by Automata/Operations"; its commit message lists "split some large tests into smaller tests for easier management") split the running testOptional into four methods; three got test, one got Test. TestFirstAndLastStopWord came with SOLR-1674 (2009) and kept its name through the move into Lucene (LUCENE-2413, 2010) and the 2021 class rename (LUCENE-8626).
  • Precedent. LUCENE-2037 (db067550e89, 2010) added a check because "i found lots of tests that simply forgot to add @Test and were never ran"; a 2010 commit (d1ecb22f8a9) renamed tesStopwordsParsing to testStopwordsParsing; LuceneTestCaseJupiter enforces @Test on test* methods "so that they're not silently ignored" (LuceneTestCaseJupiter.java:610).
The devil's-advocate check154 words

Strongest case for "not a bug": the runner and XYRectangle work exactly as documented; LuceneTestCase never promises to run every public no-arg method, and a public method may be a helper; the exact exception text is not a contract; two of the four methods are redundant. The first cross-model reviewer (refute lens) returned "not real" on these grounds, at medium confidence and partly on procedure (it could not run Java).

Why it fails: nobody claims the runner is wrong; the defect is in the test sources. The same reviewer wrote that "static review nevertheless strongly indicates accidental test-name typos" and that the renames and message fixes "look appropriate". A helper would not carry test javadoc and assertion-only bodies, and nothing marks the methods as disabled. The exact text matters only because the tests' own assertion checks it. Redundancy narrows the impact but does not make the names intended. Verdict: a minor, test-only defect (adjudication).

Suggested fix

Recommended Rename the four methods; correct the two expected messages (validated)

Rename the four methods to test... and change both TestXYRectangle checks to contains("5.0 > 4.0"): 6 lines in 3 test files. With it the classes run 7, 24 and 11 tests and pass, the geo, automaton and commongrams suites pass (453 tests), and the minimal test passes. Renaming alone leaves 2 failures.

--- TestXYRectangle.java
-  public void tesInvalidMinMaxX() {
+  public void testInvalidMinMaxX() {
-    assertTrue(expected.getMessage().contains("5 > 4"));
+    assertTrue(expected.getMessage().contains("5.0 > 4.0"));
   (the same two changes in tesInvalidMinMaxY)
--- TestOperations.java
-  public void TestOptionalOptionalAcceptsState0() {
+  public void testOptionalOptionalAcceptsState0() {
--- TestCommonGramsFilter.java
-  public void TestFirstAndLastStopWord() throws Exception {
+  public void testFirstAndLastStopWord() throws Exception {
All options and trade-offs4 options ยท 411 words

One fix was validated with gradle in a scratch worktree of 645d4fe14a20; the alternatives were not run.

Rename the four methods; correct the two expected messages (validated) (recommended)

Rename tesInvalidMinMaxX, tesInvalidMinMaxY, TestOptionalOptionalAcceptsState0 and TestFirstAndLastStopWord to testInvalidMinMaxX, testInvalidMinMaxY, testOptionalOptionalAcceptsState0 and testFirstAndLastStopWord, and in TestXYRectangle change both contains("5 > 4") to contains("5.0 > 4.0"). 3 files, +6/-6, test sources only.

--- TestXYRectangle.java
-  public void tesInvalidMinMaxX() {
+  public void testInvalidMinMaxX() {
-    assertTrue(expected.getMessage().contains("5 > 4"));
+    assertTrue(expected.getMessage().contains("5.0 > 4.0"));
   (the same two changes in tesInvalidMinMaxY)
--- TestOperations.java
-  public void TestOptionalOptionalAcceptsState0() {
+  public void testOptionalOptionalAcceptsState0() {
--- TestCommonGramsFilter.java
-  public void TestFirstAndLastStopWord() throws Exception {
+  public void testFirstAndLastStopWord() throws Exception {

Trade-offs

None for users: no library code changes. Both parts are needed: renaming without the message change makes TestXYRectangle fail 2 of 7. No CHANGES entry is strictly needed.

What the checks validated

  • gradle --no-daemon, seed DEADBEEF, OpenJDK 25.0.3: TestXYRectangle, TestOperations and TestCommonGramsFilter run 7, 24 and 11 tests (stock 5, 23, 10), 0 failures, with -Ptests.asserts=true and false.
  • Closest suites, 0 failures: org.apache.lucene.geo.* (17 classes, 195 tests, 7 skipped), org.apache.lucene.util.automaton.* (15 classes, 239 tests, 2 skipped), org.apache.lucene.analysis.commongrams.* (3 classes, 19 tests).
  • The minimal test passes against the fixed sources with assertions on and off.
  • Renaming only: TestXYRectangle 7 tests, 2 failures (TestXYRectangle.java:33, :44).

Rename and assert the whole message (not run)

assertEquals("minX must be lower than maxX, got 5.0 > 4.0", expected.getMessage()) and the minY twin.

Trade-offs

Stricter, but ties the tests to the full wording; the recommended option keeps the original substring-style check and is the smaller diff.

Delete the two redundant methods instead of renaming them (not run)

TestFirstAndLastStopWord and TestOptionalOptionalAcceptsState0 add no coverage that running tests do not already give.

Trade-offs

Defensible, but renaming is a smaller, less debatable change and keeps the explicit cases their authors wrote. The TestXYRectangle pair must be renamed either way.

Follow-up: a guard against recurrence (not run)

A check in LuceneTestCase, like the Jupiter guard (LuceneTestCaseJupiter.java:608-634), that fails on public no-arg instance methods named tes... or Test... but not test....

Trade-offs

A heuristic: a legitimate public helper with such a name would trip it (the near-miss scans found none besides these four). Turning on error-prone's JUnit3TestNotRun would likely not help, since it looks only at junit.framework.TestCase subclasses; whether JUnit4TestNotRun would flag these methods in classes run by Lucene's RandomizedRunner was not tested.

How it was found

Code reading, no formal method. While an AI tool was checking XYRectangle's input validation in a sweep of assert-only input checks, it noticed the tes prefix; two independent name scans of the test trees found the same four. Runs of the unmodified and the renamed classes confirmed it.

The method in full163 words
  • Context. A hunt unit swept Lucene for input checks that exist only as assert. Reading TestXYRectangle to see how XYRectangle's bounds checks are tested, the finder noticed that tesInvalidMinMaxX and tesInvalidMinMaxY cannot run.
  • Widening. The finder's scan of every lucene/**/src/test tree, sandbox included, for public no-arg void methods whose names start with tes, tset, tets, tst, Test, TEST or Tset but not with test found exactly four. An independent verifier's scan of the non-sandbox test trees with its own patterns (Test[A-Z]*, tes[^t]*, tset*, tets*, tst*, est[A-Z]*) found the same four. Both match name patterns only.
  • Confirmation. The unmodified classes ran 5, 23 and 10 tests with none of the four; renamed copies ran 42 tests with only the two XYRectangle checks failing; the corrected checks passed.
  • Tier. Non-formal: the first signal was reading a test and the widening was a text scan; no model or bounded check was involved.

Prior reports, history and checks

Prior reports

No prior report: GitHub issues and PRs (with the migrated JIRA archive), commits, the dev@, java-user@, issues@ and commits@ lists and downstream repositories were searched on 2026-10-29; each method name returns 0 GitHub results (re-checked 2026-11-01).

Searches5 searches

Related, none the same bug: #16027 (the JUnit 5 base class needs @Test), #9672 and #11374 (test class naming and discovery), #3112 (LUCENE-2037, the 2010 @Test check), and LFV-389, tests that lost @Test (a different root). The introducing PRs (apache/lucene-solr#1252, #14218) have no comment on these tests.

One silent signal that is not a report: an outside Kotlin port (nehemiaharchives/lucene-kmp#97, 2025-06-13) runs the two XYRectangle methods and expects 5.0 > 4.0 without comment; nothing reached apache/lucene.

What was searched, when, and what turned up
WhereWhenHowFound
Scripted searches, 3 runs2026-10-2946 GitHub issue/PR, 9 commit, 4 downstream, 60 mailing-list (dev@, java-user@, issues@, commits@), 12 JIRA-archive and 6 web queries0 high-overlap hits; 2 medium hits share only the title term testOperations
GitHub, manual (apache/lucene)2026-10-2938 phrasings ("was never running", "test method" typo, "start with test", "dead test", "public void tes", ...)nothing about these methods or a misnamed-test sweep
GitHub-wide issues and commits2026-10-29; re-checked 2026-11-01each of the four method names; "misnamed test"0
Open PRs2026-10-29changed-file lists of all 415 open apache/lucene PRsonly #16911 (deletes every file on branch_10x) touches one of the files
Upstream main2026-11-01the three files at f815e679bf6call four names and both "5 > 4" checks unchanged

History

Each method was misnamed in the commit that created it: the TestXYRectangle pair in LUCENE-9218 (4a54ffb553ad, 2020, first released in 8.5.0), the TestOperations one in #14218 (de1ed71261d5, 2025, 10.2.0), TestFirstAndLastStopWord in a 2009 Solr test moved into Lucene in 2010 (4.0.0-alpha). All four are on main and in 10.6.0.

Versions tested6 rows
  • TestXYRectangle pair: 4a54ffb553ad (LUCENE-9218, apache/lucene-solr#1252, by iverase, 2020-02-14; branch_8x backport ca3319cdbce1). The same commit made the bounds floats and replaced XYRectangle's assert minX <= maxX with the IllegalArgumentException, so the "5 > 4" checks never matched. Only reformatting touched the bodies later.
  • TestOperations method: de1ed71261d5 (#14218, by rmuir, 2025-02-13; branch_10x backport b74580c51f0c) split the running testOptional into four methods, one misnamed.
  • TestFirstAndLastStopWord: b105beef661a (SOLR-1674, 2009-12-21) in Solr's CommonGramsFilterTest; moved into Lucene by 1d1331fa033f (LUCENE-2413, 2010-05-04); the 2021 class rename (LUCENE-8626) kept the method name.
  • None of the four has run in any Lucene release. 8.8.2 to 8.11.3 are inferred (same branch, between affected 8.8.1 and 8.11.4).
Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20; upstream main f815e679bf6c affectedall four skipped: minimal test fails with -ea and -da; gradle 5/23/10 tests (upstream main read, not run)
10.6.0 affectedminimal test fails on the release jars with the tag's own test sources
10.2.0 to 10.5.2 affectedall four present; runner skips them on 10.2.0 and 10.5.2; stale checks fail on 10.5.2
8.5.0 to 10.1.0 3 of 4the TestXYRectangle pair and TestFirstAndLastStopWord; runner skips them on 8.5.0 and 9.12.3; stale checks fail on 8.5.0, 8.11.4, 9.12.3; on 10.1.0 a related state-0 check runs inside testOptional
4.0.0-alpha to 8.4.1 1 of 4only the redundant TestFirstAndLastStopWord (skipped on 4.0.0); 8.4.1 has no XYRectangle exception to test
3.x and older not affectedthe CommonGrams test is only in Solr's tests

Independent checks

Independent reproducers, a reviewer from another model family (OpenAI GPT-5.6) with lenses, a devil's-advocate check and an adjudication looked at it. All agree on the cause; the refute lens said "not real" on procedure but called the names accidental typos. Adjudication: confirmed, high confidence, minor and test-only.

Checks and verdicts238 words

Each check ran separately on main 645d4fe14a20, except the cross-model reviews: they could not run code and read an earlier checkout, ea57360912ed, whose copies of the relevant files are identical (a separate run re-ran the repro).

Each independent check and its verdict
CheckVerdictWhat it found
Finder reproducedUnmodified classes run 5/23/10 tests; renamed copies 42 tests, only the two XYRectangle checks fail; corrected checks pass.
Refute lens (OpenAI GPT-5.6) not real (medium), proceduralRunner and XYRectangle work as documented; it could not run Java; agreed on the root cause and called the methods accidental typos; a separate run re-ran the repro: 2 of 2 fail.
Reproducer realOwn discovery check: the four never run on main (-ea, -da) and 10.5.2, three on 9.12.3; own stale-check repro with passing controls; the other two bodies pass; own scan finds exactly four.
Works-as-designed lens (OpenAI GPT-5.6) real (medium)Intended-behaviour argument "moderate"; root cause agreed.
Devil's advocate real, narrow scopeTypos, not intent; two of the four are redundant; re-run 2 of 2 fail with -ea and -da.
Adjudication confirmed (high), minor, test-onlyRe-ran 2 of 2 fail; corrections: only the two XYRectangle checks are lost coverage, and the never-run proof is runner discovery.
Minimal repro and brief verified; fix validatedOne-test class fails on main (-ea, -da) and 10.6.0, passes with the fix; gradle confirms 5/23/10 stock and 7/24/11 fixed.

Diagram file

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.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,185 words

Finding record

LFV-1013, snapshot of 2026-11-06

Registry title
Four Lucene test methods never run because their names do not start with lowercase 'test' (TestXYRectangle.tesInvalidMinMaxX/Y, TestOperations.TestOptionalOptionalAcceptsState0, TestCommonGramsFilter.TestFirstAndLastStopWord); the two TestXYRectangle ones would also fail if run, because they look for '5 > 4' while XYRectangle reports '5.0 > 4.0', so XYRectangle's reversed-bounds check has had no running test since 2020
Area
tests / test-method discovery (core geo TestXYRectangle, core automaton TestOperations, analysis-common TestCommonGramsFilter)
Priority
Low, rank 1219: It only affects Lucene's own test suite, where 4 misnamed test methods (in practice one untested XYRectangle reversed-bounds check) silently never run.
Impact class
minor
Impact note
Only Lucene's own test sources, in every release since 8.5.0 (the oldest of the four methods since 4.0.0-alpha): every test run silently skips the four methods; no shipped jar, API or index behaviour is wrong, two of the four duplicate running tests, and the real loss is that XYRectangle's min > max IllegalArgumentException has no running test, so a future regression there would go unnoticed.
Summary
You run Lucene's test suite and expect every method written as a test to run. Four are named 'tes...' or 'Test...' instead of 'test...', so Lucene's runner, which picks only public no-arg methods whose names start with lowercase 'test' (or that carry @Test), silently skips TestXYRectangle.tesInvalidMinMaxX/Y, TestOperations.TestOptionalOptionalAcceptsState0 and TestCommonGramsFilter.TestFirstAndLastStopWord. Run under 'test...' names, the two TestXYRectangle methods fail, because they look for '5 > 4' while XYRectangle correctly reports 'minX must be lower than maxX, got 5.0 > 4.0'; the library itself behaves correctly.
Introduced
4a54ffb553ad (apache/lucene-solr#1252, LUCENE-9218, by iverase; branch_8x backport ca3319cdbce1) for TestXYRectangle.tesInvalidMinMaxX/Y, created misnamed and with the '5 > 4' checks in the same commit that added XYRectangle's float bounds and its min > max IllegalArgumentException [read]; de1ed71261d5 (apache/lucene#14218, by rmuir; branch_10x backport b74580c51f0c) for TestOperations.TestOptionalOptionalAcceptsState0, split out of the running testOptional [read]; b105beef661a (SOLR-1674, in the Solr test), moved into the Lucene tree by 1d1331fa033f (LUCENE-2413) for TestCommonGramsFilter.TestFirstAndLastStopWord [read]. Each method was created with its final name; none was renamed into this state later [read, history clone] (2020-02-14 (TestXYRectangle pair); 2025-02-13 (TestOperations method); 2009-12-21 in the Solr test, 2010-05-04 in the Lucene tree (TestCommonGramsFilter method))
First affected version
8.5.0 for the TestXYRectangle pair, the only real coverage loss [exec: 8.5.0 stock classes do not run them, the checks fail 2 of 2]; 10.2.0 for the TestOperations method [exec discovery]; 4.0.0-alpha for TestFirstAndLastStopWord, the oldest [read; exec discovery on 4.0.0]
Affected versions
main 645d4fe14a20 [exec: the unmodified TestXYRectangle, TestOperations and TestCommonGramsFilter run 5, 23 and 10 tests and none of the four methods; the two TestXYRectangle checks fail 2 of 2 with -ea and -da]; 10.6.0 (4ae946e5a9b) [exec: the minimal test fails on the release jars with the tag's own test sources, -ea and -da; main() form of the two checks on its core jar, both messages 'got 5.0 > 4.0']; branch_10x 657014b3c579 [read]; 10.5.2 [exec: four not run, checks fail]; 9.12.3 [exec: the three methods that exist there not run, checks fail]; 8.11.4 and 8.5.0 [exec: checks fail; 8.5.0 discovery: three not run]; 10.2.0 [exec: TestOperations method not run]; 4.0.0 [exec: TestFirstAndLastStopWord not run]; every release tag from 8.5.0 to 10.5.2 has the TestXYRectangle pair and every one from 4.0.0-alpha has TestFirstAndLastStopWord [read]. Not affected: 8.4.1 for the TestXYRectangle pair (no such test and no IllegalArgumentException; XYRectangle only asserted) [exec]; 10.1.0 for the TestOperations method (absent; testOptional held an earlier related check and ran) [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 disputed it (the adjudication ruled it real), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
non-formal: code reading
Upstream and prior reports
none found (2026-10-29: three scripted search runs over GitHub issues and PRs incl. the migrated JIRA archive, commits, downstream repositories, the dev@, java-user@, issues@ and commits@ lists and JIRA, 60 manual GitHub searches, 6 web queries and the changed-file lists of 415 open apache/lucene PRs; GitHub-wide issue and commit search for each of the four method names: 0). Related items, none the same bug: apache/lucene-solr#1252 and apache/lucene#14218 (the introducing PRs, no comment on the tests), #16027 (JUnit 5 base class needs @Test), #9672 and #11374 (test class naming and discovery), LFV-389 (a different never-run-tests root). The only open PR touching one of the files, #16911, deletes branch_10x. Follow-up re-check 2026-10-29: the four names still return 0 issues/PRs, no issue or PR created since 2026-10-26 matches 'TestXYRectangle', 'misnamed test', 'test method name typo' or 'never run tests', no open PR created since 2026-10-27 touches the three files, and upstream main HEAD is still 645d4fe14a20 (author date 2026-10-28). Not fixed upstream. Update 2026-11-02: #16911 (open when searched) merged 2026-10-29 as 48cf3f515d6, which emptied branch_10x to a README tombstone (10.6 is the last 10.x minor; 10.x fixes go to branch_10_6); it fixes nothing here.
Lucene commit tested
main 645d4fe14a20 (2026-10-28): the minimal test fails with assertions on and off (standalone and with a plain javac/java command); gradle in a scratch worktree runs 5/23/10 tests stock and 7/24/11 with the fix; the 10.6.0 release jars with that tag's own test sources fail the same way; older releases are listed under history. Upstream main f815e679bf6c (2026-11-01) was read, not run: all four names are unchanged.

Lucene and upstream sources

Finding record
  • a Lucene main checkout (645d4fe14a20): lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java:25-45 (tesInvalidMinMaxX at :26, tesInvalidMinMaxY at :37, contains("5 > 4") at :33 and :44); lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java:36-44 (float constructor, unconditional IllegalArgumentException with the bounds concatenated as floats); lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java:574; lucene/analysis/common/src/test/org/apache/lucene/analysis/commongrams/TestCommonGramsFilter.java:275-276; lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneJUnit3MethodProvider.java:39 (m.getName().startsWith("test")); lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java:88-91 ('Any test method with a testXXX prefix is considered a test case'); build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ErrorPronePlugin.java:542, :548 (JUnit3TestNotRun and JUnit4TestNotRun off)
The finding in 30 seconds
  • a Lucene main checkout (645d4fe14a20) lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java:90-91, :114
  • a Lucene main checkout (645d4fe14a20) lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneJUnit3MethodProvider.java:39-42
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java:25-45; lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java:574-588; lucene/analysis/common/src/test/org/apache/lucene/analysis/commongrams/TestCommonGramsFilter.java:275-283
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java:36-44
  • a Lucene main checkout (645d4fe14a20) build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/java/ErrorPronePlugin.java:542, :548
  • a Lucene main checkout (645d4fe14a20) lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java:608-634
Who is affected
  • a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/test/org/apache/lucene/analysis/commongrams/TestCommonGramsFilter.java:87-136 (testCommonGramsQueryFilter, 'of the' at :105)
  • a Lucene main checkout (645d4fe14a20) grep of 'new XYRectangle(' in the test trees (only TestXYRectangle.java:31 and :42 pass reversed bounds)
  • a Lucene main checkout (645d4fe14a20) lucene/analysis/common/src/test/org/apache/lucene/analysis/core/TestAnalyzers.java:101-104 (_testStandardConstants, compile-only by design)
Minimal repro
  • a Lucene main checkout (645d4fe14a20) gradle/libs.versions.toml (junit 4.13.2, randomizedtesting 2.9.2, hamcrest 3.0)
Why it looks unintended
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java:25-45; lucene/analysis/common/src/test/org/apache/lucene/analysis/commongrams/TestCommonGramsFilter.java:275-276
  • a Lucene main checkout (645d4fe14a20) git grep -nE 'AwaitsFix\(|@Ignore\b' (without @IgnoreRandomChains mentions): 52 lines, 3 of them commented out (TestMappingCharFilter.java:225, :253; BaseKnnVectorQueryTestCase.java:596), so 49 active annotations
  • a Lucene main checkout (645d4fe14a20) lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCaseJupiter.java:610
History
  • a Lucene clone: git grep of the four names at releases/lucene/10.6.0

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Made in parallel with this doc from the mechanism in the brief; the PNG is the summary frame.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.