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 8c31cbb

Browse filesBrowse files
joyeecheungaduh95
authored andcommitted
test: move test-runner-watch-mode helper into common
PR-URL: #60391 Refs: #49605 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent 3671851 commit 8c31cbb
Copy full SHA for 8c31cbb

File tree

Expand file treeCollapse file tree

5 files changed

+176
-161
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+176
-161
lines changed
Open diff view settings
Collapse file

‎test/common/watch.js‎

Copy file name to clipboardExpand all lines: test/common/watch.js
+158-1Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
'use strict';
22
const common = require('./index.js');
3+
const tmpdir = require('./tmpdir.js');
4+
const fixtures = require('./fixtures.js');
5+
const { writeFileSync, readdirSync, readFileSync, renameSync, unlinkSync } = require('node:fs');
6+
const { spawn } = require('node:child_process');
7+
const { once } = require('node:events');
8+
const assert = require('node:assert');
9+
const { setTimeout } = require('node:timers/promises');
310

4-
exports.skipIfNoWatchModeSignals = function() {
11+
function skipIfNoWatch() {
12+
if (common.isIBMi) {
13+
common.skip('IBMi does not support `fs.watch()`');
14+
}
15+
16+
if (common.isAIX) {
17+
common.skip('folder watch capability is limited in AIX.');
18+
}
19+
}
20+
21+
function skipIfNoWatchModeSignals() {
522
if (common.isWindows) {
623
common.skip('no signals on Windows');
724
}
@@ -13,4 +30,144 @@ exports.skipIfNoWatchModeSignals = function() {
1330
if (common.isAIX) {
1431
common.skip('folder watch capability is limited in AIX.');
1532
}
33+
}
34+
35+
const fixturePaths = {};
36+
const fixtureContent = {};
37+
38+
function refreshForTestRunnerWatch() {
39+
tmpdir.refresh();
40+
const files = readdirSync(fixtures.path('test-runner-watch'));
41+
for (const file of files) {
42+
const src = fixtures.path('test-runner-watch', file);
43+
const dest = tmpdir.resolve(file);
44+
fixturePaths[file] = dest;
45+
fixtureContent[file] = readFileSync(src, 'utf8');
46+
writeFileSync(dest, fixtureContent[file]);
47+
}
48+
}
49+
50+
async function testRunnerWatch({
51+
fileToUpdate,
52+
file,
53+
action = 'update',
54+
fileToCreate,
55+
isolation,
56+
}) {
57+
const ran1 = Promise.withResolvers();
58+
const ran2 = Promise.withResolvers();
59+
const child = spawn(process.execPath,
60+
['--watch', '--test', '--test-reporter=spec',
61+
isolation ? `--test-isolation=${isolation}` : '',
62+
file ? fixturePaths[file] : undefined].filter(Boolean),
63+
{ encoding: 'utf8', stdio: ['inherit', 'pipe', 'inherit'], cwd: tmpdir.path });
64+
let stdout = '';
65+
let currentRun = '';
66+
const runs = [];
67+
68+
child.stdout.on('data', (data) => {
69+
stdout += data.toString();
70+
currentRun += data.toString();
71+
const testRuns = stdout.match(/duration_ms\s\d+/g);
72+
if (testRuns?.length >= 1) ran1.resolve();
73+
if (testRuns?.length >= 2) ran2.resolve();
74+
});
75+
76+
const testUpdate = async () => {
77+
await ran1.promise;
78+
runs.push(currentRun);
79+
currentRun = '';
80+
const content = fixtureContent[fileToUpdate];
81+
const path = fixturePaths[fileToUpdate];
82+
writeFileSync(path, content);
83+
await setTimeout(common.platformTimeout(1000));
84+
await ran2.promise;
85+
runs.push(currentRun);
86+
child.kill();
87+
await once(child, 'exit');
88+
89+
assert.strictEqual(runs.length, 2);
90+
91+
for (const run of runs) {
92+
assert.match(run, /tests 1/);
93+
assert.match(run, /pass 1/);
94+
assert.match(run, /fail 0/);
95+
assert.match(run, /cancelled 0/);
96+
}
97+
};
98+
99+
const testRename = async () => {
100+
await ran1.promise;
101+
runs.push(currentRun);
102+
currentRun = '';
103+
const fileToRenamePath = tmpdir.resolve(fileToUpdate);
104+
const newFileNamePath = tmpdir.resolve(`test-renamed-${fileToUpdate}`);
105+
renameSync(fileToRenamePath, newFileNamePath);
106+
await setTimeout(common.platformTimeout(1000));
107+
await ran2.promise;
108+
runs.push(currentRun);
109+
child.kill();
110+
await once(child, 'exit');
111+
112+
assert.strictEqual(runs.length, 2);
113+
114+
for (const run of runs) {
115+
assert.match(run, /tests 1/);
116+
assert.match(run, /pass 1/);
117+
assert.match(run, /fail 0/);
118+
assert.match(run, /cancelled 0/);
119+
}
120+
};
121+
122+
const testDelete = async () => {
123+
await ran1.promise;
124+
runs.push(currentRun);
125+
currentRun = '';
126+
const fileToDeletePath = tmpdir.resolve(fileToUpdate);
127+
unlinkSync(fileToDeletePath);
128+
await setTimeout(common.platformTimeout(2000));
129+
ran2.resolve();
130+
runs.push(currentRun);
131+
child.kill();
132+
await once(child, 'exit');
133+
134+
assert.strictEqual(runs.length, 2);
135+
136+
for (const run of runs) {
137+
assert.doesNotMatch(run, /MODULE_NOT_FOUND/);
138+
}
139+
};
140+
141+
const testCreate = async () => {
142+
await ran1.promise;
143+
runs.push(currentRun);
144+
currentRun = '';
145+
const newFilePath = tmpdir.resolve(fileToCreate);
146+
writeFileSync(newFilePath, 'module.exports = {};');
147+
await setTimeout(common.platformTimeout(1000));
148+
await ran2.promise;
149+
runs.push(currentRun);
150+
child.kill();
151+
await once(child, 'exit');
152+
153+
for (const run of runs) {
154+
assert.match(run, /tests 1/);
155+
assert.match(run, /pass 1/);
156+
assert.match(run, /fail 0/);
157+
assert.match(run, /cancelled 0/);
158+
}
159+
};
160+
161+
action === 'update' && await testUpdate();
162+
action === 'rename' && await testRename();
163+
action === 'delete' && await testDelete();
164+
action === 'create' && await testCreate();
165+
}
166+
167+
168+
module.exports = {
169+
skipIfNoWatch,
170+
skipIfNoWatchModeSignals,
171+
testRunnerWatch,
172+
refreshForTestRunnerWatch,
16173
};
Collapse file
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};
Collapse file
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1;
Collapse file
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const test = require('node:test');
2+
require('./dependency.js');
3+
import('./dependency.mjs');
4+
import('data:text/javascript,');
5+
test('test has ran');
Collapse file

‎test/parallel/test-runner-watch-mode.mjs‎

Copy file name to clipboardExpand all lines: test/parallel/test-runner-watch-mode.mjs
+11-160Lines changed: 11 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,43 @@
1-
import * as common from '../common/index.mjs';
1+
import '../common/index.mjs';
2+
import { skipIfNoWatch, refreshForTestRunnerWatch, testRunnerWatch } from '../common/watch.js';
23
import { describe, it, beforeEach } from 'node:test';
3-
import { once } from 'node:events';
4-
import assert from 'node:assert';
5-
import { spawn } from 'node:child_process';
6-
import { writeFileSync, renameSync, unlinkSync } from 'node:fs';
7-
import { setTimeout } from 'node:timers/promises';
8-
import tmpdir from '../common/tmpdir.js';
94

10-
if (common.isIBMi)
11-
common.skip('IBMi does not support `fs.watch()`');
12-
13-
if (common.isAIX)
14-
common.skip('folder watch capability is limited in AIX.');
15-
16-
let fixturePaths;
17-
18-
// This test updates these files repeatedly,
19-
// Reading them from disk is unreliable due to race conditions.
20-
const fixtureContent = {
21-
'dependency.js': 'module.exports = {};',
22-
'dependency.mjs': 'export const a = 1;',
23-
'test.js': `
24-
const test = require('node:test');
25-
require('./dependency.js');
26-
import('./dependency.mjs');
27-
import('data:text/javascript,');
28-
test('test has ran');`,
29-
};
30-
31-
function refresh() {
32-
tmpdir.refresh();
33-
fixturePaths = Object.keys(fixtureContent)
34-
.reduce((acc, file) => ({ ...acc, [file]: tmpdir.resolve(file) }), {});
35-
Object.entries(fixtureContent)
36-
.forEach(([file, content]) => writeFileSync(fixturePaths[file], content));
37-
}
38-
39-
async function testWatch({
40-
fileToUpdate,
41-
file,
42-
action = 'update',
43-
fileToCreate,
44-
isolation,
45-
}) {
46-
const ran1 = Promise.withResolvers();
47-
const ran2 = Promise.withResolvers();
48-
const child = spawn(process.execPath,
49-
['--watch', '--test', '--test-reporter=spec',
50-
isolation ? `--test-isolation=${isolation}` : '',
51-
file ? fixturePaths[file] : undefined].filter(Boolean),
52-
{ encoding: 'utf8', stdio: 'pipe', cwd: tmpdir.path });
53-
let stdout = '';
54-
let currentRun = '';
55-
const runs = [];
56-
57-
child.stdout.on('data', (data) => {
58-
stdout += data.toString();
59-
currentRun += data.toString();
60-
const testRuns = stdout.match(/duration_ms\s\d+/g);
61-
if (testRuns?.length >= 1) ran1.resolve();
62-
if (testRuns?.length >= 2) ran2.resolve();
63-
});
64-
65-
const testUpdate = async () => {
66-
await ran1.promise;
67-
runs.push(currentRun);
68-
currentRun = '';
69-
const content = fixtureContent[fileToUpdate];
70-
const path = fixturePaths[fileToUpdate];
71-
writeFileSync(path, content);
72-
await setTimeout(common.platformTimeout(1000));
73-
await ran2.promise;
74-
runs.push(currentRun);
75-
child.kill();
76-
await once(child, 'exit');
77-
78-
assert.strictEqual(runs.length, 2);
79-
80-
for (const run of runs) {
81-
assert.match(run, /tests 1/);
82-
assert.match(run, /pass 1/);
83-
assert.match(run, /fail 0/);
84-
assert.match(run, /cancelled 0/);
85-
}
86-
};
87-
88-
const testRename = async () => {
89-
await ran1.promise;
90-
runs.push(currentRun);
91-
currentRun = '';
92-
const fileToRenamePath = tmpdir.resolve(fileToUpdate);
93-
const newFileNamePath = tmpdir.resolve(`test-renamed-${fileToUpdate}`);
94-
renameSync(fileToRenamePath, newFileNamePath);
95-
await setTimeout(common.platformTimeout(1000));
96-
await ran2.promise;
97-
runs.push(currentRun);
98-
child.kill();
99-
await once(child, 'exit');
100-
101-
assert.strictEqual(runs.length, 2);
102-
103-
for (const run of runs) {
104-
assert.match(run, /tests 1/);
105-
assert.match(run, /pass 1/);
106-
assert.match(run, /fail 0/);
107-
assert.match(run, /cancelled 0/);
108-
}
109-
};
110-
111-
const testDelete = async () => {
112-
await ran1.promise;
113-
runs.push(currentRun);
114-
currentRun = '';
115-
const fileToDeletePath = tmpdir.resolve(fileToUpdate);
116-
unlinkSync(fileToDeletePath);
117-
await setTimeout(common.platformTimeout(2000));
118-
ran2.resolve();
119-
runs.push(currentRun);
120-
child.kill();
121-
await once(child, 'exit');
122-
123-
assert.strictEqual(runs.length, 2);
124-
125-
for (const run of runs) {
126-
assert.doesNotMatch(run, /MODULE_NOT_FOUND/);
127-
}
128-
};
129-
130-
const testCreate = async () => {
131-
await ran1.promise;
132-
runs.push(currentRun);
133-
currentRun = '';
134-
const newFilePath = tmpdir.resolve(fileToCreate);
135-
writeFileSync(newFilePath, 'module.exports = {};');
136-
await setTimeout(common.platformTimeout(1000));
137-
await ran2.promise;
138-
runs.push(currentRun);
139-
child.kill();
140-
await once(child, 'exit');
141-
142-
for (const run of runs) {
143-
assert.match(run, /tests 1/);
144-
assert.match(run, /pass 1/);
145-
assert.match(run, /fail 0/);
146-
assert.match(run, /cancelled 0/);
147-
}
148-
};
149-
150-
action === 'update' && await testUpdate();
151-
action === 'rename' && await testRename();
152-
action === 'delete' && await testDelete();
153-
action === 'create' && await testCreate();
154-
}
5+
skipIfNoWatch();
1556

1567
describe('test runner watch mode', () => {
157-
beforeEach(refresh);
8+
beforeEach(refreshForTestRunnerWatch);
1589
for (const isolation of ['none', 'process']) {
15910
describe(`isolation: ${isolation}`, () => {
16011
it('should run tests repeatedly', async () => {
161-
await testWatch({ file: 'test.js', fileToUpdate: 'test.js', isolation });
12+
await testRunnerWatch({ file: 'test.js', fileToUpdate: 'test.js', isolation });
16213
});
16314

16415
it('should run tests with dependency repeatedly', async () => {
165-
await testWatch({ file: 'test.js', fileToUpdate: 'dependency.js', isolation });
16+
await testRunnerWatch({ file: 'test.js', fileToUpdate: 'dependency.js', isolation });
16617
});
16718

16819
it('should run tests with ESM dependency', async () => {
169-
await testWatch({ file: 'test.js', fileToUpdate: 'dependency.mjs', isolation });
20+
await testRunnerWatch({ file: 'test.js', fileToUpdate: 'dependency.mjs', isolation });
17021
});
17122

17223
it('should support running tests without a file', async () => {
173-
await testWatch({ fileToUpdate: 'test.js', isolation });
24+
await testRunnerWatch({ fileToUpdate: 'test.js', isolation });
17425
});
17526

17627
it('should support a watched test file rename', async () => {
177-
await testWatch({ fileToUpdate: 'test.js', action: 'rename', isolation });
28+
await testRunnerWatch({ fileToUpdate: 'test.js', action: 'rename', isolation });
17829
});
17930

18031
it('should not throw when delete a watched test file', async () => {
181-
await testWatch({ fileToUpdate: 'test.js', action: 'delete', isolation });
32+
await testRunnerWatch({ fileToUpdate: 'test.js', action: 'delete', isolation });
18233
});
18334

18435
it('should run new tests when a new file is created in the watched directory', {
18536
todo: isolation === 'none' ?
18637
'This test is failing when isolation is set to none and must be fixed' :
18738
undefined,
18839
}, async () => {
189-
await testWatch({ action: 'create', fileToCreate: 'new-test-file.test.js', isolation });
40+
await testRunnerWatch({ action: 'create', fileToCreate: 'new-test-file.test.js', isolation });
19041
});
19142
});
19243
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.