Skip to content

Commit 75af9c2

Browse files
chore: fix test failures on mf-layers (#12992)
test: retry native watcher watch steps Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
1 parent 12fdb20 commit 75af9c2

1 file changed

Lines changed: 63 additions & 10 deletions

File tree

  • packages/rspack-test-tools/src/case

packages/rspack-test-tools/src/case/watch.ts

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { WebRunner } from '../runner';
1010
import { BasicCaseCreator } from '../test/creator';
1111
import type {
1212
IModuleScope,
13+
ITestCompilerManager,
1314
ITestContext,
1415
ITestEnv,
1516
ITestRunner,
@@ -25,6 +26,46 @@ type TWatchContext = {
2526
watchState: Record<string, any>;
2627
};
2728

29+
const WATCH_BUILD_TIMEOUT_CODE = 'WATCH_BUILD_TIMEOUT';
30+
31+
function waitForBuildEvent(
32+
compiler: ITestCompilerManager,
33+
timeoutMs?: number,
34+
): Promise<unknown> {
35+
return new Promise((resolve, reject) => {
36+
const emitter = compiler.getEmitter();
37+
let timeout: ReturnType<typeof setTimeout> | undefined;
38+
const onBuild = (error: Error | null, stats: unknown) => {
39+
if (timeout) {
40+
clearTimeout(timeout);
41+
}
42+
emitter.off(ECompilerEvent.Build, onBuild);
43+
if (error) {
44+
reject(error);
45+
} else {
46+
resolve(stats);
47+
}
48+
};
49+
emitter.on(ECompilerEvent.Build, onBuild);
50+
if (typeof timeoutMs === 'number') {
51+
timeout = setTimeout(() => {
52+
emitter.off(ECompilerEvent.Build, onBuild);
53+
const err = new Error(`Watch build timeout after ${timeoutMs}ms`);
54+
(err as NodeJS.ErrnoException).code = WATCH_BUILD_TIMEOUT_CODE;
55+
reject(err);
56+
}, timeoutMs);
57+
}
58+
});
59+
}
60+
61+
function isWatchBuildTimeout(error: unknown) {
62+
return (
63+
!!error &&
64+
typeof error === 'object' &&
65+
(error as NodeJS.ErrnoException).code === WATCH_BUILD_TIMEOUT_CODE
66+
);
67+
}
68+
2869
export function createWatchInitialProcessor(
2970
name: string,
3071
tempDir: string,
@@ -286,12 +327,6 @@ export function createWatchStepProcessor(
286327
};
287328
processor.build = async (context: ITestContext) => {
288329
const compiler = context.getCompiler();
289-
const task = new Promise((resolve, reject) => {
290-
compiler.getEmitter().once(ECompilerEvent.Build, (e, stats) => {
291-
if (e) return reject(e);
292-
resolve(stats);
293-
});
294-
});
295330
// wait compiler to ready watch the files and diretories
296331

297332
// Native Watcher using [notify](https://github.com/notify-rs/notify) to watch files.
@@ -301,10 +336,28 @@ export function createWatchStepProcessor(
301336
// which will cause the compiler not rebuild when the files change.
302337
// The timeout is set to 400ms for windows OS and 100ms for other OS.
303338
// TODO: This is a workaround, we can remove it when notify support windows better.
304-
const timeout = nativeWatcher && process.platform === 'win32' ? 400 : 100;
305-
await new Promise((resolve) => setTimeout(resolve, timeout));
306-
copyDiff(path.join(context.getSource(), step), tempDir, false);
307-
await task;
339+
const readyDelay =
340+
nativeWatcher && process.platform === 'win32' ? 400 : 100;
341+
const buildTimeout = nativeWatcher ? 10000 : undefined;
342+
const maxRetries = nativeWatcher ? 1 : 0;
343+
const retryDelay = nativeWatcher ? 250 : 0;
344+
345+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
346+
const task = waitForBuildEvent(compiler, buildTimeout);
347+
const delay = attempt === 0 ? readyDelay : retryDelay;
348+
if (delay > 0) {
349+
await new Promise((resolve) => setTimeout(resolve, delay));
350+
}
351+
copyDiff(path.join(context.getSource(), step), tempDir, false);
352+
try {
353+
await task;
354+
break;
355+
} catch (error) {
356+
if (!isWatchBuildTimeout(error) || attempt === maxRetries) {
357+
throw error;
358+
}
359+
}
360+
}
308361
};
309362
return processor;
310363
}

0 commit comments

Comments
 (0)