Skip to content

Commit 6606a11

Browse files
MoLowRafaelGSS
authored andcommitted
test: deflake watch mode tests
PR-URL: #44621 Fixes: #44655 Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 8b54f01 commit 6606a11

File tree

4 files changed

+225
-19
lines changed

4 files changed

+225
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
console.log('running');
2+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);
3+
console.log('don\'t show me');
4+

test/fixtures/watch-mode/graceful-sigterm.js

-17
This file was deleted.

test/fixtures/watch-mode/infinite-loop.js

-2
This file was deleted.

test/sequential/test-watch-mode.mjs

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import * as common from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import tmpdir from '../common/tmpdir.js';
4+
import assert from 'node:assert';
5+
import path from 'node:path';
6+
import { execPath } from 'node:process';
7+
import { describe, it } from 'node:test';
8+
import { spawn } from 'node:child_process';
9+
import { writeFileSync, readFileSync } from 'node:fs';
10+
import { inspect } from 'node:util';
11+
import { once } from 'node:events';
12+
13+
if (common.isIBMi)
14+
common.skip('IBMi does not support `fs.watch()`');
15+
16+
function restart(file) {
17+
// To avoid flakiness, we save the file repeatedly until test is done
18+
writeFileSync(file, readFileSync(file));
19+
const timer = setInterval(() => writeFileSync(file, readFileSync(file)), 1000);
20+
return () => clearInterval(timer);
21+
}
22+
23+
async function spawnWithRestarts({
24+
args,
25+
file,
26+
watchedFile = file,
27+
restarts = 1,
28+
isReady,
29+
}) {
30+
args ??= [file];
31+
const printedArgs = inspect(args.slice(args.indexOf(file)).join(' '));
32+
isReady ??= (data) => Boolean(data.match(new RegExp(`(Failed|Completed) running ${printedArgs.replace(/\\/g, '\\\\')}`, 'g'))?.length);
33+
34+
let stderr = '';
35+
let stdout = '';
36+
let cancelRestarts;
37+
38+
const child = spawn(execPath, ['--watch', '--no-warnings', ...args], { encoding: 'utf8' });
39+
child.stderr.on('data', (data) => {
40+
stderr += data;
41+
});
42+
child.stdout.on('data', async (data) => {
43+
stdout += data;
44+
const restartsCount = stdout.match(new RegExp(`Restarting ${printedArgs.replace(/\\/g, '\\\\')}`, 'g'))?.length ?? 0;
45+
if (restarts === 0 || !isReady(data.toString())) {
46+
return;
47+
}
48+
if (restartsCount >= restarts) {
49+
cancelRestarts?.();
50+
child.kill();
51+
return;
52+
}
53+
cancelRestarts ??= restart(watchedFile);
54+
});
55+
56+
await once(child, 'exit');
57+
cancelRestarts?.();
58+
return { stderr, stdout };
59+
}
60+
61+
let tmpFiles = 0;
62+
function createTmpFile(content = 'console.log("running");') {
63+
const file = path.join(tmpdir.path, `${tmpFiles++}.js`);
64+
writeFileSync(file, content);
65+
return file;
66+
}
67+
68+
function assertRestartedCorrectly({ stdout, messages: { inner, completed, restarted } }) {
69+
const lines = stdout.split(/\r?\n/).filter(Boolean);
70+
71+
const start = [inner, completed, restarted].filter(Boolean);
72+
const end = [restarted, inner, completed].filter(Boolean);
73+
assert.deepStrictEqual(lines.slice(0, start.length), start);
74+
assert.deepStrictEqual(lines.slice(-end.length), end);
75+
}
76+
77+
tmpdir.refresh();
78+
79+
// Warning: this suite can run safely with concurrency: true
80+
// only if tests do not watch/depend on the same files
81+
describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => {
82+
it('should watch changes to a file - event loop ended', async () => {
83+
const file = createTmpFile();
84+
const { stderr, stdout } = await spawnWithRestarts({ file });
85+
86+
assert.strictEqual(stderr, '');
87+
assertRestartedCorrectly({
88+
stdout,
89+
messages: { inner: 'running', completed: `Completed running ${inspect(file)}`, restarted: `Restarting ${inspect(file)}` },
90+
});
91+
});
92+
93+
it('should watch changes to a failing file', async () => {
94+
const file = fixtures.path('watch-mode/failing.js');
95+
const { stderr, stdout } = await spawnWithRestarts({ file });
96+
97+
assert.match(stderr, /Error: fails\r?\n/);
98+
assert.strictEqual(stderr.match(/Error: fails\r?\n/g).length, 2);
99+
assertRestartedCorrectly({
100+
stdout,
101+
messages: { completed: `Failed running ${inspect(file)}`, restarted: `Restarting ${inspect(file)}` },
102+
});
103+
});
104+
105+
it('should not watch when running an non-existing file', async () => {
106+
const file = fixtures.path('watch-mode/non-existing.js');
107+
const { stderr, stdout } = await spawnWithRestarts({ file, restarts: 0 });
108+
109+
assert.match(stderr, /code: 'MODULE_NOT_FOUND'/);
110+
assert.strictEqual(stdout, `Failed running ${inspect(file)}\n`);
111+
});
112+
113+
it('should watch when running an non-existing file - when specified under --watch-path', {
114+
skip: !common.isOSX && !common.isWindows
115+
}, async () => {
116+
const file = fixtures.path('watch-mode/subdir/non-existing.js');
117+
const watchedFile = fixtures.path('watch-mode/subdir/file.js');
118+
const { stderr, stdout } = await spawnWithRestarts({
119+
file,
120+
watchedFile,
121+
args: ['--watch-path', fixtures.path('./watch-mode/subdir/'), file],
122+
});
123+
124+
assert.strictEqual(stderr, '');
125+
assertRestartedCorrectly({
126+
stdout,
127+
messages: { completed: `Failed running ${inspect(file)}`, restarted: `Restarting ${inspect(file)}` },
128+
});
129+
});
130+
131+
it('should watch changes to a file - event loop blocked', async () => {
132+
const file = fixtures.path('watch-mode/event_loop_blocked.js');
133+
const { stderr, stdout } = await spawnWithRestarts({
134+
file,
135+
isReady: (data) => data.startsWith('running'),
136+
});
137+
138+
assert.strictEqual(stderr, '');
139+
assertRestartedCorrectly({
140+
stdout,
141+
messages: { inner: 'running', restarted: `Restarting ${inspect(file)}` },
142+
});
143+
});
144+
145+
it('should watch changes to dependencies - cjs', async () => {
146+
const file = fixtures.path('watch-mode/dependant.js');
147+
const dependency = fixtures.path('watch-mode/dependency.js');
148+
const { stderr, stdout } = await spawnWithRestarts({
149+
file,
150+
watchedFile: dependency,
151+
});
152+
153+
assert.strictEqual(stderr, '');
154+
assertRestartedCorrectly({
155+
stdout,
156+
messages: { inner: '{}', restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` },
157+
});
158+
});
159+
160+
it('should watch changes to dependencies - esm', async () => {
161+
const file = fixtures.path('watch-mode/dependant.mjs');
162+
const dependency = fixtures.path('watch-mode/dependency.mjs');
163+
const { stderr, stdout } = await spawnWithRestarts({
164+
file,
165+
watchedFile: dependency,
166+
});
167+
168+
assert.strictEqual(stderr, '');
169+
assertRestartedCorrectly({
170+
stdout,
171+
messages: { inner: '{}', restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` },
172+
});
173+
});
174+
175+
it('should restart multiple times', async () => {
176+
const file = createTmpFile();
177+
const { stderr, stdout } = await spawnWithRestarts({ file, restarts: 3 });
178+
179+
assert.strictEqual(stderr, '');
180+
assert.strictEqual(stdout.match(new RegExp(`Restarting ${inspect(file).replace(/\\/g, '\\\\')}`, 'g')).length, 3);
181+
});
182+
183+
it('should pass arguments to file', async () => {
184+
const file = fixtures.path('watch-mode/parse_args.js');
185+
const random = Date.now().toString();
186+
const args = [file, '--random', random];
187+
const { stderr, stdout } = await spawnWithRestarts({ file, args });
188+
189+
assert.strictEqual(stderr, '');
190+
assertRestartedCorrectly({
191+
stdout,
192+
messages: { inner: random, restarted: `Restarting ${inspect(args.join(' '))}`, completed: `Completed running ${inspect(args.join(' '))}` },
193+
});
194+
});
195+
196+
it('should not load --require modules in main process', async () => {
197+
const file = createTmpFile('');
198+
const required = fixtures.path('watch-mode/process_exit.js');
199+
const args = ['--require', required, file];
200+
const { stderr, stdout } = await spawnWithRestarts({ file, args });
201+
202+
assert.strictEqual(stderr, '');
203+
assertRestartedCorrectly({
204+
stdout,
205+
messages: { restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` },
206+
});
207+
});
208+
209+
it('should not load --import modules in main process', async () => {
210+
const file = createTmpFile('');
211+
const imported = fixtures.fileURL('watch-mode/process_exit.js');
212+
const args = ['--import', imported, file];
213+
const { stderr, stdout } = await spawnWithRestarts({ file, args });
214+
215+
assert.strictEqual(stderr, '');
216+
assertRestartedCorrectly({
217+
stdout,
218+
messages: { restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` },
219+
});
220+
});
221+
});

0 commit comments

Comments
 (0)