Skip to content

Commit 8bae8dc

Browse files
committed
test: relax Windows legacy broker request budget
1 parent 96b4f7d commit 8bae8dc

1 file changed

Lines changed: 10 additions & 7 deletions

File tree

tests/hooks.test.mjs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const root = fileURLToPath(new URL('../', import.meta.url));
1717
const fakeZCode = join(root, 'tests/fixtures/fake-zcode-cli.mjs');
1818
const legacyBroker = join(root, 'tests/fixtures/legacy-zcode-broker-v1.mjs');
1919
const ownerStoreLockHolder = join(root, 'tests/fixtures/owner-store-lock-holder.mjs');
20+
// Parallel Windows runners can spend more than 750 ms scheduling a legacy
21+
// broker request even though the SessionEnd cleanup budget remains bounded.
22+
const brokerTestRequestTimeoutMs = process.platform === 'win32' ? 2_000 : 750;
2023

2124
function isGateRunPath(path) { return path.split(sep).includes('gate-runs'); }
2225

@@ -207,17 +210,17 @@ test('HEAD cleanup interoperates with the hermetic v1 broker while a sibling rem
207210
const legacy = spawn(process.execPath, [legacyBroker, configPath], { cwd, env: { ...process.env, FAKE_ZCODE_RECORD: record, FAKE_ZCODE_STOP_ERROR_PREFIX: 'legacy-history-' }, stdio: ['ignore', 'ignore', 'pipe'] }); let legacyStderr = ''; let legacyExited = false; legacy.stderr.on('data', (chunk) => { legacyStderr += chunk; }); legacy.once('exit', () => { legacyExited = true; }); await writeBrokerIdentity(identityPath, { endpoint, pid: legacy.pid, instanceId, brokerToken });
208211
t.after(async () => { if (!legacyExited) { try { process.kill(legacy.pid, 'SIGTERM'); } catch { /* already exited */ } } });
209212
const identity = { endpoint, pid: legacy.pid, instanceId, brokerToken }; const readyDeadline = Date.now() + 5_000; while (Date.now() < readyDeadline && !await probeBrokerHealth(identity)) await new Promise((resolve) => setTimeout(resolve, 25)); assert.equal(await probeBrokerHealth(identity), true, legacyStderr);
210-
const client = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: owner, requestTimeoutMs: 750 }); assert.deepEqual(await client.brokerCapabilities(), { releaseOwnerExclusions: false }); await client.createSession({ workspace: cwd, sessionId: 'legacy-later-active' }); await client.close(); const siblingClient = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: sibling, requestTimeoutMs: 750 }); await siblingClient.createSession({ workspace: cwd, sessionId: 'legacy-sibling-active' }); t.after(() => siblingClient.close()); const probe = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: probeOwner, requestTimeoutMs: 750 }); assert.deepEqual((await probe.releaseOwner(['legacy-exclusion-probe'])).releasedSessionIds, ['legacy-exclusion-probe'], 'v1 broker must faithfully ignore the future exclusion field'); await probe.close();
211-
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: owner, requestTimeoutMs: 750 }); assert.equal(processAlive(legacy.pid), true, 'active sibling must keep the shared legacy broker alive'); let owners = JSON.parse(await readFile(ownershipPath, 'utf8')).sessions; assert.equal(owners['legacy-later-active'], undefined); assert.equal(owners['legacy-sibling-active'], sibling); assert.equal(owners['legacy-sibling-session'], sibling); assert.equal(histories.filter((sessionId) => owners[sessionId] === owner).length, 17); assert.ok(result.failedSessionIds.length <= 17); assert.ok(result.deferredSessionCount <= 17); assert.ok((await siblingClient.listSessions()).sessions.some((session) => session.sessionId === 'legacy-sibling-active'));
212-
const reuseOwner = ownerIdForSession('legacy-reuse'); const reuse = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: reuseOwner, requestTimeoutMs: 750 }); await reuse.createSession({ workspace: cwd, sessionId: 'legacy-later-active' }); await reuse.close(); owners = JSON.parse(await readFile(ownershipPath, 'utf8')).sessions; assert.equal(owners['legacy-later-active'], reuseOwner);
213+
const client = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: owner, requestTimeoutMs: brokerTestRequestTimeoutMs }); assert.deepEqual(await client.brokerCapabilities(), { releaseOwnerExclusions: false }); await client.createSession({ workspace: cwd, sessionId: 'legacy-later-active' }); await client.close(); const siblingClient = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: sibling, requestTimeoutMs: brokerTestRequestTimeoutMs }); await siblingClient.createSession({ workspace: cwd, sessionId: 'legacy-sibling-active' }); t.after(() => siblingClient.close()); const probe = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: probeOwner, requestTimeoutMs: brokerTestRequestTimeoutMs }); assert.deepEqual((await probe.releaseOwner(['legacy-exclusion-probe'])).releasedSessionIds, ['legacy-exclusion-probe'], 'v1 broker must faithfully ignore the future exclusion field'); await probe.close();
214+
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: owner, requestTimeoutMs: brokerTestRequestTimeoutMs }); assert.equal(processAlive(legacy.pid), true, 'active sibling must keep the shared legacy broker alive'); let owners = JSON.parse(await readFile(ownershipPath, 'utf8')).sessions; assert.equal(owners['legacy-later-active'], undefined); assert.equal(owners['legacy-sibling-active'], sibling); assert.equal(owners['legacy-sibling-session'], sibling); assert.equal(histories.filter((sessionId) => owners[sessionId] === owner).length, 17); assert.ok(result.failedSessionIds.length <= 17); assert.ok(result.deferredSessionCount <= 17); assert.ok((await siblingClient.listSessions()).sessions.some((session) => session.sessionId === 'legacy-sibling-active'));
215+
const reuseOwner = ownerIdForSession('legacy-reuse'); const reuse = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: reuseOwner, requestTimeoutMs: brokerTestRequestTimeoutMs }); await reuse.createSession({ workspace: cwd, sessionId: 'legacy-later-active' }); await reuse.close(); owners = JSON.parse(await readFile(ownershipPath, 'utf8')).sessions; assert.equal(owners['legacy-later-active'], reuseOwner);
213216
const stops = (await readFile(record, 'utf8')).trim().split('\n').filter(Boolean).map(JSON.parse).filter((call) => call.method === 'session/stop').map((call) => call.params.sessionId); assert.ok(stops.includes('legacy-later-active')); assert.ok(!stops.includes('legacy-sibling-active')); assert.ok(stops.filter((sessionId) => sessionId.startsWith('legacy-history-')).length <= 32, 'legacy failed prefix retries must stay bounded');
214217
});
215218

