Skip to content

build(errorprone): promote the ten checks that have no sites left - #3419

Open
juherr wants to merge 6 commits into
masterfrom
juherr/errorprone-promote-clean-checks-to-error
Open

build(errorprone): promote the ten checks that have no sites left#3419
juherr wants to merge 6 commits into
masterfrom
juherr/errorprone-promote-clean-checks-to-error

Conversation

@juherr

@juherr juherr commented Aug 25, 2026

Copy link
Copy Markdown
Member

Ten Error Prone checks have no unsuppressed site left in the build, so they are now errors
instead of warnings. Until now none of them failed anything: they printed into an output long
enough that nobody read it, which is the same as not running them.

The measurement was wrong before this branch, and that is the main finding

javac reports at most 100 warnings per compile task. Four tasks here were past that, so
every warning count previously quoted for this repository was truncated — including the one
this work was scoped against. Fixing sites of one check made unrelated checks appear from
behind the cut-off, which is how it was noticed.

scoped against actually
Error Prone warnings in a build 345 1002
sites across the ten checks below 39 222
MissingSummary 158 361

Every number in this description comes from --rerun-tasks with -Xmaxwarns raised through
an untracked init script, with the javac error count published beside it — a plain javac
error silences the whole Error Prone pass, so a low warning count can mean the pass never
ran.

Result

before after
Error Prone warnings, uncapped 1002 775
warnings the build prints (capped at 100/task) 345 338
javac errors 0 0
sites across the ten promoted checks 222 0
check before after
MissingOverride 76 0
UnnecessaryParentheses 67 0
UnusedVariable 35 0
BooleanLiteral 13 0
BadImport 11 0
StringCaseLocaleUsage 10 0
TypeParameterUnusedInFormals 4 0
Finalize 2 0
NotJavadoc 2 0
InconsistentCapitalization 2 0

The whole 1002 → 775 drop is accounted for: 222 from the checks above, 3 MissingSummary that
stop applying once testng-test-kit is correctly treated as test code, 1 JavaUtilDate on a
Date that nothing read, and 1 UnusedMethod on the constructor whose suppression names it
explicitly. No check anywhere in the build gained a warning.

Both changes that are not sweeps

The test-code gate no longer keys on the task name. name.contains("Test") decided how
NullAway reads a compile, so testng-test-kit — test code living in a main source set — got
neither HandleTestAssertionLibraries nor the SelfAssertion opt-out, while its
org.testng.xml half was null-marked and checked. The Error Prone plugin already models
this: compilingTestOnlyCode takes its convention from the source set name and a module
can override it.

This has no other visible effect, so a green build could not prove it. A throwaway probe in
the marked package of testng-test-kit did:

static int nullAwayProbe(@Nullable String s) {
  assertThat(s).isNotNull();
  return s.length();
}
[NullAway] javac errors
before 1dereferenced expression 's' is @Nullable 0
after 0 0

The promotion was verified by breaking it, not by reading the config. One @Test() put
back into test/enable/A.java:

A.java:14: error: [UnnecessaryParentheses] These parentheses are unnecessary
BUILD FAILED

Both probes were removed before committing.

Where the sweep needed judgement

UnnecessaryParentheses looked like it would cost fixture coverage: the test.enable samples
deliberately pair @Test with @Test(), and EnableTest asserts both run. They cannot
differ — JLS 9.7.2 defines a marker annotation as shorthand for the empty-parenthesis form, and
TestNG reads annotations reflectively — so the pair was a source-form duplicate and the
parentheses went. The now-visible duplication between those method pairs is left alone.

Error Prone's patcher was used for MissingOverride, UnnecessaryParentheses, BadImport,
BooleanLiteral and NotJavadoc, and rejected for UnusedVariable.
It deletes the initialiser along
with the variable, so on this codebase it removed a parser.parse call that was the subject of
its own test, the factory-parameter rows from the emailable report, createXmlInclude calls
that build the XML the test then runs, and a static initialiser that throws on purpose. All of
it stayed green. Those 36 sites are by hand: where the value was dead it is gone, where only
the variable was dead the call stays.

The sweep also surfaced a real bug: EmailableReporter2 overwrote hasRows instead of
accumulating into it, so a result carrying only factory parameters got a placeholder row it did
not need. That was out of scope for an inert sweep, so it was reported as #3418 rather than
fixed here — and it has since been fixed upstream by #3427, which this branch is rebased onto.

Ten sites are suppressed rather than fixed, each with the reason at the site — the two
finalizers the leak test spins on, two Guice-injected constructor parameters whose injection is
what the sample proves, a field whose evaluation is what makes class initialisation fail, a
constructor parameter whose absence would change what a PackageTest assertion is exposed to,
Comparator.compare on the enum constant that compares nothing, and the three
<T> T newInstance(String, ...) declarations — interface, implementation and override, one of
them public API — where the class is named rather than passed so the type variable cannot reach
the formals.

