chore(lint): refactor magic-sleep tests to async waits + ratchet rules to error (#735)

Replace raw setTimeout/Atomics.wait synchronization sleeps in 4 test files
with a shared async delay()/waitFor() poll-for-condition helper in
tests/helpers.cjs, then flip local/no-magic-sleep-in-tests and
no-restricted-syntax from warn to error so the debt can't regrow.

- tests/helpers.cjs: add delay(ms) + waitFor(predicate, opts), exported
- bug-1974: setTimeout backoff -> await delay()
- config.test: drop Atomics.wait sleep(); async retry via await delay()
- graphify: waitForBuildStatus/cleanupHookRepo async via await delay()
- locking-bugs: 3 Atomics.wait poll loops -> await waitFor()
- eslint.config.mjs: ratchet both rules warn -> error

Refs #733

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-06 12:04:38 -04:00
committed by GitHub
parent bf48127c13
commit b4e817cfa0
6 changed files with 77 additions and 65 deletions

View File

@@ -11,7 +11,7 @@ const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { runGsdTools, createTempProject, cleanup, delay } = require('./helpers.cjs');
// ─── helpers ──────────────────────────────────────────────────────────────────
@@ -25,11 +25,7 @@ function writeConfig(tmpDir, obj) {
fs.writeFileSync(configPath, JSON.stringify(obj, null, 2), 'utf-8');
}
function sleep(ms) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
function runConfigEnsureSectionWithRetry(tmpDir, attempts = 4) {
async function runConfigEnsureSectionWithRetry(tmpDir, attempts = 4) {
let last;
for (let i = 0; i < attempts; i += 1) {
last = runGsdTools('config-ensure-section', tmpDir);
@@ -38,7 +34,7 @@ function runConfigEnsureSectionWithRetry(tmpDir, attempts = 4) {
const detail = `${last.error || ''}\n${last.output || ''}`;
const transient = /(EPERM|EBUSY|EACCES|ENOTEMPTY|resource busy|used by another process|permission denied)/i.test(detail);
if (!transient || i === attempts - 1) return last;
sleep(150 * (i + 1));
await delay(150 * (i + 1));
}
return last;
}
@@ -81,13 +77,13 @@ describe('config-ensure-section command', () => {
assert.ok('search_gitignored' in config, 'search_gitignored should exist');
});
test('is idempotent — returns already_exists on second call', () => {
const first = runConfigEnsureSectionWithRetry(tmpDir);
test('is idempotent — returns already_exists on second call', async () => {
const first = await runConfigEnsureSectionWithRetry(tmpDir);
assert.ok(first.success, `First call failed: ${first.error}`);
const firstOutput = JSON.parse(first.output);
assert.strictEqual(firstOutput.created, true);
const second = runConfigEnsureSectionWithRetry(tmpDir);
const second = await runConfigEnsureSectionWithRetry(tmpDir);
assert.ok(second.success, `Second call failed: ${second.error}`);
const secondOutput = JSON.parse(second.output);
assert.strictEqual(secondOutput.created, false);