fix/review-findings-batch-1: Batch-1 review fixes — glob alignment, DELETE diffs, locale, shallow-clone msg, DX#27
Merged
vedanthvdev merged 1 commit intomasterfrom Apr 22, 2026
Conversation
…X fixes from the post-v1.9.15 review
Consolidates the tier-1 findings from the four parallel review passes
(correctness, security, reliability, adversarial) run against the v2
codebase. Every fix below landed with a targeted test so the
regression cannot re-enter silently.
Correctness — the two bugs that could silently reduce coverage:
- out-of-scope dir globs now align on both sides of the pipeline.
The diff-side classifier in PathToClassMapper honoured
"api-test/**" but the on-disk classifier in ProjectIndex treated
the same string as a literal prefix, so a mixed diff (one
production file + a refactor under api-test/) bucketed the
api-test file correctly yet still dispatched tests discovered
under api-test/src/test/java. Both sides now delegate to a
shared OutOfScopeMatchers utility, with a regression test that
exercises literal and glob shapes against identical on-disk
layouts.
- `git rm`-only MRs no longer skip all tests. DiffEntry.DELETE
entries now surface through their old path so ignore/out-of-scope
rules apply normally; deleted production classes reach the
transitive strategy instead of routing the whole MR through
EMPTY_DIFF → SKIPPED under local/ci mode. The existing engine
filter still drops FQNs whose backing file is gone, so surfacing
deletions never asks Gradle to run a missing test.
- ImplementationStrategy now recognises the DefaultFooService prefix
shape, not only the FooServiceImpl suffix. The plugin ships with
implementationNaming = ["Impl", "Default"] and the Javadoc
documents both shapes, but the naming-convention loop used to
append both tokens as suffixes (FooServiceDefault), matching
nothing real.
Reliability — hardening against inputs the v2 engine didn't handle:
- InvalidPathException (NUL bytes, Linux-committed colon paths on
Windows, reserved device names) is now caught in both
PathToClassMapper.isIgnored and the out-of-scope glob matcher.
Before, the unhandled exception killed the whole affectedTest
task with a stack trace; now the offending file falls through to
the unmapped bucket and the safety net escalates normally.
- GitChangeDetector translates JGit's MissingObjectException into a
targeted message naming the likely cause (shallow clone in CI
that doesn't know the base ref). Before, users saw the raw JGit
exception and had to guess whether the problem was the ref, the
clone depth, or a corrupt repo.
- Malformed globs in outOfScope*Dirs and ignorePaths now fail at
config-time with an IllegalStateException naming the config key,
list index, and offending pattern, instead of surfacing a raw
PatternSyntaxException from several engine frames deep.
- parseMode / parseAction / isWindows pinned to Locale.ROOT so
Turkish-locale JVMs don't turn `mode = "ci"` into `Unknown
affectedTests.mode 'ci'`. Matches the Locale.ROOT pin
AffectedTestsConfig already applied to legacy parsing.
DX and defense-in-depth:
- FQN shape validated before passing to Gradle's --tests argv.
Discovered strings that aren't Java-shaped identifiers are logged
as warnings and skipped — a last line of defence against a buggy
custom strategy or parser anomaly injecting a shell metacharacter
into the test-filter flag.
- Per-FQN dispatch lines demoted from lifecycle to info. Each MR
now gets one lifecycle summary per Gradle task
(":api:test (3 test classes)"); individual FQNs print only under
--info. Before, a 200-class MR buried the outcome/situation line
the user actually cared about under 200 dispatch lines.
Documentation:
- Mode Javadoc re-aligned with the mode-defaults table in the
design doc (LOCAL is the pre-v2 baseline; CI adds the
DISCOVERY_EMPTY = FULL_SUITE safety net on top of that).
- Situation Javadoc cross-reference for ALL_FILES_OUT_OF_SCOPE
fixed — the old reference pointed at a since-renamed constant.
- TransitiveStrategy and AffectedTestsExtension now document the
actual transitiveDepth default of 4, not the pre-v2 value of 2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Post-v1.9.15 in-depth review across four lenses (correctness,
reliability, security, adversarial) surfaced a batch of issues. This
PR applies every tier-1 finding with a targeted test so the regression
cannot re-enter silently. The tier-2 findings (mostly wording and log
tone) will land in a follow-up.
The two correctness fixes that could silently reduce coverage
outOfScope*Dirs glob alignment
PathToClassMapper(diff-side) honoured"api-test/**"butProjectIndex(on-disk) treated the same string as a literal prefix.A mixed diff — one production file + a refactor under
api-test/—bucketed the api-test file correctly yet still dispatched tests
discovered under
api-test/src/test/java. This is the exact hole thev1.9.14
--explainhint was introduced to warn about, but the fixitself never landed.
Both sides now delegate to a shared
OutOfScopeMatchersutility,with a regression test that exercises literal and glob shapes against
identical on-disk layouts and asserts they produce the same dispatch
map.
git rm-only MRs were silently skipping all testsDiffEntry.DELETE'snewPathis/dev/nulland the detectoronly recorded
newPath, so a pure-rm MR produced an empty diff androuted the whole change through
EMPTY_DIFF → SKIPPEDunderlocal/cimode. Now DELETE entries surface through their oldpath, the ignore/out-of-scope rules apply, and deleted production
classes reach the transitive strategy like any other change. The
engine's existing missing-file filter still drops FQNs whose backing
test file is gone, so surfacing deletions never asks Gradle to run a
missing test.
Reliability hardening
InvalidPathException(NUL bytes, Linux-committedfoo:bar.mdon Windows, reserved device names) now caught in
isIgnoredandthe glob matcher. Before, the unhandled exception killed the whole
affectedTesttask with a stack trace; now the offending filefalls through to the unmapped bucket and the safety net escalates
normally.
GitChangeDetectortranslates JGit'sMissingObjectExceptioninto a targeted message naming the likely cause (shallow clone in
CI that doesn't know the base ref).
IllegalStateExceptionnaming the config key, list index, andoffending pattern, instead of surfacing a raw
PatternSyntaxExceptionfrom several engine frames deep.parseMode/parseAction/isWindowspinned toLocale.ROOTso Turkish-locale JVMs don't turnmode = "ci"into
Unknown affectedTests.mode 'ci'.DX / defense-in-depth
--testsargv.Discovered strings that aren't Java-shaped identifiers are logged
as warnings and skipped.
ImplementationStrategynow recognises theDefaultFooServiceprefix shape, not only the
FooServiceImplsuffix — matching theshipped
implementationNaming = ["Impl", "Default"].lifecycletoinfo. EachMR now gets one
lifecyclesummary per Gradle task(
:api:test (3 test classes)); individual FQNs print only under--info. A 200-class MR no longer buries the outcome line under200 dispatch lines.
Documentation
ModeJavadoc re-aligned with the mode-defaults table in thedesign doc.
SituationJavadoc cross-reference forALL_FILES_OUT_OF_SCOPEfixed.TransitiveStrategyandAffectedTestsExtensionnow documentthe actual
transitiveDepthdefault of4.Test plan
./gradlew checkgreen locally (core + gradle plugin +functional tests).
OutOfScopeMatchersTestcovers literal/glob/mixedentries, blank-and-null quietly dropped, malformed glob fails with
config-key and index, and NUL-byte path fails closed.
ProjectIndexTestasserts literal-form and glob-formproduce the same dispatch map for identical on-disk layouts.
GitChangeDetectorTestextended withdetectsDeletedFilesByOldPathexercising a realgit rm+commit cycle.
ImplementationStrategyTestextended withfindsTestForDefaultPrefixedImplementationforcing the naming-convention branch to do the work alone (no
implementsclauseto let the AST branch rescue it).
AffectedTestTaskFqnValidationTestcovers accept-pathsand the shell-metachar / whitespace / hyphen / leading-and-trailing-
dot reject paths.
AffectedTestTaskLocaleTestreproduces the originalJDK-locale bug by flipping
Locale.setDefaultto Turkish beforeasserting parse correctness.
security-service(thepilot adopter) once the resulting release is on the Gradle Plugin
Portal. No behaviour change for config that already worked; the
fixes only close silent-coverage-loss paths.