* fix(#474): route state date-stamping + installer lock loop through clock seam (nowIso/today + GSD_NOW_MS adapter) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#474): use process.ppid (not pid 1) as held-lock owner in install-lock timeout test pid 1 is POSIX init/launchd (always alive) but does not exist on Windows, so isPidAlive(1) returns false, the lock is reclaimed as stale, and acquireInstallMigrationLock no longer throws -- failing the timeout assertion on windows-latest,22. process.ppid is a live, non-self process on every platform, so the lock is seen as held and the timeout path throws deterministically cross-platform. Refs #474 --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
3.3 KiB
JavaScript
108 lines
3.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Fake clock helper for deterministic lock tests (issue #453).
|
|
*
|
|
* Usage:
|
|
* const { makeFakeClock } = require('./helpers/clock.cjs');
|
|
* const clock = makeFakeClock(0); // start at t=0
|
|
* clock.advance(5000); // jump 5 000 ms forward
|
|
* acquireStateLock(path, clock); // no real waits; timeout/stale logic driven by advance()
|
|
*
|
|
* The fake clock returned by makeFakeClock() is compatible with the clock seam
|
|
* accepted by acquireStateLock(statePath, clock) in state.cjs and
|
|
* withPlanningLock(cwd, fn, clock) in planning-workspace.cjs.
|
|
*
|
|
* API
|
|
* ───
|
|
* now() → returns the current virtual epoch milliseconds.
|
|
* sleep(ms) → records a sleep call without blocking; advances virtual time by ms.
|
|
* advance(ms) → advance the virtual clock by ms without sleeping. Use between
|
|
* synchronous retries to simulate elapsed time.
|
|
* sleepCalls → array of ms values passed to sleep(), for assertion use.
|
|
* nowValue → current virtual milliseconds (same as calling now()).
|
|
*
|
|
* Design notes
|
|
* ────────────
|
|
* • sleep() advances the clock by the requested duration so that a retry loop
|
|
* checking `clock.now() - startedAt >= maxWaitMs` eventually trips the timeout
|
|
* without needing any real sleeps in between.
|
|
* • advance() allows the test to simulate arbitrary elapsed time without triggering
|
|
* a sleep call (useful for driving the stale-lock check independently).
|
|
* • Both now() and sleep() are intentionally synchronous so tests using them remain
|
|
* fully synchronous — no async needed for lock serialization / timeout assertions.
|
|
*/
|
|
|
|
/**
|
|
* @param {number} [startMs=0] - initial virtual epoch milliseconds
|
|
* @returns {{ now(): number, sleep(ms: number): void, advance(ms: number): void, sleepCalls: number[], nowValue: number }}
|
|
*/
|
|
function makeFakeClock(startMs) {
|
|
if (startMs === undefined) startMs = 0;
|
|
|
|
let _now = startMs;
|
|
const _sleepCalls = [];
|
|
|
|
const clock = {
|
|
/** Return current virtual time (epoch ms). */
|
|
now() {
|
|
return _now;
|
|
},
|
|
|
|
/**
|
|
* Record a sleep call and advance virtual time by ms.
|
|
* Does NOT block.
|
|
*
|
|
* @param {number} ms
|
|
*/
|
|
sleep(ms) {
|
|
_sleepCalls.push(ms);
|
|
_now += ms;
|
|
},
|
|
|
|
/**
|
|
* Advance the virtual clock by ms without recording a sleep call.
|
|
* Use to simulate time passing between lock attempts.
|
|
*
|
|
* @param {number} ms
|
|
*/
|
|
advance(ms) {
|
|
_now += ms;
|
|
},
|
|
|
|
/**
|
|
* Return the virtual instant as an ISO 8601 string (UTC).
|
|
* Mirrors realClock.nowIso() so fake clocks are drop-in substitutes.
|
|
*
|
|
* @returns {string} e.g. "2020-06-15T12:00:00.000Z"
|
|
*/
|
|
nowIso() {
|
|
return new Date(_now).toISOString();
|
|
},
|
|
|
|
/**
|
|
* Return the virtual date as a YYYY-MM-DD string (UTC calendar day).
|
|
* Mirrors realClock.today() so fake clocks are drop-in substitutes.
|
|
*
|
|
* @returns {string} e.g. "2020-06-15"
|
|
*/
|
|
today() {
|
|
return new Date(_now).toISOString().split('T')[0];
|
|
},
|
|
|
|
/** Array of ms values passed to sleep() in call order. */
|
|
get sleepCalls() {
|
|
return _sleepCalls;
|
|
},
|
|
|
|
/** Current virtual time (same as now()). */
|
|
get nowValue() {
|
|
return _now;
|
|
},
|
|
};
|
|
|
|
return clock;
|
|
}
|
|
|
|
module.exports = { makeFakeClock };
|