node/test/parallel/test-fs-watch-recursive-symlink.js
Santiago Gimeno b345118e1e
test: refactor fs-watch tests due to macOS issue
In `macOS`, fsevents generated immediately before start watching may
leak into the event callback. See: https://github.com/nodejs/node/issues/54450
for an explanation. This might be fixed at some point in `libuv` though
it may take some time (see: https://github.com/libuv/libuv/issues/3866).
This commit comes in anticipation of the soon-to-be-released
`libuv@1.49.0` which was making these tests very flaky.

PR-URL: https://github.com/nodejs/node/pull/54498
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
2024-09-06 17:38:28 +00:00

112 lines
3.7 KiB
JavaScript

'use strict';
const common = require('../common');
const { setTimeout } = require('timers/promises');
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
// fs-watch on folders have limited capability in AIX.
// The testcase makes use of folder watching, and causes
// hang. This behavior is documented. Skip this for AIX.
if (common.isAIX)
common.skip('folder watch capability is limited in AIX.');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const testDir = tmpdir.path;
tmpdir.refresh();
(async () => {
// Add a recursive symlink to the parent folder
const testDirectory = fs.mkdtempSync(testDir + path.sep);
// Do not use `testDirectory` as base. It will hang the tests.
const rootDirectory = path.join(testDirectory, 'test-1');
fs.mkdirSync(rootDirectory);
const filePath = path.join(rootDirectory, 'file.txt');
const symlinkFolder = path.join(rootDirectory, 'symlink-folder');
fs.symlinkSync(rootDirectory, symlinkFolder);
if (common.isMacOS) {
// On macOS delay watcher start to avoid leaking previous events.
// Refs: https://github.com/libuv/libuv/pull/4503
await setTimeout(common.platformTimeout(100));
}
const watcher = fs.watch(rootDirectory, { recursive: true });
let watcherClosed = false;
watcher.on('change', function(event, filename) {
assert.ok(event === 'rename', `Received ${event}`);
assert.ok(filename === path.basename(symlinkFolder) || filename === path.basename(filePath), `Received ${filename}`);
if (filename === path.basename(filePath)) {
watcher.close();
watcherClosed = true;
}
});
await setTimeout(common.platformTimeout(100));
fs.writeFileSync(filePath, 'world');
process.once('exit', function() {
assert(watcherClosed, 'watcher Object was not closed');
});
})().then(common.mustCall());
(async () => {
// This test checks how a symlink to outside the tracking folder can trigger change
// tmp/sub-directory/tracking-folder/symlink-folder -> tmp/sub-directory
const rootDirectory = fs.mkdtempSync(testDir + path.sep);
const subDirectory = path.join(rootDirectory, 'sub-directory');
fs.mkdirSync(subDirectory);
const trackingSubDirectory = path.join(subDirectory, 'tracking-folder');
fs.mkdirSync(trackingSubDirectory);
const symlinkFolder = path.join(trackingSubDirectory, 'symlink-folder');
fs.symlinkSync(subDirectory, symlinkFolder);
const forbiddenFile = path.join(subDirectory, 'forbidden.txt');
const acceptableFile = path.join(trackingSubDirectory, 'acceptable.txt');
if (common.isMacOS) {
// On macOS delay watcher start to avoid leaking previous events.
// Refs: https://github.com/libuv/libuv/pull/4503
await setTimeout(common.platformTimeout(100));
}
const watcher = fs.watch(trackingSubDirectory, { recursive: true });
let watcherClosed = false;
watcher.on('change', function(event, filename) {
// macOS will only change the following events:
// { event: 'rename', filename: 'symlink-folder' }
// { event: 'rename', filename: 'acceptable.txt' }
assert.ok(event === 'rename', `Received ${event}`);
assert.ok(filename === path.basename(symlinkFolder) || filename === path.basename(acceptableFile), `Received ${filename}`);
if (filename === path.basename(acceptableFile)) {
watcher.close();
watcherClosed = true;
}
});
await setTimeout(common.platformTimeout(100));
fs.writeFileSync(forbiddenFile, 'world');
await setTimeout(common.platformTimeout(100));
fs.writeFileSync(acceptableFile, 'acceptable');
process.once('exit', function() {
assert(watcherClosed, 'watcher Object was not closed');
});
})().then(common.mustCall());