TauriTavern/tests/generation-single-flight.test.mjs

136 lines
3.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
async function importFresh(modulePath) {
const url = `${pathToFileURL(modulePath).href}?t=${Date.now()}-${Math.random()}`;
return import(url);
}
function nextMicrotask() {
return Promise.resolve();
}
function nextTick() {
return new Promise((resolve) => {
setImmediate(resolve);
});
}
test('generation idle gate blocks until markIdle()', async () => {
const { createGenerationIdleGate } = await importFresh(
path.join(REPO_ROOT, 'src/scripts/util/generation-idle-gate.js'),
);
const gate = createGenerationIdleGate();
await gate.wait();
gate.markBusy();
const pending = gate.wait();
let resolved = false;
pending.then(() => {
resolved = true;
});
await nextMicrotask();
assert.equal(resolved, false);
gate.markBusy();
assert.equal(gate.wait(), pending);
gate.markIdle();
await pending;
assert.equal(resolved, true);
gate.markIdle();
await gate.wait();
});
test('safe generate waits for idle and serializes callers', async () => {
const { createGenerationIdleGate } = await importFresh(
path.join(REPO_ROOT, 'src/scripts/util/generation-idle-gate.js'),
);
const { createSafeGenerate } = await importFresh(
path.join(REPO_ROOT, 'src/scripts/util/safe-generate.js'),
);
const gate = createGenerationIdleGate();
gate.markBusy();
const calls = [];
const generate = async (...args) => {
calls.push(args);
return args.join(':');
};
const safeGenerate = createSafeGenerate({
waitForIdle: gate.wait,
generate,
});
const run = safeGenerate('regenerate');
await nextMicrotask();
assert.equal(calls.length, 0);
gate.markIdle();
const result = await run;
assert.deepEqual(calls, [['regenerate']]);
assert.equal(result, 'regenerate');
const started = [];
const resolvers = [];
const slowGenerate = (label) => {
started.push(label);
return new Promise((resolve) => {
resolvers.push(resolve);
});
};
const safeSlowGenerate = createSafeGenerate({
waitForIdle: gate.wait,
generate: slowGenerate,
});
const first = safeSlowGenerate('first');
await nextTick();
assert.deepEqual(started, ['first']);
const second = safeSlowGenerate('second');
await nextTick();
assert.deepEqual(started, ['first']);
resolvers[0]('ok-1');
await nextTick();
assert.deepEqual(started, ['first', 'second']);
resolvers[1]('ok-2');
assert.equal(await first, 'ok-1');
assert.equal(await second, 'ok-2');
});
test('safe generate does not stall after a rejected run', async () => {
const { createSafeGenerate } = await importFresh(
path.join(REPO_ROOT, 'src/scripts/util/safe-generate.js'),
);
let callCount = 0;
const generate = async () => {
callCount += 1;
if (callCount === 1) {
throw new Error('boom');
}
return 'ok';
};
const safeGenerate = createSafeGenerate({
waitForIdle: () => Promise.resolve(),
generate,
});
await assert.rejects(() => safeGenerate('first'), /boom/);
assert.equal(await safeGenerate('second'), 'ok');
});