Skip to content

Commit 8c31cbb

Browse 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

5 files changed

Lines changed: 176 additions & 161 deletions

File tree

‎test/common/watch.js‎

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
'use strict';
22
constcommon=require('./index.js');
3+
consttmpdir=require('./tmpdir.js');
4+
constfixtures=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+
constassert=require('node:assert');
9+
const{ setTimeout }=require('node:timers/promises');
310

4-
exports.skipIfNoWatchModeSignals=function(){
11+
functionskipIfNoWatch(){
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+
functionskipIfNoWatchModeSignals(){
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+
constfixturePaths={};
36+
constfixtureContent={};
37+
38+
functionrefreshForTestRunnerWatch(){
39+
tmpdir.refresh();
40+
constfiles=readdirSync(fixtures.path('test-runner-watch'));
41+
for(constfileoffiles){
42+
constsrc=fixtures.path('test-runner-watch',file);
43+
constdest=tmpdir.resolve(file);
44+
fixturePaths[file]=dest;
45+
fixtureContent[file]=readFileSync(src,'utf8');
46+
writeFileSync(dest,fixtureContent[file]);
47+
}
48+
}
49+
50+
asyncfunctiontestRunnerWatch({
51+
fileToUpdate,
52+
file,
53+
action ='update',
54+
fileToCreate,
55+
isolation,
56+
}){
57+
constran1=Promise.withResolvers();
58+
constran2=Promise.withResolvers();
59+
constchild=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+
letstdout='';
65+
letcurrentRun='';
66+
construns=[];
67+
68+
child.stdout.on('data',(data)=>{
69+
stdout+=data.toString();
70+
currentRun+=data.toString();
71+
consttestRuns=stdout.match(/duration_ms\s\d+/g);
72+
if(testRuns?.length>=1)ran1.resolve();
73+
if(testRuns?.length>=2)ran2.resolve();
74+
});
75+
76+
consttestUpdate=async()=>{
77+
awaitran1.promise;
78+
runs.push(currentRun);
79+
currentRun='';
80+
constcontent=fixtureContent[fileToUpdate];
81+
constpath=fixturePaths[fileToUpdate];
82+
writeFileSync(path,content);
83+
awaitsetTimeout(common.platformTimeout(1000));
84+
awaitran2.promise;
85+
runs.push(currentRun);
86+
child.kill();
87+
awaitonce(child,'exit');
88+
89+
assert.strictEqual(runs.length,2);
90+
91+
for(construnofruns){
92+
assert.match(run,/tests1/);
93+
assert.match(run,/pass1/);
94+
assert.match(run,/fail0/);
95+
assert.match(run,/cancelled0/);
96+
}
97+
};
98+
99+
consttestRename=async()=>{
100+
awaitran1.promise;
101+
runs.push(currentRun);
102+
currentRun='';
103+
constfileToRenamePath=tmpdir.resolve(fileToUpdate);
104+
constnewFileNamePath=tmpdir.resolve(`test-renamed-${fileToUpdate}`);
105+
renameSync(fileToRenamePath,newFileNamePath);
106+
awaitsetTimeout(common.platformTimeout(1000));
107+
awaitran2.promise;
108+
runs.push(currentRun);
109+
child.kill();
110+
awaitonce(child,'exit');
111+
112+
assert.strictEqual(runs.length,2);
113+
114+
for(construnofruns){
115+
assert.match(run,/tests1/);
116+
assert.match(run,/pass1/);
117+
assert.match(run,/fail0/);
118+
assert.match(run,/cancelled0/);
119+
}
120+
};
121+
122+
consttestDelete=async()=>{
123+
awaitran1.promise;
124+
runs.push(currentRun);
125+
currentRun='';
126+
constfileToDeletePath=tmpdir.resolve(fileToUpdate);
127+
unlinkSync(fileToDeletePath);
128+
awaitsetTimeout(common.platformTimeout(2000));
129+
ran2.resolve();
130+
runs.push(currentRun);
131+
child.kill();
132+
awaitonce(child,'exit');
133+
134+
assert.strictEqual(runs.length,2);
135+
136+
for(construnofruns){
137+
assert.doesNotMatch(run,/MODULE_NOT_FOUND/);
138+
}
139+
};
140+
141+
consttestCreate=async()=>{
142+
awaitran1.promise;
143+
runs.push(currentRun);
144+
currentRun='';
145+
constnewFilePath=tmpdir.resolve(fileToCreate);
146+
writeFileSync(newFilePath,'module.exports = {};');
147+
awaitsetTimeout(common.platformTimeout(1000));
148+
awaitran2.promise;
149+
runs.push(currentRun);
150+
child.kill();
151+
awaitonce(child,'exit');
152+
153+
for(construnofruns){
154+
assert.match(run,/tests1/);
155+
assert.match(run,/pass1/);
156+
assert.match(run,/fail0/);
157+
assert.match(run,/cancelled0/);
158+
}
159+
};
160+
161+
action==='update'&&awaittestUpdate();
162+
action==='rename'&&awaittestRename();
163+
action==='delete'&&awaittestDelete();
164+
action==='create'&&awaittestCreate();
165+
}
166+
167+
168+
module.exports={
169+
skipIfNoWatch,
170+
skipIfNoWatchModeSignals,
171+
testRunnerWatch,
172+
refreshForTestRunnerWatch,
16173
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports={};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exportconsta=1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
consttest=require('node:test');
2+
require('./dependency.js');
3+
import('./dependency.mjs');
4+
import('data:text/javascript,');
5+
test('test has ran');

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

Lines changed: 11 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,43 @@
1-
import*ascommonfrom'../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-
importassertfrom'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-
importtmpdirfrom'../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-
letfixturePaths;
17-
18-
// This test updates these files repeatedly,
19-
// Reading them from disk is unreliable due to race conditions.
20-
constfixtureContent={
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-
functionrefresh(){
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-
asyncfunctiontestWatch({
40-
fileToUpdate,
41-
file,
42-
action ='update',
43-
fileToCreate,
44-
isolation,
45-
}){
46-
constran1=Promise.withResolvers();
47-
constran2=Promise.withResolvers();
48-
constchild=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-
letstdout='';
54-
letcurrentRun='';
55-
construns=[];
56-
57-
child.stdout.on('data',(data)=>{
58-
stdout+=data.toString();
59-
currentRun+=data.toString();
60-
consttestRuns=stdout.match(/duration_ms\s\d+/g);
61-
if(testRuns?.length>=1)ran1.resolve();
62-
if(testRuns?.length>=2)ran2.resolve();
63-
});
64-
65-
consttestUpdate=async()=>{
66-
awaitran1.promise;
67-
runs.push(currentRun);
68-
currentRun='';
69-
constcontent=fixtureContent[fileToUpdate];
70-
constpath=fixturePaths[fileToUpdate];
71-
writeFileSync(path,content);
72-
awaitsetTimeout(common.platformTimeout(1000));
73-
awaitran2.promise;
74-
runs.push(currentRun);
75-
child.kill();
76-
awaitonce(child,'exit');
77-
78-
assert.strictEqual(runs.length,2);
79-
80-
for(construnofruns){
81-
assert.match(run,/tests1/);
82-
assert.match(run,/pass1/);
83-
assert.match(run,/fail0/);
84-
assert.match(run,/cancelled0/);
85-
}
86-
};
87-
88-
consttestRename=async()=>{
89-
awaitran1.promise;
90-
runs.push(currentRun);
91-
currentRun='';
92-
constfileToRenamePath=tmpdir.resolve(fileToUpdate);
93-
constnewFileNamePath=tmpdir.resolve(`test-renamed-${fileToUpdate}`);
94-
renameSync(fileToRenamePath,newFileNamePath);
95-
awaitsetTimeout(common.platformTimeout(1000));
96-
awaitran2.promise;
97-
runs.push(currentRun);
98-
child.kill();
99-
awaitonce(child,'exit');
100-
101-
assert.strictEqual(runs.length,2);
102-
103-
for(construnofruns){
104-
assert.match(run,/tests1/);
105-
assert.match(run,/pass1/);
106-
assert.match(run,/fail0/);
107-
assert.match(run,/cancelled0/);
108-
}
109-
};
110-
111-
consttestDelete=async()=>{
112-
awaitran1.promise;
113-
runs.push(currentRun);
114-
currentRun='';
115-
constfileToDeletePath=tmpdir.resolve(fileToUpdate);
116-
unlinkSync(fileToDeletePath);
117-
awaitsetTimeout(common.platformTimeout(2000));
118-
ran2.resolve();
119-
runs.push(currentRun);
120-
child.kill();
121-
awaitonce(child,'exit');
122-
123-
assert.strictEqual(runs.length,2);
124-
125-
for(construnofruns){
126-
assert.doesNotMatch(run,/MODULE_NOT_FOUND/);
127-
}
128-
};
129-
130-
consttestCreate=async()=>{
131-
awaitran1.promise;
132-
runs.push(currentRun);
133-
currentRun='';
134-
constnewFilePath=tmpdir.resolve(fileToCreate);
135-
writeFileSync(newFilePath,'module.exports = {};');
136-
awaitsetTimeout(common.platformTimeout(1000));
137-
awaitran2.promise;
138-
runs.push(currentRun);
139-
child.kill();
140-
awaitonce(child,'exit');
141-
142-
for(construnofruns){
143-
assert.match(run,/tests1/);
144-
assert.match(run,/pass1/);
145-
assert.match(run,/fail0/);
146-
assert.match(run,/cancelled0/);
147-
}
148-
};
149-
150-
action==='update'&&awaittestUpdate();
151-
action==='rename'&&awaittestRename();
152-
action==='delete'&&awaittestDelete();
153-
action==='create'&&awaittestCreate();
154-
}
5+
skipIfNoWatch();
1556

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

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

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

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

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

18031
it('should not throw when delete a watched test file',async()=>{
181-
awaittestWatch({fileToUpdate: 'test.js',action: 'delete', isolation });
32+
awaittestRunnerWatch({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-
awaittestWatch({action: 'create',fileToCreate: 'new-test-file.test.js', isolation });
40+
awaittestRunnerWatch({action: 'create',fileToCreate: 'new-test-file.test.js', isolation });
19041
});
19142
});
19243
}

0 commit comments

Comments
 (0)