216219
test('legacy fallback stays inside the hook budget when the owner store lock is contended cross-process', async (t) => {
217220
const { cwd, data } = await workspace(); const record = join(data, 'contended-zcode-calls.jsonl'); await writeFile(record, ''); const owner = ownerIdForSession('contended-owner'); const sibling = ownerIdForSession('contended-sibling'); const histories = Array.from({ length: 17 }, (_, index) => `contended-history-${String(index).padStart(2, '0')}`); await reconcileBrokerOwnership({ dataRoot: data, workspace: cwd, ownerId: owner, ownedSessionIds: histories });
218221
const storage = await resolveWorkspaceStorage({ dataRoot: data, workspace: cwd }); const brokerDirectory = join(storage.directory, 'broker'); const identityPath = join(brokerDirectory, 'identity.json'); const ownershipPath = join(brokerDirectory, 'session-owners.json'); const configPath = join(brokerDirectory, 'contended-config.json'); const endpoint = brokerEndpointFor({ dataRoot: data, workspace: storage.workspacePath }); const instanceId = 'd'.repeat(48); const brokerToken = 'e'.repeat(64); await writeFile(configPath, JSON.stringify({ endpoint, instanceId, brokerToken, launch: { command: process.execPath, args: [fakeZCode], target: fakeZCode }, workspace: storage.workspacePath, ownershipPath, identityPath }));
219222
const legacy = spawn(process.execPath, [legacyBroker, configPath], { cwd, env: { ...process.env, FAKE_ZCODE_RECORD: record, FAKE_ZCODE_STOP_ERROR_PREFIX: 'contended-history-' }, stdio: ['ignore', 'ignore', 'pipe'] }); let legacyExited = false; legacy.once('exit', () => { legacyExited = true; }); await writeBrokerIdentity(identityPath, { endpoint, pid: legacy.pid, instanceId, brokerToken }); t.after(() => { if (!legacyExited) { try { process.kill(legacy.pid, 'SIGTERM'); } catch { /* exited */ } } }); const identity = { endpoint, pid: legacy.pid, instanceId, brokerToken }; const readyDeadline = Date.now() + 5_000; while (Date.now() < readyDeadline && !await probeBrokerHealth(identity)) await new Promise((resolve) => setTimeout(resolve, 25)); assert.equal(await probeBrokerHealth(identity), true);
220-
const target = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: owner, requestTimeoutMs: 750 }); await target.createSession({ workspace: cwd, sessionId: 'contended-target-active' }); await target.close(); const siblingClient = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: sibling, requestTimeoutMs: 750 }); await siblingClient.createSession({ workspace: cwd, sessionId: 'contended-sibling-active' }); t.after(() => siblingClient.close());
223+
const target = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: owner, requestTimeoutMs: brokerTestRequestTimeoutMs }); await target.createSession({ workspace: cwd, sessionId: 'contended-target-active' }); await target.close(); const siblingClient = await createZCodeClient({ workspace: cwd, brokerEndpoint: endpoint, brokerToken, ownerId: sibling, requestTimeoutMs: brokerTestRequestTimeoutMs }); await siblingClient.createSession({ workspace: cwd, sessionId: 'contended-sibling-active' }); t.after(() => siblingClient.close());
221224
const beforeContention = JSON.parse(await readFile(ownershipPath, 'utf8')).sessions; assert.equal(beforeContention['contended-target-active'], owner); assert.equal(beforeContention['contended-sibling-active'], sibling);
222225
const holder = spawn(process.execPath, [ownerStoreLockHolder, data, cwd, 'identity.json'], { stdio: ['pipe', 'pipe', 'pipe'] }); t.after(() => { try { process.kill(holder.pid, 'SIGTERM'); } catch { /* exited */ } }); const holderArmed = await new Promise((resolvePromise, reject) => { holder.stdout.once('data', (chunk) => resolvePromise(chunk.toString('utf8').trim())); holder.once('error', reject); holder.once('exit', (code) => reject(new Error(`lock holder exited ${code}`))); }); assert.equal(holderArmed, `armed:${ownershipPath}.lock`);
223226
const holderReadyPromise = new Promise((resolvePromise, reject) => { holder.stdout.once('data', (chunk) => resolvePromise(chunk.toString('utf8').trim())); holder.once('error', reject); holder.once('exit', (code) => reject(new Error(`lock holder exited ${code}`))); }); holder.stdin.write('acquire'); const holderReady = await holderReadyPromise; assert.equal(holderReady, `ready:${ownershipPath}.lock`); assert.equal(processAlive(holder.pid), true);
@@ -244,7 +247,7 @@ test('owner release spans existing broker profiles and preserves mappings whose
244247
const sibling = await createManagedZCodeClient({ dataRoot: data, workspace: cwd, launch, ownerId: ownerB, env: { ...process.env, FAKE_ZCODE_RECORD: record, ...(index ? { FAKE_ZCODE_ERROR: 'session/stop' } : {}) }, ...profile });
245248
await sibling.createSession({ workspace: cwd, sessionId: `owner-b-${index}` }); await sibling.close();
246249
}
247-
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: 750 });
250+
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: brokerTestRequestTimeoutMs });
248251
assert.deepEqual(result.releasedSessionIds, ['owner-a-0']); assert.deepEqual(result.failedSessionIds, ['owner-a-1']);
249252
const storage = await resolveWorkspaceStorage({ dataRoot: data, workspace: cwd }); const brokerFiles = await readdir(join(storage.directory, 'broker')); const stores = brokerFiles.filter((name) => /^session-owners(?:-[a-f0-9]{16})?\.json$/.test(name));
250253
const mappings = Object.assign({}, ...await Promise.all(stores.map(async (name) => {
@@ -264,7 +267,7 @@ test('hung owner stops are broker-bounded and retain every unconfirmed and sibli
264267
for (let index = 0; index < 4; index += 1) await first.createSession({ workspace: cwd, sessionId: `hung-owner-${index}` }); await first.close();
265268
const sibling = await createManagedZCodeClient({ dataRoot: data, workspace: cwd, launch, ownerId: ownerB, env: peerEnv }); await sibling.createSession({ workspace: cwd, sessionId: 'hung-sibling' }); await sibling.close();
266269
const storage = await resolveWorkspaceStorage({ dataRoot: data, workspace: cwd }); const identity = JSON.parse(await readFile(join(storage.directory, 'broker/identity.json'), 'utf8')); const started = Date.now();
267-
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: 750 });
270+
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: brokerTestRequestTimeoutMs });
268271
assert.ok(Date.now() - started < 2_000, 'broker owner cleanup must finish inside the SessionEnd budget'); assert.deepEqual(result.releasedSessionIds, []); assert.deepEqual(result.failedSessionIds.sort(), ['hung-owner-0', 'hung-owner-1', 'hung-owner-2', 'hung-owner-3']); assert.equal(result.deferredSessionCount, 0);
269272
const owners = JSON.parse(await readFile(join(storage.directory, 'broker/session-owners.json'), 'utf8')).sessions;
270273
assert.deepEqual(owners, { 'hung-owner-0': ownerA, 'hung-owner-1': ownerA, 'hung-owner-2': ownerA, 'hung-owner-3': ownerA, 'hung-sibling': ownerB });
@@ -276,7 +279,7 @@ test('owner release does not spawn ZCode when a live broker has only historical
276279
const { cwd, data } = await workspace(); const record = join(data, 'zcode-calls.jsonl'); await writeFile(record, ''); const ownerA = ownerIdForSession('historical-a'); const ownerB = ownerIdForSession('historical-b');
277280
await reconcileBrokerOwnership({ dataRoot: data, workspace: cwd, ownerId: ownerA, ownedSessionIds: ['historical-a-session'] }); await reconcileBrokerOwnership({ dataRoot: data, workspace: cwd, ownerId: ownerB, ownedSessionIds: ['historical-b-session'] });
278281
const identity = await ensureZCodeBroker({ dataRoot: data, workspace: cwd, launch: { command: process.execPath, args: [fakeZCode], target: fakeZCode }, env: { ...process.env, FAKE_ZCODE_RECORD: record } }); const started = Date.now();
279-
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: 750 }); assert.ok(Date.now() - started < 2_000); assert.deepEqual(result, { releasedSessionIds: ['historical-a-session'], failedSessionIds: [], deferredSessionCount: 0 }); assert.equal(await readFile(record, 'utf8'), '', 'cleanup must not spawn the configured ZCode peer');
282+
const result = await releaseManagedZCodeOwner({ dataRoot: data, workspace: cwd, ownerId: ownerA, requestTimeoutMs: brokerTestRequestTimeoutMs }); assert.ok(Date.now() - started < 2_000); assert.deepEqual(result, { releasedSessionIds: ['historical-a-session'], failedSessionIds: [], deferredSessionCount: 0 }); assert.equal(await readFile(record, 'utf8'), '', 'cleanup must not spawn the configured ZCode peer');
280283
const storage = await resolveWorkspaceStorage({ dataRoot: data, workspace: cwd }); assert.deepEqual(JSON.parse(await readFile(join(storage.directory, 'broker/session-owners.json'), 'utf8')).sessions, { 'historical-b-session': ownerB });
281284
const deadline = Date.now() + 2_000; while (Date.now() < deadline && processAlive(identity.pid)) await new Promise((resolve) => setTimeout(resolve, 25)); assert.equal(processAlive(identity.pid), false);
282285
});

0 commit comments

Comments
 (0)