Skip to content

fix/discovery-incomplete-and-gradlew-timeout: surface parse failures as DISCOVERY_INCOMPLETE and deadline nested gradlew invocations#33

Merged
vedanthvdev merged 1 commit intomasterfrom
fix/discovery-incomplete-and-gradlew-timeout
Apr 22, 2026
Merged

fix/discovery-incomplete-and-gradlew-timeout: surface parse failures as DISCOVERY_INCOMPLETE and deadline nested gradlew invocations#33
vedanthvdev merged 1 commit intomasterfrom
fix/discovery-incomplete-and-gradlew-timeout

Conversation

@vedanthvdev
Copy link
Copy Markdown
Owner

Summary

Closes the two deferred reliability findings on the path to v2.0 (v1.9.22):

  • Situation.DISCOVERY_INCOMPLETE now fires on any scanned Java file that fails to parse. ProjectIndex counts parse failures at the cache boundary (de-duplicated across strategies) and AffectedTestsEngine routes through the new situation before the old empty/success branches. Mode defaults: CI/STRICT escalate to FULL_SUITE, LOCAL stays on SELECTED. New DSL knob onDiscoveryIncomplete; matching EscalationReason.RUN_ALL_ON_DISCOVERY_INCOMPLETE in --explain.
  • affectedTests.gradlewTimeoutSeconds deadlines the nested ./gradlew invocation. Defaults to 0 (pre-v1.9.22 wait-forever behaviour). Positive values branch onto a ProcessBuilder watchdog with a destroy() → 10s grace → destroyForcibly() → 5s reap ladder. Descendants (shared Gradle daemons, test JVMs) are snapshotted before the first signal and reaped on both legs of the ladder — the initial cut only reaped on the forcible leg, which failed the new regression test because SIGTERM-responsive wrappers exit on destroy() and leave children re-parented to pid 1.

Tier-2 findings from the code-reviewer sweep are also addressed in this PR: situationOrder() + matrix regression test, action-aware describeSkipReason, Situation javadoc refresh, DISCOVERY_INCOMPLETE unwired from runAllIfNoMatches, and AffectedTestsEngine.run() tail deduplicated (no more missing === Result header).

Test plan

  • ./gradlew check — all core + gradle module tests green, including the new AffectedTestTaskTimeoutTest suite
  • Revert-check: removed the descendants-kill loop from shutdownChild, confirmed descendantsAreTerminatedNotOnlyTheWrapper flips red (PID survives), restored the fix
  • Revert-check: removed the parseFailureCount++ in ProjectIndex, confirmed the DISCOVERY_INCOMPLETE engine tests fail, restored the fix
  • Code-reviewer subagent sweep on the staged diff; all tier-1 and tier-2 findings addressed in this PR
  • Pilot run on security-service with the resulting v1.9.22 build to confirm the DSL knob and the watchdog behave as documented under a real merge-gate diff

…as DISCOVERY_INCOMPLETE and deadline nested gradlew invocations

Closes the two deferred findings from the v1.9.21 reliability batch. Before this release, a single mid-refactor syntax error in a scanned Java file would silently shrink the test selection: `JavaParsers.parseOrWarn` returned null, the dispatch map was built from whatever happened to parse, and the engine reported `DISCOVERY_SUCCESS` — exactly the scenario where the merge-gate safety net needs to pay out. `ProjectIndex` now counts parse failures at the cache boundary (de-duplicated across strategies) and `AffectedTestsEngine` routes through a new `Situation.DISCOVERY_INCOMPLETE` before the old empty/success branches. Mode defaults keep the existing safety-vs-speed asymmetry: `CI`/`STRICT` escalate to `FULL_SUITE`, `LOCAL` stays on `SELECTED` because the developer already saw the WARN in their terminal. The new `onDiscoveryIncomplete` DSL knob lets operators override per-project, and a matching `EscalationReason.RUN_ALL_ON_DISCOVERY_INCOMPLETE` feeds `--explain` so a full-suite run from a parse failure is distinguishable from one triggered by an empty diff or an unmapped file.

The second half of the release deadlines the nested `./gradlew :module:test` invocation. Before this release, that child was launched via `ExecOperations` with no timeout surface at all — a hung test kept the CI worker busy until the outer CI-job deadline killed everything, which typically surfaces as "pipeline failed with no useful logs" hours later. The new `affectedTests.gradlewTimeoutSeconds` knob defaults to `0` (preserves the wait-forever behaviour for zero-config upgrades) and, when set, branches onto a `ProcessBuilder` watchdog that runs a polite `destroy()` → 10s grace → `destroyForcibly()` → 5s reap ladder and fails the build with a message that names the setting. Because `./gradlew` connects to a shared daemon JVM, killing only the wrapper would leave the actually-hung test JVM running as a grandchild; the watchdog snapshots `ProcessHandle.descendants()` before signalling and reaps every still-live descendant on both legs of the ladder — the initial cut only reaped on the forcible leg, which failed the regression test that now pins the behaviour because a SIGTERM-responsive wrapper (plain `sh`, most test runners) exits on `destroy()` and leaves its children re-parented to pid 1. Outer-build cancellation propagates through the same path and re-asserts the interrupt. Validation lives at the builder gate (rejects negative values, pins the zero default) and at runtime in a new `AffectedTestTaskTimeoutTest` that drives the watchdog against a hung `sleep 60` child and verifies the grandchild PID via `ProcessHandle` after the exception surfaces.

The PR also addresses the tier-2 findings from the code-reviewer sweep on the initial cut: `situationOrder()` and the matrix-order regression test now include `DISCOVERY_INCOMPLETE` in the correct evaluation position, `describeSkipReason` branches on `Action` so `DISCOVERY_INCOMPLETE + SELECTED + empty` no longer renders the contradictory "SELECTED … onDiscoveryIncomplete=SKIPPED" summary, `Situation` javadoc is refreshed from "six" to "seven" situations with the new evaluation order, `DISCOVERY_INCOMPLETE` is explicitly unwired from the `runAllIfNoMatches` legacy boolean so `CI` mode cannot silently skip on parse failures, and the `AffectedTestsEngine.run()` tail no longer duplicates the result-construction block or drops the `=== Result` log header.
@vedanthvdev vedanthvdev merged commit f86d97f into master Apr 22, 2026
1 check passed
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