fix(tests): deterministic concurrency via injectable clock seam; delete flaky racing tests (#459)
* feat(#453): add deterministic clock seam to lock modules Introduces get-shit-done/bin/lib/clock.cjs exporting realClock with now() (Date.now) and sleep() (Atomics.wait). acquireStateLock, writeStateMd, and readModifyWriteStateMd in state.cjs each accept an optional trailing clock param (default: realClock). withPlanningLock in planning-workspace.cjs gains the same seam. No production behavior change — all callers that omit the param continue to use realClock. Adds tests/helpers/clock.cjs (makeFakeClock) and tests/clock-seam.test.cjs with 20 deterministic in-process tests covering: lock serialization, timeout throw at maxWaitMs boundary, stale-lock takeover, lock released on error path, withPlanningLock timeout recovery, exit-cleanup integration, readModifyWriteStateMd call-site coverage (7 cmd*), and roadmap analyze behavioral assertion (50 phases, no elapsed-time gate). Deletes/converts per research verdicts: removes 11 source-grep/elapsed-time/ non-deterministic-concurrent tests across concurrency-safety.test.cjs, locking-bugs-1909-1916-1925-1927.test.cjs, and bug-1974-context-exhaustion- record.test.cjs. All deleted tests have deterministic replacements in clock-seam.test.cjs or surviving barrier-based tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#453): update module inventory for clock.cjs; make EEXIST-retry assertion behavioral Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#453): satisfy lint-tests — allow-test-rule annotation on readFileSync/includes runtime output check Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,41 +96,6 @@ describe('acquireStateLock: success path still returns lockPath', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// C3. EEXIST error → retry semantics unchanged
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('acquireStateLock: EEXIST retry semantics unchanged', () => {
|
||||
test('C3: source still handles EEXIST with retry / stale-lock removal', () => {
|
||||
const src = fs.readFileSync(STATE_CJS_PATH, 'utf-8');
|
||||
const fnSrc = extractAcquireStateLockSource(src);
|
||||
|
||||
// The EEXIST branch falls through to stale-lock detection and Atomics.wait retry.
|
||||
// These must still be present after the non-EEXIST fix.
|
||||
assert.ok(
|
||||
fnSrc.includes('Atomics.wait'),
|
||||
'acquireStateLock must still use Atomics.wait() for EEXIST retry sleep'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
fnSrc.includes('staleThresholdMs'),
|
||||
'acquireStateLock must still check stale lock threshold on EEXIST'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
fnSrc.includes('maxWaitMs'),
|
||||
'acquireStateLock must still enforce max wait budget on EEXIST retry exhaustion'
|
||||
);
|
||||
|
||||
// The fix only affects the non-EEXIST branch; the EEXIST guard must still exist.
|
||||
const eexistGuard = /err\.code\s*!==\s*['"]EEXIST['"]/;
|
||||
assert.ok(
|
||||
eexistGuard.test(fnSrc),
|
||||
'acquireStateLock must still distinguish EEXIST from other errors'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// C4. RETRY_ERRNOS set: new Docker/NFS transient codes must be present (#3776)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user