test: move test-runner-watch-mode helper into common · nodejs/node@8c31cbb
1-import * as common from '../common/index.mjs';
1+import '../common/index.mjs';
2+import { skipIfNoWatch, refreshForTestRunnerWatch, testRunnerWatch } from '../common/watch.js';
23import { 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';
9410-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();
15561567describe('test runner watch mode', () => {
157-beforeEach(refresh);
8+beforeEach(refreshForTestRunnerWatch);
1589for (const isolation of ['none', 'process']) {
15910describe(`isolation: ${isolation}`, () => {
16011it('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});
1631416415it('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});
1671816819it('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});
1712217223it('should support running tests without a file', async () => {
173-await testWatch({ fileToUpdate: 'test.js', isolation });
24+await testRunnerWatch({ fileToUpdate: 'test.js', isolation });
17425});
1752617627it('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});
1793018031it('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});
1833418435it('should run new tests when a new file is created in the watched directory', {
18536todo: isolation === 'none' ?
18637'This test is failing when isolation is set to none and must be fixed' :
18738undefined,
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}