Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/internal/fs/watchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ FSWatcher.prototype[kFSWatchStart] = function(filename,
encoding);
if (err) {
if (!throwIfNoEntry && err === UV_ENOENT) {
// FSEventWrap::Start() already started closing the native handle.
this._handle = null;
return;
}

Expand Down
6 changes: 5 additions & 1 deletion test/parallel/test-fs-watch-enoent.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ tmpdir.refresh();
);
} else {
const watcher = fs.watch(nonexistentFile, { throwIfNoEntry: false }, common.mustNotCall());
watcher.close();
if (common.isLinux) {
setTimeout(common.mustCall(() => watcher.close()), common.platformTimeout(10));
} else {
watcher.close();
}
}
}

Expand Down
91 changes: 91 additions & 0 deletions test/parallel/test-watch-mode-missing-env-file-signal.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as common from '../common/index.mjs';
import assert from 'node:assert';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { writeFileSync } from 'node:fs';
import { clearTimeout, setTimeout } from 'node:timers';
import tmpdir from '../common/tmpdir.js';

if (!common.isLinux)
common.skip('This test verifies Linux fs.watch() behavior');

tmpdir.refresh();
const entry = tmpdir.resolve('entry.js');
const missingEnvFile = tmpdir.resolve('missing.env');
writeFileSync(entry, '');

async function withTimeout(promise, message) {
let timeout;
const timedOut = new Promise((_, reject) => {
timeout = setTimeout(
() => reject(new Error(message)),
common.platformTimeout(10_000),
);
});

try {
return await Promise.race([promise, timedOut]);
} finally {
clearTimeout(timeout);
}
}

async function testSignal(signal) {
const child = spawn(process.execPath, [
'--watch',
`--env-file-if-exists=${missingEnvFile}`,
entry,
], {
cwd: tmpdir.path,
stdio: ['ignore', 'pipe', 'pipe'],
});
const closed = once(child, 'close');
const ready = Promise.withResolvers();
let stdout = '';
let stderr = '';

child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
if (stdout.includes('Completed running')) {
ready.resolve();
}
});
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.once('error', ready.reject);
child.once('exit', (code, exitSignal) => {
ready.reject(new Error(
`Watch mode exited before becoming ready: code=${code}, signal=${exitSignal}`,
));
});

try {
await withTimeout(
ready.promise,
'Timed out waiting for watch mode readiness',
);
assert.strictEqual(child.kill(signal), true);
const [code, exitSignal] = await withTimeout(
closed,
`Timed out waiting for watch mode to exit after ${signal}`,
);

assert.strictEqual(code, 0);
assert.strictEqual(exitSignal, null);
assert.doesNotMatch(
`${stdout}\n${stderr}`,
/Assertion failed|FSEventWrap::GetInitialized/,
);
} finally {
if (child.exitCode === null && child.signalCode === null) {
child.kill('SIGKILL');
}
await closed.catch(() => {});
}
}

await testSignal('SIGINT');
await testSignal('SIGTERM');