Finalize is the one check that cleaned nothing: both its sites are suppressed, so it is
promoted on the bet that a new violation is worth stopping rather than on cleanup it bought.
The build config says so. TypeParameterUnusedInFormals did clean one —
YamlSchema.uncheckedCast had a single call site and a sibling helper that already did the job,
so the method went instead of gaining a suppression.

Two further sites are named rather than annotated, which is what Error Prone's unused prefix
means: a Comparator lambda, where a parameter cannot carry an annotation at all, and the local
whose division by zero is what makes a @BeforeClass fail. The annotated sites are spelled
"unused" to match what is already in the tree — except Test2's constructor, which names
UnusedVariable and UnusedMethod explicitly: it is never called and its parameter is never
read, both deliberately, and the alias would have hidden the second one, whose suggested fix is
the no-arg constructor the comment at that site says must not exist.

StringCaseLocaleUsage is not a bug fix at any of its sites, and that was checked rather
than assumed. Turkish lowercasing only moves an uppercase I. The entry names Parser.canParse
matches carry none (.xml, .yml, .yaml), and the two script engines on the test classpath
report Groovy and BeanShell — identical under tr_TR, as is every spelling a suite file
could use for them. Locale.ROOT is insurance against an engine or a suffix that does contain
one; no behaviour changes today.

What this deliberately does not do

  • It does not reduce the warning output. MissingSummary is 358 of the remaining 775 and
    stays a warning; Error Prone MissingSummary is the bulk of the build's warning output and is not enforced #3417 tracks what to do about it. What the branch buys is that these ten
    checks cannot regrow.
  • The build still truncates at 100 warnings per task. Raising it was tried and dropped to
    keep the printed output at its current size. Correctness does not depend on it — a promoted check
    emits errors, and any error fails the build whether or not it is among the first hundred
    reported — but anyone counting warnings from an ordinary build will undercount, and the
    configuration says so at the point where checks are promoted.
  • Checks needing a per-site behavioural decision (ReferenceEquality, EqualsGetClass,
    JdkObsolete, JavaUtilDate, StringSplitter, MixedMutabilityReturnType) are untouched,
    as is the javac deprecation/removal backlog.

Verification

./gradlew build: 0 failures, 0 errors. Six commits, each formatted and each compiling
with both counters at zero.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed emailable reports so factory-only results no longer include an unnecessary blank parameter row.
    • Improved locale-independent handling for archive detection, protocol parsing, script selection, URL processing, and report generation.
  • Refactor

    • Improved code clarity and consistency across test execution, configuration, annotations, and object creation.
  • Chores

    • Strengthened static analysis checks and cleaned up unused code, imports, and redundant test fixtures.

@juherr
juherr requested a review from krmahadevan as a code owner August 25, 2026 14:22
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 231b85af-2c9a-4649-a1af-80c2576d6df1

📥 Commits

Reviewing files that changed from the base of the PR and between db6c1b6 and 0cb61e2.

📒 Files selected for processing (2)
  • testng-core/src/main/java/org/testng/reporters/EmailableReporter2.java
  • testng-core/src/test/java/test/reports/EmailableReporterTest.java

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

This change tightens Error Prone configuration and updates production and test code to satisfy the enabled checks. It also makes string normalization locale-independent and removes unused code, redundant syntax, and ambiguous imports.

Changes

Error Prone cleanup

Layer / File(s) Summary
Error Prone configuration and source remediation
build-logic/..., testng-core-api/..., testng-core/..., testng-runner-api/..., testng-test-kit/..., testng-yaml/..., testng-jcommander/...
Promotes selected checks to errors, detects test-only compilation from task configuration, applies Locale.ROOT, adds override and suppression annotations, qualifies nested builders, removes unused code, simplifies syntax, and adds a regression test for factory-only reports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 0cb61

The change promotes cleaned Error Prone checks and adjusts test-code handling, with the reported build completing successfully and no actionable merge-blocking risk remaining beyond normal checks and review.

Suggested reviewers: krmahadevan, kalayciburak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.20% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 125 functions across 54 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: promoting ten Error Prone checks after removing all remaining sites.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch juherr/errorprone-promote-clean-checks-to-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

juherr added 6 commits August 26, 2026 11:16
The gate that chose how NullAway and SelfAssertion read a compile was
`name.contains("Test")`, so it classified code by a coincidence in the task
name. testng-test-kit is test code living in a main source set: its task is
`compileJava`, so it received neither HandleTestAssertionLibraries nor the
SelfAssertion opt-out, even though its org.testng.xml half is null-marked by
the package-info.class on its compile classpath and checked today.

The Error Prone plugin already models this. `compilingTestOnlyCode` takes its
convention from the source set name and a module can override it, so the
module declares what it is instead of being guessed at.

Proven with a throwaway probe in the marked package of testng-test-kit --
`assertThat(s).isNotNull()` followed by a dereference -- which fails to compile
before the change and passes after. A green build alone could not show this:
the change has no other visible effect.
Each of these is a rewrite javac itself suggests, with no change in behaviour:

