fix(testHub): retry deferred last-test-finish flush so it isn't dropped (SDK-7265) - #129
fix(testHub): retry deferred last-test-finish flush so it isn't dropped (SDK-7265)#129rounak610 wants to merge 5 commits into
Conversation
…ed (SDK-7265) The binary flow defers each mocha TestRunFinished past the afterEach window and flushes it at the next test's boundary; the worker's last test relies on the single flush from service.after(). sendTestFrameworkEvent swallowed send errors with no retry, so a transient gRPC failure on that flush dropped the finish — orphaning one test that Test Hub reaps at its ~60-min per-test timeout, which stamps the whole (passing) build `timeout`. Retry the flush up to 3x with backoff, surface send success/failure, and re-stash on total failure so a later flush/teardown can retry rather than dropping it. Mirrors SDK-7061's build-stop retry, applied to the test-finish path. Adds a deterministic reproduction test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🔴 Blocking findings — fix required See the SDK PR Review Agent's report from your local run. Change map (generated deterministically from the diff)graph LR
subgraph nwdio_browserstack_service["wdio-browserstack-service"]
npackages_browserstack_service_tests_cli_modules_testHubModule_deferredFinish_test_ts["⚠ testHubModule.deferredFinish.test.ts<br/>~144 lines"]
npackages_browserstack_service_src_cli_modules_testHubModule_ts["testHubModule.ts<br/>~31 lines"]
n_changeset_pr_129_md["pr-129.md<br/>~5 lines"]
end
↻ This verdict comment is the review anchor — it's updated in place on each run (the gate posts its status separately). — SDK PR Review Agent |
…then tests (SDK-7265) Review found the re-stash-on-exhaustion guard races the two fire-and-forget flushPendingTestFinishEvent call sites: because the retry loop keeps an invocation in flight, a newer test can take the single pendingTestFinish slot while an older invocation is still retrying, and on exhaustion the guard either re-stashes a stale, already-failed event or drops silently — reintroducing the SDK-7265 symptom under a narrower window. Remove the re-stash entirely (it gave no reliable benefit: nothing re-flushes after service.after(), the last-test path). args is captured locally and the shared slot is only cleared, never written back, so concurrent flushes each retry their own event without clobbering one another. Tests: assert every retry re-sends the same finish (not just call count); drive the retry budget to full exhaustion and assert no re-stash; add a concurrency guard proving an older retrying flush never drops a newer test's finish. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review findings addressed — review passing ✅All 4 blocking findings from the automated review are resolved in #1 (Concurrency — re-stash race, Critical): Removed the re-stash-on-exhaustion entirely. It gave no reliable benefit — nothing re-flushes after #2 (re-stash-on-exhaustion branch never exercised): No longer applicable — that branch is gone. Replaced by an explicit exhaustion test. #3 & #4 (retry test only asserted call count): Strengthened.
Verification (Node 20): 🤖 Generated with Claude Code |
What is this about?
Hardens delivery of the deferred last-test
TestRunFinishedin the CLI/binary flow so a passing WebdriverIO (Mocha) build is no longer mis-reported astimeout.In the binary flow each Mocha
TestRunFinished(TEST/POST) is deferred past theafterEachwindow and flushed at the next test's boundary. The worker's last test has no next boundary, so it relies on the single flush fromservice.after()→flushPendingTestFinishEvent(). That flush went throughsendTestFrameworkEvent, which swallowed send errors with no retry — so a transient gRPC failure on that one send silently dropped the finish. The test then stays "in progress" and Test Hub reaps it at its ~60-min per-test timeout (TEST_TIMED_OUT_WITH_BUILD_SUCCESS), which stamps the whole (passing) buildtimeout.Fix:
flushPendingTestFinishEventnow retries the send up to 3× with backoff,sendTestFrameworkEventreturns success/failure instead of silently swallowing, and on total failure the finish is re-stashed so a later flush/teardown can retry rather than dropping it. Mirrors the build-stop retry from SDK-7061, applied to the test-finish path. Adds a deterministic reproduction test (testHubModule.deferredFinish.test.ts).Observed on the fast-upgrader cohort (9.32.1 / 9.33.0 / 9.33.1): representative group
4941460had ~178 timeout builds vs 3 healthy over 10 days — 98.3% sent a build-stop and carried exactly one dangling-test reap per build (build-stop ~4 min in, the loneTEST_TIMED_OUT_WITH_BUILD_SUCCESSat ~64 min).Related Jira task/s
https://browserstack.atlassian.net/browse/SDK-7265
Release (mandatory for every PR — required for the
ready-for-reviewlabel)Version bump: (required — tick exactly one)
Release notes type: (optional)
Release notes (customer-facing): (optional but encouraged)
Release notes (internal): (required — engineer-facing; what actually changed / why)
TestRunFinishedflush (flushPendingTestFinishEvent) had no retry and swallowed send failures, so a single transient gRPC hiccup on the worker's last test dropped the finish → the test was reaped by Test Hub's ~60-min per-test timeout and the passing build was stampedtimeout.flushPendingTestFinishEventnow retries 3× with backoff;sendTestFrameworkEventreturns a boolean success; on total failure the pending finish is re-stashed for a later flush instead of being dropped. Mirrors SDK-7061's build-stop retry, applied to the test-finish path.Checklist
PR Validations
Run Tests: Comment RUN_TESTS to trigger sanity tests.
🤖 Generated with Claude Code