Skip to content

Commit 375e39a

Browse files
committed
test: refactor enforce-timeout
1 parent 524cb77 commit 375e39a

File tree

2 files changed

+81
-39
lines changed

2 files changed

+81
-39
lines changed

tests/specs/watch.ts

+19-20
Original file line numberDiff line numberDiff line change
@@ -323,37 +323,36 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
323323

324324
const negativeSignal = 'fail';
325325

326-
await processInteract(
327-
tsxProcess.stdout!,
328-
[
329-
async (data) => {
330-
if (data.includes(negativeSignal)) {
331-
throw new Error('should not log ignored file');
332-
}
326+
await expect(
327+
processInteract(
328+
tsxProcess.stdout!,
329+
[
330+
async (data) => {
331+
if (data !== 'logA logB logC\n') {
332+
return;
333+
}
333334

334-
if (data === 'logA logB logC\n') {
335335
// These changes should not trigger a re-run
336336
await Promise.all([
337337
fixtureGlob.writeFile(fileA, `export default "${negativeSignal}"`),
338338
fixtureGlob.writeFile(fileB, `export default "${negativeSignal}"`),
339339
fixtureGlob.writeFile(depA, `export default "${negativeSignal}"`),
340340
]);
341-
342-
await setTimeout(1000);
343-
fixtureGlob.writeFile(entryFile, 'console.log("TERMINATE")');
344341
return true;
345-
}
346-
},
347-
data => data === 'TERMINATE\n',
348-
],
349-
9000,
350-
);
342+
},
343+
(data) => {
344+
if (data.includes(negativeSignal)) {
345+
throw new Error('Unexpected re-run');
346+
}
347+
},
348+
],
349+
2000,
350+
),
351+
).rejects.toThrow('Timeout'); // Watch should not trigger
351352

352353
tsxProcess.kill();
353354

354-
const p = await tsxProcess;
355-
expect(p.all).not.toMatch('fail');
356-
expect(p.stderr).toBe('');
355+
await tsxProcess;
357356
}, 10_000);
358357
});
359358
});

tests/utils/process-interact.ts

+62-19
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,80 @@ import type { Readable } from 'node:stream';
22
import { on } from 'node:events';
33
import { setTimeout } from 'node:timers/promises';
44

5-
type MaybePromise<T> = T | Promise<T>;
5+
type OnTimeoutCallback = () => void;
66

7-
export const processInteract = async (
8-
stdout: Readable,
9-
actions: ((data: string) => MaybePromise<boolean | void>)[],
7+
type Api = {
8+
startTime: number;
9+
onTimeout: (callback: OnTimeoutCallback) => void;
10+
};
11+
12+
const enforceTimeout = <ReturnType>(
1013
timeout: number,
11-
) => {
14+
function_: (api: Api) => ReturnType,
15+
): ReturnType => {
1216
const startTime = Date.now();
13-
const logs: [time: number, string][] = [];
17+
let onTimeoutCallback: OnTimeoutCallback;
1418

15-
let currentAction = actions.shift();
19+
const runFunction = function_({
20+
startTime,
21+
onTimeout: (callback) => {
22+
onTimeoutCallback = callback;
23+
},
24+
});
25+
26+
if (!(runFunction instanceof Promise)) {
27+
return runFunction;
28+
}
1629

1730
const ac = new AbortController();
18-
setTimeout(timeout, true, ac).then(
19-
() => {
20-
if (currentAction) {
21-
console.error(`Timeout ${timeout}ms exceeded:`);
22-
console.log(logs);
31+
const timer = setTimeout(timeout, true, ac).then(
32+
async () => {
33+
if (onTimeoutCallback) {
34+
await onTimeoutCallback();
2335
}
36+
37+
throw new Error('Timeout');
2438
},
25-
() => {},
39+
() => { /* Timeout aborted */ },
2640
);
2741

42+
return Promise.race([
43+
runFunction.finally(() => ac.abort()),
44+
timer,
45+
]) as ReturnType;
46+
};
47+
48+
type MaybePromise<T> = T | Promise<T>;
49+
50+
export const processInteract = async (
51+
stdout: Readable,
52+
actions: ((data: string) => MaybePromise<boolean | void>)[],
53+
timeout: number,
54+
) => enforceTimeout(timeout, async ({ startTime, onTimeout }) => {
55+
const logs: {
56+
time: number;
57+
stdout: string;
58+
}[] = [];
59+
60+
let currentAction = actions.shift();
61+
62+
onTimeout(() => {
63+
if (currentAction) {
64+
const error = Object.assign(
65+
new Error(`Timeout ${timeout}ms exceeded:`),
66+
{ logs },
67+
);
68+
throw error;
69+
}
70+
});
71+
2872
while (currentAction) {
2973
for await (const [chunk] of on(stdout, 'data')) {
3074
const chunkString = chunk.toString();
31-
logs.push([
32-
Date.now() - startTime,
33-
chunkString,
34-
]);
75+
logs.push({
76+
time: Date.now() - startTime,
77+
stdout: chunkString,
78+
});
3579

3680
const gotoNextAction = await currentAction(chunkString);
3781
if (gotoNextAction) {
@@ -40,5 +84,4 @@ export const processInteract = async (
4084
}
4185
}
4286
}
43-
ac.abort();
44-
};
87+
});

0 commit comments

Comments
 (0)