@@ -9,10 +9,13 @@ import { spawn } from 'node:child_process'; |
9 | 9 | import { writeFileSync, readFileSync } from 'node:fs'; |
10 | 10 | import { inspect } from 'node:util'; |
11 | 11 | import { once } from 'node:events'; |
| 12 | +import { createInterface } from 'node:readline/promises'; |
12 | 13 |
|
13 | 14 | if (common.isIBMi) |
14 | 15 | common.skip('IBMi does not support `fs.watch()`'); |
15 | 16 |
|
| 17 | +const supportsRecursive = common.isOSX || common.isWindows; |
| 18 | + |
16 | 19 | function restart(file) { |
17 | 20 | // To avoid flakiness, we save the file repeatedly until test is done |
18 | 21 | writeFileSync(file, readFileSync(file)); |
@@ -59,8 +62,8 @@ async function spawnWithRestarts({ |
59 | 62 | } |
60 | 63 |
|
61 | 64 | let tmpFiles = 0; |
62 | | -function createTmpFile(content = 'console.log("running");') { |
63 | | - const file = path.join(tmpdir.path, `${tmpFiles++}.js`); |
| 65 | +function createTmpFile(content = 'console.log("running");', ext = '.js') { |
| 66 | + const file = path.join(tmpdir.path, `${tmpFiles++}${ext}`); |
64 | 67 | writeFileSync(file, content); |
65 | 68 | return file; |
66 | 69 | } |
@@ -74,11 +77,29 @@ function assertRestartedCorrectly({ stdout, messages: { inner, completed, restar |
74 | 77 | assert.deepStrictEqual(lines.slice(-end.length), end); |
75 | 78 | } |
76 | 79 |
|
| 80 | +async function failWriteSucceed({ file, watchedFile }) { |
| 81 | + const child = spawn(execPath, ['--watch', '--no-warnings', file], { encoding: 'utf8' }); |
| 82 | + |
| 83 | + try { |
| 84 | + // Break the chunks into lines |
| 85 | + for await (const data of createInterface({ input: child.stdout })) { |
| 86 | + if (data.startsWith('Completed running')) { |
| 87 | + break; |
| 88 | + } |
| 89 | + if (data.startsWith('Failed running')) { |
| 90 | + writeFileSync(watchedFile, 'console.log("test has ran");'); |
| 91 | + } |
| 92 | + } |
| 93 | + } finally { |
| 94 | + child.kill(); |
| 95 | + } |
| 96 | +} |
| 97 | + |
77 | 98 | tmpdir.refresh(); |
78 | 99 |
|
79 | 100 | // Warning: this suite can run safely with concurrency: true |
80 | 101 | // only if tests do not watch/depend on the same files |
81 | | -describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { |
| 102 | +describe('watch mode', { concurrency: true, timeout: 60_000 }, () => { |
82 | 103 | it('should watch changes to a file - event loop ended', async () => { |
83 | 104 | const file = createTmpFile(); |
84 | 105 | const { stderr, stdout } = await spawnWithRestarts({ file }); |
@@ -104,16 +125,8 @@ describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { |
104 | 125 | }); |
105 | 126 | }); |
106 | 127 |
|
107 | | - it('should not watch when running an non-existing file', async () => { |
108 | | - const file = fixtures.path('watch-mode/non-existing.js'); |
109 | | - const { stderr, stdout } = await spawnWithRestarts({ file, restarts: 0 }); |
110 | | - |
111 | | - assert.match(stderr, /code: 'MODULE_NOT_FOUND'/); |
112 | | - assert.strictEqual(stdout, `Failed running ${inspect(file)}\n`); |
113 | | - }); |
114 | | - |
115 | 128 | it('should watch when running an non-existing file - when specified under --watch-path', { |
116 | | - skip: !common.isOSX && !common.isWindows |
| 129 | + skip: !supportsRecursive |
117 | 130 | }, async () => { |
118 | 131 | const file = fixtures.path('watch-mode/subdir/non-existing.js'); |
119 | 132 | const watchedFile = fixtures.path('watch-mode/subdir/file.js'); |
@@ -222,4 +235,39 @@ describe('watch mode', { concurrency: true, timeout: 60_0000 }, () => { |
222 | 235 | messages: { restarted: `Restarting ${inspect(file)}`, completed: `Completed running ${inspect(file)}` }, |
223 | 236 | }); |
224 | 237 | }); |
| 238 | + |
| 239 | + // TODO: Remove skip after https://github.com/nodejs/node/pull/45271 lands |
| 240 | + it('should not watch when running an missing file', { |
| 241 | + skip: !supportsRecursive |
| 242 | + }, async () => { |
| 243 | + const nonExistingfile = path.join(tmpdir.path, `${tmpFiles++}.js`); |
| 244 | + await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile }); |
| 245 | + }); |
| 246 | + |
| 247 | + it('should not watch when running an missing mjs file', { |
| 248 | + skip: !supportsRecursive |
| 249 | + }, async () => { |
| 250 | + const nonExistingfile = path.join(tmpdir.path, `${tmpFiles++}.mjs`); |
| 251 | + await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile }); |
| 252 | + }); |
| 253 | + |
| 254 | + it('should watch changes to previously missing dependency', { |
| 255 | + skip: !supportsRecursive |
| 256 | + }, async () => { |
| 257 | + const dependency = path.join(tmpdir.path, `${tmpFiles++}.js`); |
| 258 | + const relativeDependencyPath = `./${path.basename(dependency)}`; |
| 259 | + const dependant = createTmpFile(`console.log(require('${relativeDependencyPath}'))`); |
| 260 | + |
| 261 | + await failWriteSucceed({ file: dependant, watchedFile: dependency }); |
| 262 | + }); |
| 263 | + |
| 264 | + it('should watch changes to previously missing ESM dependency', { |
| 265 | + skip: !supportsRecursive |
| 266 | + }, async () => { |
| 267 | + const dependency = path.join(tmpdir.path, `${tmpFiles++}.mjs`); |
| 268 | + const relativeDependencyPath = `./${path.basename(dependency)}`; |
| 269 | + const dependant = createTmpFile(`import '${relativeDependencyPath}'`, '.mjs'); |
| 270 | + |
| 271 | + await failWriteSucceed({ file: dependant, watchedFile: dependency }); |
| 272 | + }); |
225 | 273 | }); |