diff --git a/test/parallel/test-fs-promises-watch-ignore-function.mjs b/test/parallel/test-fs-promises-watch-ignore-function.mjs new file mode 100644 index 00000000000000..40d54add0585c7 --- /dev/null +++ b/test/parallel/test-fs-promises-watch-ignore-function.mjs @@ -0,0 +1,40 @@ +import * as common from '../common/index.mjs'; +import { skipIfNoWatch } from '../common/watch.js'; + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = await import('node:assert'); +const path = await import('node:path'); +const tmpdir = await import('../common/tmpdir.js'); +const { watch } = await import('node:fs/promises'); +const { writeFileSync } = await import('node:fs'); + +tmpdir.refresh(); + +const testDir = tmpdir.resolve(); +const keepFile = 'visible.txt'; +const ignoreFile = '.hidden'; +const keepFilePath = path.join(testDir, keepFile); +const ignoreFilePath = path.join(testDir, ignoreFile); + +const watcher = watch(testDir, { + ignore: (filename) => filename.startsWith('.'), +}); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + writeFileSync(ignoreFilePath, 'ignored'); + writeFileSync(keepFilePath, 'content'); +}, common.platformTimeout(100)); + +for await (const { filename } of watcher) { + assert.notStrictEqual(filename, ignoreFile); + + if (filename === keepFile) { + break; + } +} diff --git a/test/parallel/test-fs-promises-watch-ignore-glob.mjs b/test/parallel/test-fs-promises-watch-ignore-glob.mjs new file mode 100644 index 00000000000000..dfc529e68cdaf9 --- /dev/null +++ b/test/parallel/test-fs-promises-watch-ignore-glob.mjs @@ -0,0 +1,38 @@ +import * as common from '../common/index.mjs'; +import { skipIfNoWatch } from '../common/watch.js'; + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = await import('node:assert'); +const path = await import('node:path'); +const tmpdir = await import('../common/tmpdir.js'); +const { watch } = await import('node:fs/promises'); +const { writeFileSync } = await import('node:fs'); + +tmpdir.refresh(); + +const testDir = tmpdir.resolve(); +const keepFile = 'keep.txt'; +const ignoreFile = 'ignore.log'; +const keepFilePath = path.join(testDir, keepFile); +const ignoreFilePath = path.join(testDir, ignoreFile); + +const watcher = watch(testDir, { ignore: '*.log' }); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + writeFileSync(ignoreFilePath, 'ignored'); + writeFileSync(keepFilePath, 'content'); +}, common.platformTimeout(100)); + +for await (const { filename } of watcher) { + assert.notStrictEqual(filename, ignoreFile); + + if (filename === keepFile) { + break; + } +} diff --git a/test/parallel/test-fs-promises-watch-ignore-invalid.mjs b/test/parallel/test-fs-promises-watch-ignore-invalid.mjs new file mode 100644 index 00000000000000..597fecc3bb04bd --- /dev/null +++ b/test/parallel/test-fs-promises-watch-ignore-invalid.mjs @@ -0,0 +1,35 @@ +import '../common/index.mjs'; +import { skipIfNoWatch } from '../common/watch.js'; + +skipIfNoWatch(); + +const assert = await import('node:assert'); +const { watch } = await import('node:fs/promises'); + +await assert.rejects( + async () => { + const watcher = watch('.', { ignore: 123 }); + // eslint-disable-next-line no-unused-vars + for await (const _ of watcher) { + // Will throw before yielding + } + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); + +await assert.rejects( + async () => { + const watcher = watch('.', { ignore: '' }); + // eslint-disable-next-line no-unused-vars + for await (const _ of watcher) { + // Will throw before yielding + } + }, + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + } +); diff --git a/test/parallel/test-fs-promises-watch-ignore-mixed.mjs b/test/parallel/test-fs-promises-watch-ignore-mixed.mjs new file mode 100644 index 00000000000000..ea57b6477e8482 --- /dev/null +++ b/test/parallel/test-fs-promises-watch-ignore-mixed.mjs @@ -0,0 +1,47 @@ +import * as common from '../common/index.mjs'; +import { skipIfNoWatch } from '../common/watch.js'; + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = await import('node:assert'); +const path = await import('node:path'); +const tmpdir = await import('../common/tmpdir.js'); +const { watch } = await import('node:fs/promises'); +const { writeFileSync } = await import('node:fs'); + +tmpdir.refresh(); + +const testDir = tmpdir.resolve(); +const keepFile = 'keep.txt'; +const ignoreLog = 'debug.log'; +const ignoreTmp = 'temp.tmp'; +const keepFilePath = path.join(testDir, keepFile); +const ignoreLogPath = path.join(testDir, ignoreLog); +const ignoreTmpPath = path.join(testDir, ignoreTmp); + +const watcher = watch(testDir, { + ignore: [ + '*.log', + /\.tmp$/, + ], +}); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + writeFileSync(ignoreLogPath, 'ignored'); + writeFileSync(ignoreTmpPath, 'ignored'); + writeFileSync(keepFilePath, 'content'); +}, common.platformTimeout(100)); + +for await (const { filename } of watcher) { + assert.notStrictEqual(filename, ignoreLog); + assert.notStrictEqual(filename, ignoreTmp); + + if (filename === keepFile) { + break; + } +} diff --git a/test/parallel/test-fs-promises-watch-ignore-regexp.mjs b/test/parallel/test-fs-promises-watch-ignore-regexp.mjs new file mode 100644 index 00000000000000..4baafe64bc739f --- /dev/null +++ b/test/parallel/test-fs-promises-watch-ignore-regexp.mjs @@ -0,0 +1,38 @@ +import * as common from '../common/index.mjs'; +import { skipIfNoWatch } from '../common/watch.js'; + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = await import('node:assert'); +const path = await import('node:path'); +const tmpdir = await import('../common/tmpdir.js'); +const { watch } = await import('node:fs/promises'); +const { writeFileSync } = await import('node:fs'); + +tmpdir.refresh(); + +const testDir = tmpdir.resolve(); +const keepFile = 'keep.txt'; +const ignoreFile = 'ignore.tmp'; +const keepFilePath = path.join(testDir, keepFile); +const ignoreFilePath = path.join(testDir, ignoreFile); + +const watcher = watch(testDir, { ignore: /\.tmp$/ }); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + writeFileSync(ignoreFilePath, 'ignored'); + writeFileSync(keepFilePath, 'content'); +}, common.platformTimeout(100)); + +for await (const { filename } of watcher) { + assert.notStrictEqual(filename, ignoreFile); + + if (filename === keepFile) { + break; + } +} diff --git a/test/parallel/test-fs-watch-ignore-function.js b/test/parallel/test-fs-watch-ignore-function.js new file mode 100644 index 00000000000000..a3404b5e15bb16 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-function.js @@ -0,0 +1,41 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const testFileName = 'visible.txt'; +const testFilePath = path.join(tmpdir.path, testFileName); +const ignoredFileName = '.hidden'; +const ignoredFilePath = path.join(tmpdir.path, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + ignore: (filename) => filename.startsWith('.'), +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + assert.notStrictEqual(filename, ignoredFileName); + + if (filename === testFileName) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-glob.js b/test/parallel/test-fs-watch-ignore-glob.js new file mode 100644 index 00000000000000..4615d1368652b6 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-glob.js @@ -0,0 +1,41 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const testFileName = 'file.txt'; +const testFilePath = path.join(tmpdir.path, testFileName); +const ignoredFileName = 'file.log'; +const ignoredFilePath = path.join(tmpdir.path, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + ignore: '*.log', +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + assert.notStrictEqual(filename, ignoredFileName); + + if (filename === testFileName) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-invalid.js b/test/parallel/test-fs-watch-ignore-invalid.js new file mode 100644 index 00000000000000..e5d4dba0e23ff6 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-invalid.js @@ -0,0 +1,41 @@ +'use strict'; + +require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +const assert = require('assert'); +const fs = require('fs'); + +assert.throws( + () => fs.watch('.', { ignore: 123 }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); + +assert.throws( + () => fs.watch('.', { ignore: '' }), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + } +); + +assert.throws( + () => fs.watch('.', { ignore: [123] }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); + +assert.throws( + () => fs.watch('.', { ignore: [''] }), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + } +); diff --git a/test/parallel/test-fs-watch-ignore-mixed.js b/test/parallel/test-fs-watch-ignore-mixed.js new file mode 100644 index 00000000000000..e5b1af98ca3cc1 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-mixed.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const testFileName = 'keep.txt'; +const testFilePath = path.join(tmpdir.path, testFileName); +const ignoredLogName = 'debug.log'; +const ignoredLogPath = path.join(tmpdir.path, ignoredLogName); +const ignoredTmpName = 'temp.tmp'; +const ignoredTmpPath = path.join(tmpdir.path, ignoredTmpName); +const ignoredHiddenName = '.secret'; +const ignoredHiddenPath = path.join(tmpdir.path, ignoredHiddenName); + +const watcher = fs.watch(tmpdir.path, { + ignore: [ + '*.log', + /\.tmp$/, + (filename) => filename.startsWith('.'), + ], +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + assert.notStrictEqual(filename, ignoredLogName); + assert.notStrictEqual(filename, ignoredTmpName); + assert.notStrictEqual(filename, ignoredHiddenName); + + if (filename === testFileName) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredLogPath, 'ignored'); + fs.writeFileSync(ignoredTmpPath, 'ignored'); + fs.writeFileSync(ignoredHiddenPath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-promise.js b/test/parallel/test-fs-watch-ignore-promise.js deleted file mode 100644 index 8229a19e61a8e9..00000000000000 --- a/test/parallel/test-fs-watch-ignore-promise.js +++ /dev/null @@ -1,219 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (common.isIBMi) - common.skip('IBMi does not support `fs.watch()`'); - -if (common.isAIX) - common.skip('folder watch capability is limited in AIX.'); - -if (common.isSunOS) - common.skip('fs.watch is not reliable on SunOS.'); - -const assert = require('assert'); -const path = require('path'); -const fs = require('fs/promises'); -const fsSync = require('fs'); - -const tmpdir = require('../common/tmpdir'); -const testDir = tmpdir.path; -tmpdir.refresh(); - -// Test 1: String glob pattern ignore with Promise API -(async function testGlobIgnore() { - const testsubdir = await fs.mkdtemp(testDir + path.sep); - const keepFile = 'keep.txt'; - const ignoreFile = 'ignore.log'; - const keepFilePath = path.join(testsubdir, keepFile); - const ignoreFilePath = path.join(testsubdir, ignoreFile); - - const watcher = fs.watch(testsubdir, { ignore: '*.log' }); - - let seenIgnored = false; - let interval; - - process.on('exit', () => { - assert.ok(interval === null); - assert.strictEqual(seenIgnored, false); - }); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fsSync.writeFileSync(ignoreFilePath, 'ignored'); - fsSync.writeFileSync(keepFilePath, 'content-' + Date.now()); - }, 100); - })); - - for await (const { filename } of watcher) { - if (filename === ignoreFile) { - seenIgnored = true; - } - if (filename === keepFile) { - break; - } - } - - clearInterval(interval); - interval = null; -})().then(common.mustCall()); - -// Test 2: RegExp ignore pattern with Promise API -(async function testRegExpIgnore() { - const testsubdir = await fs.mkdtemp(testDir + path.sep); - const keepFile = 'keep.txt'; - const ignoreFile = 'ignore.tmp'; - const keepFilePath = path.join(testsubdir, keepFile); - const ignoreFilePath = path.join(testsubdir, ignoreFile); - - const watcher = fs.watch(testsubdir, { ignore: /\.tmp$/ }); - - let seenIgnored = false; - let interval; - - process.on('exit', () => { - assert.ok(interval === null); - assert.strictEqual(seenIgnored, false); - }); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fsSync.writeFileSync(ignoreFilePath, 'ignored'); - fsSync.writeFileSync(keepFilePath, 'content-' + Date.now()); - }, 100); - })); - - for await (const { filename } of watcher) { - if (filename === ignoreFile) { - seenIgnored = true; - } - if (filename === keepFile) { - break; - } - } - - clearInterval(interval); - interval = null; -})().then(common.mustCall()); - -// Test 3: Function ignore pattern with Promise API -(async function testFunctionIgnore() { - const testsubdir = await fs.mkdtemp(testDir + path.sep); - const keepFile = 'visible.txt'; - const ignoreFile = '.hidden'; - const keepFilePath = path.join(testsubdir, keepFile); - const ignoreFilePath = path.join(testsubdir, ignoreFile); - - const watcher = fs.watch(testsubdir, { - ignore: (filename) => filename.startsWith('.'), - }); - - let seenIgnored = false; - let interval; - - process.on('exit', () => { - assert.ok(interval === null); - assert.strictEqual(seenIgnored, false); - }); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fsSync.writeFileSync(ignoreFilePath, 'ignored'); - fsSync.writeFileSync(keepFilePath, 'content-' + Date.now()); - }, 100); - })); - - for await (const { filename } of watcher) { - if (filename === ignoreFile) { - seenIgnored = true; - } - if (filename === keepFile) { - break; - } - } - - clearInterval(interval); - interval = null; -})().then(common.mustCall()); - -// Test 4: Array of mixed patterns with Promise API -(async function testArrayIgnore() { - const testsubdir = await fs.mkdtemp(testDir + path.sep); - const keepFile = 'keep.txt'; - const ignoreLog = 'debug.log'; - const ignoreTmp = 'temp.tmp'; - const keepFilePath = path.join(testsubdir, keepFile); - const ignoreLogPath = path.join(testsubdir, ignoreLog); - const ignoreTmpPath = path.join(testsubdir, ignoreTmp); - - const watcher = fs.watch(testsubdir, { - ignore: [ - '*.log', - /\.tmp$/, - ], - }); - - let seenLog = false; - let seenTmp = false; - let interval; - - process.on('exit', () => { - assert.ok(interval === null); - assert.strictEqual(seenLog, false); - assert.strictEqual(seenTmp, false); - }); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fsSync.writeFileSync(ignoreLogPath, 'ignored'); - fsSync.writeFileSync(ignoreTmpPath, 'ignored'); - fsSync.writeFileSync(keepFilePath, 'content-' + Date.now()); - }, 100); - })); - - for await (const { filename } of watcher) { - if (filename === ignoreLog) seenLog = true; - if (filename === ignoreTmp) seenTmp = true; - if (filename === keepFile) { - break; - } - } - - clearInterval(interval); - interval = null; -})().then(common.mustCall()); - -// Test 5: Invalid option validation with Promise API -// Note: async generators don't execute until iteration starts, -// so we need to use assert.rejects with iteration -(async function testInvalidIgnore() { - const testsubdir = await fs.mkdtemp(testDir + path.sep); - - await assert.rejects( - async () => { - const watcher = fs.watch(testsubdir, { ignore: 123 }); - // eslint-disable-next-line no-unused-vars - for await (const _ of watcher) { - // Will throw before yielding - } - }, - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - } - ); - - await assert.rejects( - async () => { - const watcher = fs.watch(testsubdir, { ignore: '' }); - // eslint-disable-next-line no-unused-vars - for await (const _ of watcher) { - // Will throw before yielding - } - }, - { - code: 'ERR_INVALID_ARG_VALUE', - name: 'TypeError', - } - ); -})().then(common.mustCall()); diff --git a/test/parallel/test-fs-watch-ignore-recursive-glob-subdirectories.js b/test/parallel/test-fs-watch-ignore-recursive-glob-subdirectories.js new file mode 100644 index 00000000000000..378332af591950 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-recursive-glob-subdirectories.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const nodeModules = path.join(tmpdir.path, 'node_modules'); +const srcDir = path.join(tmpdir.path, 'src'); + +fs.mkdirSync(nodeModules); +fs.mkdirSync(srcDir); + +const testFileName = 'app.js'; +const testFilePath = path.join(srcDir, testFileName); +const ignoredFileName = 'package.json'; +const ignoredFilePath = path.join(nodeModules, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + recursive: true, + // On Linux, matching the directory skips watching it entirely. + // On macOS, the native watcher still needs to filter file events inside. + ignore: ['**/node_modules/**', '**/node_modules'], +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + if (!filename) return; + + // On recursive watch, filename includes relative path from watched dir + assert(!filename.includes('node_modules')); + + if (filename.endsWith(testFileName)) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, '{}'); + fs.writeFileSync(testFilePath, 'console.log("hello-' + Date.now() + '")'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-recursive-glob.js b/test/parallel/test-fs-watch-ignore-recursive-glob.js new file mode 100644 index 00000000000000..2fbf1970fb3342 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-recursive-glob.js @@ -0,0 +1,48 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const subDirectory = path.join(tmpdir.path, 'subdir'); +fs.mkdirSync(subDirectory); + +const testFileName = 'file.txt'; +const testFilePath = path.join(subDirectory, testFileName); +const ignoredFileName = 'file.log'; +const ignoredFilePath = path.join(subDirectory, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + recursive: true, + ignore: '*.log', +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + if (!filename) return; + + // On recursive watch, filename includes relative path from watched dir + assert(!filename.endsWith(ignoredFileName)); + + if (filename.endsWith(testFileName)) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-recursive-mixed.js b/test/parallel/test-fs-watch-ignore-recursive-mixed.js new file mode 100644 index 00000000000000..7c708d771bc422 --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-recursive-mixed.js @@ -0,0 +1,60 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const subDirectory = path.join(tmpdir.path, 'deep'); +fs.mkdirSync(subDirectory); + +const testFileName = 'visible.txt'; +const testFilePath = path.join(subDirectory, testFileName); +const ignoredLogName = 'debug.log'; +const ignoredLogPath = path.join(subDirectory, ignoredLogName); +const ignoredTmpName = 'temp.tmp'; +const ignoredTmpPath = path.join(subDirectory, ignoredTmpName); +const ignoredHiddenName = '.gitignore'; +const ignoredHiddenPath = path.join(subDirectory, ignoredHiddenName); + +const watcher = fs.watch(tmpdir.path, { + recursive: true, + ignore: [ + '*.log', + /\.tmp$/, + (filename) => path.basename(filename).startsWith('.'), + ], +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + if (!filename) return; + + // On recursive watch, filename includes relative path from watched dir + assert(!filename.endsWith(ignoredLogName)); + assert(!filename.endsWith(ignoredTmpName)); + assert(!filename.endsWith(ignoredHiddenName)); + + if (filename.endsWith(testFileName)) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredLogPath, 'ignored'); + fs.writeFileSync(ignoredTmpPath, 'ignored'); + fs.writeFileSync(ignoredHiddenPath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-recursive-regexp.js b/test/parallel/test-fs-watch-ignore-recursive-regexp.js new file mode 100644 index 00000000000000..714539b7308b8f --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-recursive-regexp.js @@ -0,0 +1,48 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const subDirectory = path.join(tmpdir.path, 'nested'); +fs.mkdirSync(subDirectory); + +const testFileName = 'keep.txt'; +const testFilePath = path.join(subDirectory, testFileName); +const ignoredFileName = 'temp.tmp'; +const ignoredFilePath = path.join(subDirectory, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + recursive: true, + ignore: /\.tmp$/, +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + if (!filename) return; + + // On recursive watch, filename includes relative path from watched dir + assert(!filename.endsWith(ignoredFileName)); + + if (filename.endsWith(testFileName)) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore-recursive.js b/test/parallel/test-fs-watch-ignore-recursive.js deleted file mode 100644 index f4533a73ab0a99..00000000000000 --- a/test/parallel/test-fs-watch-ignore-recursive.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (common.isIBMi) - common.skip('IBMi does not support `fs.watch()`'); - -if (common.isAIX) - common.skip('folder watch capability is limited in AIX.'); - -if (common.isSunOS) - common.skip('fs.watch is not reliable on SunOS.'); - -const assert = require('assert'); -const path = require('path'); -const fs = require('fs'); - -const tmpdir = require('../common/tmpdir'); -const testDir = tmpdir.path; -tmpdir.refresh(); - -// Test 1: String glob pattern ignore with recursive watch -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-recursive-glob'); - const subDirectory = path.join(testDirectory, 'subdir'); - fs.mkdirSync(testDirectory); - fs.mkdirSync(subDirectory); - - const testFile = path.join(subDirectory, 'file.txt'); - const ignoredFile = path.join(subDirectory, 'file.log'); - - const watcher = fs.watch(testDirectory, { - recursive: true, - ignore: '*.log', - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - // On recursive watch, filename includes relative path from watched dir - if (filename && filename.endsWith('file.txt')) { - seenFile = true; - } - if (filename && filename.endsWith('file.log')) { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - // Use setInterval to handle potential event delays on macOS FSEvents - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 2: RegExp ignore pattern with recursive watch -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-recursive-regexp'); - const subDirectory = path.join(testDirectory, 'nested'); - fs.mkdirSync(testDirectory); - fs.mkdirSync(subDirectory); - - const testFile = path.join(subDirectory, 'keep.txt'); - const ignoredFile = path.join(subDirectory, 'temp.tmp'); - - const watcher = fs.watch(testDirectory, { - recursive: true, - ignore: /\.tmp$/, - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename && filename.endsWith('keep.txt')) { - seenFile = true; - } - if (filename && filename.endsWith('temp.tmp')) { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - // Use setInterval to handle potential event delays on macOS FSEvents - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 3: Glob pattern with ** to match paths in subdirectories -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-recursive-globstar'); - const nodeModules = path.join(testDirectory, 'node_modules'); - const srcDir = path.join(testDirectory, 'src'); - fs.mkdirSync(testDirectory); - fs.mkdirSync(nodeModules); - fs.mkdirSync(srcDir); - - const testFile = path.join(srcDir, 'app.js'); - const ignoredFile = path.join(nodeModules, 'package.json'); - - const watcher = fs.watch(testDirectory, { - recursive: true, - // On Linux, matching the directory skips watching it entirely. - // On macOS, the native watcher still needs to filter file events inside. - ignore: ['**/node_modules/**', '**/node_modules'], - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename && filename.endsWith('app.js')) { - seenFile = true; - } - if (filename && filename.includes('node_modules')) { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - // Use setInterval to handle potential event delays on macOS FSEvents - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, '{}'); - fs.writeFileSync(testFile, 'console.log("hello-' + Date.now() + '")'); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 4: Array of mixed patterns with recursive watch -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-recursive-array'); - const subDirectory = path.join(testDirectory, 'deep'); - fs.mkdirSync(testDirectory); - fs.mkdirSync(subDirectory); - - const testFile = path.join(subDirectory, 'visible.txt'); - const ignoredLog = path.join(subDirectory, 'debug.log'); - const ignoredTmp = path.join(subDirectory, 'temp.tmp'); - const ignoredHidden = path.join(subDirectory, '.gitignore'); - - const watcher = fs.watch(testDirectory, { - recursive: true, - ignore: [ - '*.log', - /\.tmp$/, - (filename) => path.basename(filename).startsWith('.'), - ], - }); - - let seenFile = false; - let seenLog = false; - let seenTmp = false; - let seenHidden = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename && filename.endsWith('visible.txt')) { - seenFile = true; - } - if (filename && filename.endsWith('debug.log')) seenLog = true; - if (filename && filename.endsWith('temp.tmp')) seenTmp = true; - if (filename && filename.endsWith('.gitignore')) seenHidden = true; - - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - // Use setInterval to handle potential event delays on macOS FSEvents - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredLog, 'ignored'); - fs.writeFileSync(ignoredTmp, 'ignored'); - fs.writeFileSync(ignoredHidden, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenLog, false); - assert.strictEqual(seenTmp, false); - assert.strictEqual(seenHidden, false); - }); -} diff --git a/test/parallel/test-fs-watch-ignore-regexp.js b/test/parallel/test-fs-watch-ignore-regexp.js new file mode 100644 index 00000000000000..1600e8b2f599df --- /dev/null +++ b/test/parallel/test-fs-watch-ignore-regexp.js @@ -0,0 +1,41 @@ +'use strict'; + +const common = require('../common'); +const { skipIfNoWatch } = require('../common/watch.js'); + +skipIfNoWatch(); + +// if (common.isSunOS) +// common.skip('`fs.watch()` is not reliable on SunOS.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const testFileName = 'keep.txt'; +const testFilePath = path.join(tmpdir.path, testFileName); +const ignoredFileName = 'ignore.tmp'; +const ignoredFilePath = path.join(tmpdir.path, ignoredFileName); + +const watcher = fs.watch(tmpdir.path, { + ignore: /\.tmp$/, +}); + +watcher.on('change', common.mustCallAtLeast((event, filename) => { + assert.notStrictEqual(filename, ignoredFileName); + + if (filename === testFileName) { + watcher.close(); + } +}, 1)); + +// Do the write with a delay to ensure that the OS is ready to notify us. See +// https://github.com/nodejs/node/issues/52601. +setTimeout(() => { + fs.writeFileSync(ignoredFilePath, 'ignored'); + fs.writeFileSync(testFilePath, 'content'); +}, common.platformTimeout(100)); diff --git a/test/parallel/test-fs-watch-ignore.js b/test/parallel/test-fs-watch-ignore.js deleted file mode 100644 index bedab156706df9..00000000000000 --- a/test/parallel/test-fs-watch-ignore.js +++ /dev/null @@ -1,248 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (common.isIBMi) - common.skip('IBMi does not support `fs.watch()`'); - -if (common.isAIX) - common.skip('folder watch capability is limited in AIX.'); - -if (common.isSunOS) - common.skip('fs.watch is not reliable on SunOS.'); - -const assert = require('assert'); -const path = require('path'); -const fs = require('fs'); - -const tmpdir = require('../common/tmpdir'); -const testDir = tmpdir.path; -tmpdir.refresh(); - -// Test 1: String glob pattern ignore -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-glob'); - fs.mkdirSync(testDirectory); - - const testFile = path.join(testDirectory, 'file.txt'); - const ignoredFile = path.join(testDirectory, 'file.log'); - - const watcher = fs.watch(testDirectory, { - ignore: '*.log', - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename === 'file.txt') { - seenFile = true; - } - if (filename === 'file.log') { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 2: RegExp ignore pattern -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-regexp'); - fs.mkdirSync(testDirectory); - - const testFile = path.join(testDirectory, 'keep.txt'); - const ignoredFile = path.join(testDirectory, 'ignore.tmp'); - - const watcher = fs.watch(testDirectory, { - ignore: /\.tmp$/, - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename === 'keep.txt') { - seenFile = true; - } - if (filename === 'ignore.tmp') { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 3: Function ignore pattern -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-function'); - fs.mkdirSync(testDirectory); - - const testFile = path.join(testDirectory, 'visible.txt'); - const ignoredFile = path.join(testDirectory, '.hidden'); - - const watcher = fs.watch(testDirectory, { - ignore: (filename) => filename.startsWith('.'), - }); - - let seenFile = false; - let seenIgnored = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename === 'visible.txt') { - seenFile = true; - } - if (filename === '.hidden') { - seenIgnored = true; - } - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredFile, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenIgnored, false); - }); -} - -// Test 4: Array of mixed patterns -{ - const rootDirectory = fs.mkdtempSync(testDir + path.sep); - const testDirectory = path.join(rootDirectory, 'test-array'); - fs.mkdirSync(testDirectory); - - const testFile = path.join(testDirectory, 'keep.txt'); - const ignoredLog = path.join(testDirectory, 'debug.log'); - const ignoredTmp = path.join(testDirectory, 'temp.tmp'); - const ignoredHidden = path.join(testDirectory, '.secret'); - - const watcher = fs.watch(testDirectory, { - ignore: [ - '*.log', - /\.tmp$/, - (filename) => filename.startsWith('.'), - ], - }); - - let seenFile = false; - let seenLog = false; - let seenTmp = false; - let seenHidden = false; - let interval; - - watcher.on('change', common.mustCallAtLeast((event, filename) => { - if (filename === 'keep.txt') { - seenFile = true; - } - if (filename === 'debug.log') seenLog = true; - if (filename === 'temp.tmp') seenTmp = true; - if (filename === '.secret') seenHidden = true; - - if (seenFile) { - clearInterval(interval); - interval = null; - watcher.close(); - } - }, 1)); - - process.nextTick(common.mustCall(() => { - interval = setInterval(() => { - fs.writeFileSync(ignoredLog, 'ignored'); - fs.writeFileSync(ignoredTmp, 'ignored'); - fs.writeFileSync(ignoredHidden, 'ignored'); - fs.writeFileSync(testFile, 'content-' + Date.now()); - }, 100); - })); - - process.on('exit', () => { - assert.strictEqual(interval, null); - assert.strictEqual(seenFile, true); - assert.strictEqual(seenLog, false); - assert.strictEqual(seenTmp, false); - assert.strictEqual(seenHidden, false); - }); -} - -// Test 5: Invalid option validation -{ - assert.throws( - () => fs.watch('.', { ignore: 123 }), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - } - ); - - assert.throws( - () => fs.watch('.', { ignore: '' }), - { - code: 'ERR_INVALID_ARG_VALUE', - name: 'TypeError', - } - ); - - assert.throws( - () => fs.watch('.', { ignore: [123] }), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - } - ); - - assert.throws( - () => fs.watch('.', { ignore: [''] }), - { - code: 'ERR_INVALID_ARG_VALUE', - name: 'TypeError', - } - ); -}