- empty parentheses on annotations in the test.enable fixtures. @test and
  @test() are the same annotation by definition (JLS 9.7.2) and TestNG reads
  annotations reflectively, so the two forms it paired were never distinct.
- Boolean.FALSE/Boolean.TRUE where a primitive literal is meant.
- the nested ConfigMethodArguments.Builder import, qualified at its uses.
- parameters differing from the field they assign only by capitalisation.
- a javadoc block sitting between @OverRide and the method it documents, which
  is a comment rather than javadoc where it stood.
- toLowerCase without a locale. Not a bug fix: the suffixes Parser.canParse
  receives contain neither i nor I, so a Turkish default locale cannot reach
  them. It is insurance, and cheaper than the argument about whether it bites.
- a field that was only ever written, with the writes that fed it.

Only InconsistentCapitalization is emptied here. The counts the others were
scoped against came from truncated compiler output -- javac stops reporting
after 100 warnings per compile task -- so the real inventory is several times
larger and the rest is worked through in the commits that follow.
Machine output, kept in its own commit so it can be re-derived rather than
read hunk by hunk. Produced by Error Prone's own patcher:

    -XepPatchChecks:MissingOverride,UnnecessaryParentheses,BadImport,BooleanLiteral,NotJavadoc
    -XepPatchLocation:IN_PLACE

followed by autostyleApply, which is what puts the inserted annotations on
their own line.

MissingOverride, UnnecessaryParentheses, BadImport and BooleanLiteral are now
empty. NotJavadoc has one site the patcher does not fix; it is handled by hand
in the next commit.

The parenthesis removals all keep their grouping, and the one Boolean.TRUE in
a data provider boxes to the same cached instance the literal does.
The Error Prone patcher has no fix for these, or its fix would be wrong.

Fixed:
- toLowerCase without a locale, at every remaining site. All eight compare
  identifiers -- file suffixes, a protocol name, a scripting language name, a
  class name -- so Locale.ROOT is what they meant.
- a stray "/////" separator at the end of a test file. javac parses a run of
  three or more slashes as a markdown documentation comment (JEP 467), so it
  parsed as documentation attached to nothing.
- YamlSchema.mapKey now casts the setter through the same helper that key and
  listKey already use, which removes the single-call-site uncheckedCast the
  check was pointing at rather than suppressing it.

Suppressed, with the reason at the site:
- both finalizers in the github1461 leak test. One decrements the counter the
  test spins on until it reaches zero -- without it the wait never ends and the
  test fails on its timeOut -- and the other is the observation the test was
  written to make, so neither can go.
- the String overload of newInstance, on the interface, the implementation and
  the override in SuiteRunner. The class is named rather than passed, so the
  type variable cannot appear in the formals, and the signature is public API.
The Error Prone patcher was tried here first and rejected: it deletes the
initialiser along with the variable, so it removed a parser.parse call that was
the subject of its own test, the factory-parameter rows from the emailable
report, createXmlInclude calls that build the XML the test then runs, and a
static field initialiser that throws on purpose. All of that stayed green.
These are by hand instead.

Where the value really was dead it is gone. Where only the variable was dead
the call stays: dumpParametersInfo, parser.parse and createXmlInclude are all
kept and only their unread results dropped.

EmailableReporter2's dead store was the one site this branch reported rather
than fixed, as GITHUB-3418; it has since been fixed upstream by accumulating
into hasRows instead of overwriting it. All that is left here is reading the
parameters local already in hand rather than calling getParameters again.

Two sites are named rather than annotated, because Error Prone reads an
"unused" prefix as deliberate and a lambda parameter cannot carry an annotation
at all: the Comparator lambda in TestHTMLReporterTest, and the local whose
division by zero is what makes a @BeforeClass fail.

Elsewhere the reason goes at the site with @SuppressWarnings("unused"), the
spelling already used in this tree: the Comparator body on MethodSorting.NONE,
the Guice-injected constructor parameters whose injection is what the sample
proves, and the field whose evaluation is what makes class initialisation fail.

Test2's constructor names UnusedVariable and UnusedMethod instead of that
alias. It is never called and its parameter is never read, both deliberately,
and the alias would have hidden the second one -- whose suggested fix is the
no-arg constructor the comment at the site says must not exist.
Ten checks now have no occurrence anywhere in the build, so they can be errors
instead of warnings. Until now none of them failed anything: they printed into
an output long enough that nobody read it, which is the same as not running.

Promotion is what makes the preceding commits hold. Verified by putting one
violation back and watching the compile fail on it, rather than by reading the
configuration.

Checks that still have sites are untouched and stay warnings, MissingSummary
among them.
@juherr
juherr force-pushed the juherr/errorprone-promote-clean-checks-to-error branch from db6c1b6 to 0cb61e2 Compare August 26, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant