mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 21:19:50 +01:00
cff7da7749
PR-URL: https://github.com/nodejs/node/pull/53820 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
186 lines
6.5 KiB
JavaScript
186 lines
6.5 KiB
JavaScript
// Flags: --expose-internals
|
|
import * as common from '../common/index.mjs';
|
|
import * as fixtures from '../common/fixtures.mjs';
|
|
import tmpdir from '../common/tmpdir.js';
|
|
import path from 'node:path';
|
|
import assert from 'node:assert';
|
|
import process from 'node:process';
|
|
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
import { setTimeout } from 'node:timers/promises';
|
|
import { once } from 'node:events';
|
|
import { spawn } from 'node:child_process';
|
|
import watcher from 'internal/watch_mode/files_watcher';
|
|
|
|
if (common.isIBMi)
|
|
common.skip('IBMi does not support `fs.watch()`');
|
|
|
|
const supportsRecursiveWatching = common.isMacOS || common.isWindows;
|
|
|
|
const { FilesWatcher } = watcher;
|
|
tmpdir.refresh();
|
|
|
|
describe('watch mode file watcher', () => {
|
|
let watcher;
|
|
let changesCount;
|
|
|
|
beforeEach(() => {
|
|
changesCount = 0;
|
|
watcher = new FilesWatcher({ debounce: 100 });
|
|
watcher.on('changed', () => changesCount++);
|
|
});
|
|
|
|
afterEach(() => watcher.clear());
|
|
|
|
let counter = 0;
|
|
function writeAndWaitForChanges(watcher, file) {
|
|
return new Promise((resolve) => {
|
|
const interval = setInterval(() => writeFileSync(file, `write ${counter++}`), 100);
|
|
watcher.once('changed', () => {
|
|
clearInterval(interval);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
it('should watch changed files', async () => {
|
|
const file = tmpdir.resolve('file1');
|
|
writeFileSync(file, 'written');
|
|
watcher.filterFile(file);
|
|
await writeAndWaitForChanges(watcher, file);
|
|
assert.strictEqual(changesCount, 1);
|
|
});
|
|
|
|
it('should debounce changes', async () => {
|
|
const file = tmpdir.resolve('file2');
|
|
writeFileSync(file, 'written');
|
|
watcher.filterFile(file);
|
|
await writeAndWaitForChanges(watcher, file);
|
|
|
|
writeFileSync(file, '1');
|
|
writeFileSync(file, '2');
|
|
writeFileSync(file, '3');
|
|
writeFileSync(file, '4');
|
|
await setTimeout(200); // debounce * 2
|
|
writeFileSync(file, '5');
|
|
const changed = once(watcher, 'changed');
|
|
writeFileSync(file, 'after');
|
|
await changed;
|
|
// Unfortunately testing that changesCount === 2 is flaky
|
|
assert.ok(changesCount < 5);
|
|
});
|
|
|
|
it('should debounce changes on multiple files', async () => {
|
|
const files = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
const file = tmpdir.resolve(`file-debounced-${i}`);
|
|
writeFileSync(file, 'written');
|
|
watcher.filterFile(file);
|
|
files.push(file);
|
|
}
|
|
|
|
files.forEach((file) => writeFileSync(file, '1'));
|
|
files.forEach((file) => writeFileSync(file, '2'));
|
|
files.forEach((file) => writeFileSync(file, '3'));
|
|
files.forEach((file) => writeFileSync(file, '4'));
|
|
|
|
await setTimeout(200); // debounce * 2
|
|
files.forEach((file) => writeFileSync(file, '5'));
|
|
const changed = once(watcher, 'changed');
|
|
files.forEach((file) => writeFileSync(file, 'after'));
|
|
await changed;
|
|
// Unfortunately testing that changesCount === 2 is flaky
|
|
assert.ok(changesCount < 5);
|
|
});
|
|
|
|
it('should ignore files in watched directory if they are not filtered',
|
|
{ skip: !supportsRecursiveWatching }, async () => {
|
|
watcher.on('changed', common.mustNotCall());
|
|
watcher.watchPath(tmpdir.path);
|
|
writeFileSync(tmpdir.resolve('file3'), '1');
|
|
// Wait for this long to make sure changes are not triggered
|
|
await setTimeout(1000);
|
|
});
|
|
|
|
it('should allow clearing filters', async () => {
|
|
const file = tmpdir.resolve('file4');
|
|
writeFileSync(file, 'written');
|
|
watcher.filterFile(file);
|
|
await writeAndWaitForChanges(watcher, file);
|
|
|
|
writeFileSync(file, '1');
|
|
assert.strictEqual(changesCount, 1);
|
|
|
|
watcher.clearFileFilters();
|
|
writeFileSync(file, '2');
|
|
// Wait for this long to make sure changes are triggered only once
|
|
await setTimeout(1000);
|
|
assert.strictEqual(changesCount, 1);
|
|
});
|
|
|
|
it('should watch all files in watched path when in "all" mode',
|
|
{ skip: !supportsRecursiveWatching }, async () => {
|
|
watcher = new FilesWatcher({ debounce: 100, mode: 'all' });
|
|
watcher.on('changed', () => changesCount++);
|
|
|
|
const file = tmpdir.resolve('file5');
|
|
watcher.watchPath(tmpdir.path);
|
|
|
|
const changed = once(watcher, 'changed');
|
|
await setTimeout(common.platformTimeout(100)); // avoid throttling
|
|
writeFileSync(file, 'changed');
|
|
await changed;
|
|
assert.strictEqual(changesCount, 1);
|
|
});
|
|
|
|
it('should ruse existing watcher if it exists',
|
|
{ skip: !supportsRecursiveWatching }, () => {
|
|
assert.deepStrictEqual(watcher.watchedPaths, []);
|
|
watcher.watchPath(tmpdir.path);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
watcher.watchPath(tmpdir.path);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
});
|
|
|
|
it('should ruse existing watcher of a parent directory',
|
|
{ skip: !supportsRecursiveWatching }, () => {
|
|
assert.deepStrictEqual(watcher.watchedPaths, []);
|
|
watcher.watchPath(tmpdir.path);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
watcher.watchPath(tmpdir.resolve('subdirectory'));
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
});
|
|
|
|
it('should remove existing watcher if adding a parent directory watcher',
|
|
{ skip: !supportsRecursiveWatching }, () => {
|
|
assert.deepStrictEqual(watcher.watchedPaths, []);
|
|
const subdirectory = tmpdir.resolve('subdirectory');
|
|
mkdirSync(subdirectory);
|
|
watcher.watchPath(subdirectory);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [subdirectory]);
|
|
watcher.watchPath(tmpdir.path);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
});
|
|
|
|
it('should clear all watchers when calling clear',
|
|
{ skip: !supportsRecursiveWatching }, () => {
|
|
assert.deepStrictEqual(watcher.watchedPaths, []);
|
|
watcher.watchPath(tmpdir.path);
|
|
assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
|
|
watcher.clear();
|
|
assert.deepStrictEqual(watcher.watchedPaths, []);
|
|
});
|
|
|
|
it('should watch files from subprocess IPC events', async () => {
|
|
const file = fixtures.path('watch-mode/ipc.js');
|
|
const child = spawn(process.execPath, [file], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'], encoding: 'utf8' });
|
|
watcher.watchChildProcessModules(child);
|
|
await once(child, 'exit');
|
|
let expected = [file, tmpdir.resolve('file')];
|
|
if (supportsRecursiveWatching) {
|
|
expected = expected.map((file) => path.dirname(file));
|
|
}
|
|
assert.deepStrictEqual(watcher.watchedPaths, expected);
|
|
});
|
|
});
|