Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6606a11

Browse filesBrowse files
MoLowRafaelGSS
authored andcommitted
test: deflake watch mode tests
PR-URL: #44621 Fixes: #44655 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 8b54f01 commit 6606a11
Copy full SHA for 6606a11

File tree

Expand file treeCollapse file tree

4 files changed

+225
-19
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+225
-19
lines changed
Open diff view settings
Collapse file
+4Lines changed: 4 additions & 0 deletions
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+
Collapse file

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

Copy file name to clipboardExpand all lines: test/fixtures/watch-mode/graceful-sigterm.js
-17Lines changed: 0 additions & 17 deletions
This file was deleted.
Collapse file

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

Copy file name to clipboardExpand all lines: test/fixtures/watch-mode/infinite-loop.js
-2Lines changed: 0 additions & 2 deletions
This file was deleted.
Collapse file
+221Lines changed: 221 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.