-
Notifications
You must be signed in to change notification settings - Fork 30.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PR-URL: #45098 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: James M Snell <[email protected]>
Showing
13 changed files
with
801 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
'use strict'; | ||
|
||
const { | ||
ArrayPrototypePush, | ||
SafePromiseAllReturnVoid, | ||
Promise, | ||
PromisePrototypeThen, | ||
SafeMap, | ||
SafeSet, | ||
StringPrototypeStartsWith, | ||
SymbolAsyncIterator, | ||
} = primordials; | ||
|
||
const { EventEmitter } = require('events'); | ||
const assert = require('internal/assert'); | ||
const { | ||
AbortError, | ||
codes: { | ||
ERR_INVALID_ARG_VALUE, | ||
}, | ||
} = require('internal/errors'); | ||
const { getValidatedPath } = require('internal/fs/utils'); | ||
const { kFSWatchStart, StatWatcher } = require('internal/fs/watchers'); | ||
const { kEmptyObject } = require('internal/util'); | ||
const { validateBoolean, validateAbortSignal } = require('internal/validators'); | ||
const path = require('path'); | ||
|
||
let internalSync; | ||
let internalPromises; | ||
|
||
function lazyLoadFsPromises() { | ||
internalPromises ??= require('fs/promises'); | ||
return internalPromises; | ||
} | ||
|
||
function lazyLoadFsSync() { | ||
internalSync ??= require('fs'); | ||
return internalSync; | ||
} | ||
|
||
async function traverse(dir, files = new SafeMap(), symbolicLinks = new SafeSet()) { | ||
const { opendir } = lazyLoadFsPromises(); | ||
|
||
const filenames = await opendir(dir); | ||
const subdirectories = []; | ||
|
||
for await (const file of filenames) { | ||
const f = path.join(dir, file.name); | ||
|
||
files.set(f, file); | ||
|
||
// Do not follow symbolic links | ||
if (file.isSymbolicLink()) { | ||
symbolicLinks.add(f); | ||
} else if (file.isDirectory()) { | ||
ArrayPrototypePush(subdirectories, traverse(f, files)); | ||
} | ||
} | ||
|
||
await SafePromiseAllReturnVoid(subdirectories); | ||
|
||
return files; | ||
} | ||
|
||
class FSWatcher extends EventEmitter { | ||
#options = null; | ||
#closed = false; | ||
#files = new SafeMap(); | ||
#symbolicFiles = new SafeSet(); | ||
#rootPath = path.resolve(); | ||
#watchingFile = false; | ||
|
||
constructor(options = kEmptyObject) { | ||
super(); | ||
|
||
assert(typeof options === 'object'); | ||
|
||
const { persistent, recursive, signal, encoding } = options; | ||
|
||
// TODO(anonrig): Add non-recursive support to non-native-watcher for IBMi & AIX support. | ||
if (recursive != null) { | ||
validateBoolean(recursive, 'options.recursive'); | ||
} | ||
|
||
if (persistent != null) { | ||
validateBoolean(persistent, 'options.persistent'); | ||
} | ||
|
||
if (signal != null) { | ||
validateAbortSignal(signal, 'options.signal'); | ||
} | ||
|
||
if (encoding != null) { | ||
// This is required since on macOS and Windows it throws ERR_INVALID_ARG_VALUE | ||
if (typeof encoding !== 'string') { | ||
throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding'); | ||
} | ||
} | ||
|
||
this.#options = { persistent, recursive, signal, encoding }; | ||
} | ||
|
||
close() { | ||
if (this.#closed) { | ||
return; | ||
} | ||
|
||
const { unwatchFile } = lazyLoadFsSync(); | ||
this.#closed = true; | ||
|
||
for (const file of this.#files.keys()) { | ||
unwatchFile(file); | ||
} | ||
|
||
this.#files.clear(); | ||
this.#symbolicFiles.clear(); | ||
this.emit('close'); | ||
} | ||
|
||
#unwatchFiles(file) { | ||
const { unwatchFile } = lazyLoadFsSync(); | ||
|
||
this.#symbolicFiles.delete(file); | ||
|
||
for (const filename of this.#files.keys()) { | ||
if (StringPrototypeStartsWith(filename, file)) { | ||
unwatchFile(filename); | ||
} | ||
} | ||
} | ||
|
||
async #watchFolder(folder) { | ||
const { opendir } = lazyLoadFsPromises(); | ||
|
||
try { | ||
const files = await opendir(folder); | ||
|
||
for await (const file of files) { | ||
if (this.#closed) { | ||
break; | ||
} | ||
|
||
const f = path.join(folder, file.name); | ||
|
||
if (!this.#files.has(f)) { | ||
this.emit('change', 'rename', path.relative(this.#rootPath, f)); | ||
|
||
if (file.isSymbolicLink()) { | ||
this.#symbolicFiles.add(f); | ||
} | ||
|
||
if (file.isFile()) { | ||
this.#watchFile(f); | ||
} else { | ||
this.#files.set(f, file); | ||
|
||
if (file.isDirectory() && !file.isSymbolicLink()) { | ||
await this.#watchFolder(f); | ||
} | ||
} | ||
} | ||
} | ||
} catch (error) { | ||
this.emit('error', error); | ||
} | ||
} | ||
|
||
#watchFile(file) { | ||
if (this.#closed) { | ||
return; | ||
} | ||
|
||
const { watchFile } = lazyLoadFsSync(); | ||
const existingStat = this.#files.get(file); | ||
|
||
watchFile(file, { | ||
persistent: this.#options.persistent, | ||
}, (currentStats, previousStats) => { | ||
if (existingStat && !existingStat.isDirectory() && | ||
currentStats.nlink !== 0 && existingStat.mtimeMs === currentStats.mtimeMs) { | ||
return; | ||
} | ||
|
||
this.#files.set(file, currentStats); | ||
|
||
if (currentStats.birthtimeMs === 0 && previousStats.birthtimeMs !== 0) { | ||
// The file is now deleted | ||
this.#files.delete(file); | ||
this.emit('change', 'rename', path.relative(this.#rootPath, file)); | ||
this.#unwatchFiles(file); | ||
} else if (file === this.#rootPath && this.#watchingFile) { | ||
// This case will only be triggered when watching a file with fs.watch | ||
this.emit('change', 'change', path.basename(file)); | ||
} else if (this.#symbolicFiles.has(file)) { | ||
// Stats from watchFile does not return correct value for currentStats.isSymbolicLink() | ||
// Since it is only valid when using fs.lstat(). Therefore, check the existing symbolic files. | ||
this.emit('change', 'rename', path.relative(this.#rootPath, file)); | ||
} else if (currentStats.isDirectory()) { | ||
this.#watchFolder(file); | ||
} | ||
}); | ||
} | ||
|
||
[kFSWatchStart](filename) { | ||
filename = path.resolve(getValidatedPath(filename)); | ||
|
||
try { | ||
const file = lazyLoadFsSync().statSync(filename); | ||
|
||
this.#rootPath = filename; | ||
this.#closed = false; | ||
this.#watchingFile = file.isFile(); | ||
|
||
if (file.isDirectory()) { | ||
this.#files.set(filename, file); | ||
|
||
PromisePrototypeThen( | ||
traverse(filename, this.#files, this.#symbolicFiles), | ||
() => { | ||
for (const f of this.#files.keys()) { | ||
this.#watchFile(f); | ||
} | ||
}, | ||
); | ||
} else { | ||
this.#watchFile(filename); | ||
} | ||
} catch (error) { | ||
if (error.code === 'ENOENT') { | ||
error.filename = filename; | ||
throw error; | ||
} | ||
} | ||
|
||
} | ||
|
||
ref() { | ||
this.#files.forEach((file) => { | ||
if (file instanceof StatWatcher) { | ||
file.ref(); | ||
} | ||
}); | ||
} | ||
|
||
unref() { | ||
this.#files.forEach((file) => { | ||
if (file instanceof StatWatcher) { | ||
file.unref(); | ||
} | ||
}); | ||
} | ||
|
||
[SymbolAsyncIterator]() { | ||
const { signal } = this.#options; | ||
const promiseExecutor = signal == null ? | ||
(resolve) => { | ||
this.once('change', (eventType, filename) => { | ||
resolve({ __proto__: null, value: { eventType, filename } }); | ||
}); | ||
} : (resolve, reject) => { | ||
const onAbort = () => reject(new AbortError(undefined, { cause: signal.reason })); | ||
if (signal.aborted) return onAbort(); | ||
signal.addEventListener('abort', onAbort, { __proto__: null, once: true }); | ||
this.once('change', (eventType, filename) => { | ||
signal.removeEventListener('abort', onAbort); | ||
resolve({ __proto__: null, value: { eventType, filename } }); | ||
}); | ||
}; | ||
return { | ||
next: () => (this.#closed ? | ||
{ __proto__: null, done: true } : | ||
new Promise(promiseExecutor)), | ||
[SymbolAsyncIterator]() { return this; }, | ||
}; | ||
} | ||
} | ||
|
||
module.exports = { | ||
FSWatcher, | ||
kFSWatchStart, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
|
||
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/promises'); | ||
const fsSync = require('fs'); | ||
|
||
const tmpdir = require('../common/tmpdir'); | ||
const testDir = tmpdir.path; | ||
tmpdir.refresh(); | ||
|
||
(async function run() { | ||
// Add a file to already watching folder | ||
|
||
const testsubdir = await fs.mkdtemp(testDir + path.sep); | ||
const file = '1.txt'; | ||
const filePath = path.join(testsubdir, file); | ||
const watcher = fs.watch(testsubdir, { recursive: true }); | ||
|
||
let interval; | ||
|
||
process.on('exit', function() { | ||
assert.ok(interval === null, 'watcher Object was not closed'); | ||
}); | ||
|
||
process.nextTick(common.mustCall(() => { | ||
interval = setInterval(() => { | ||
fsSync.writeFileSync(filePath, 'world'); | ||
}, 500); | ||
})); | ||
|
||
for await (const payload of watcher) { | ||
const { eventType, filename } = payload; | ||
|
||
assert.ok(eventType === 'change' || eventType === 'rename'); | ||
|
||
if (filename === file) { | ||
break; | ||
} | ||
} | ||
|
||
clearInterval(interval); | ||
interval = null; | ||
})().then(common.mustCall()); | ||
|
||
(async function() { | ||
// Test that aborted AbortSignal are reported. | ||
const testsubdir = await fs.mkdtemp(testDir + path.sep); | ||
const error = new Error(); | ||
const watcher = fs.watch(testsubdir, { recursive: true, signal: AbortSignal.abort(error) }); | ||
await assert.rejects(async () => { | ||
// eslint-disable-next-line no-unused-vars | ||
for await (const _ of watcher); | ||
}, { code: 'ABORT_ERR', cause: error }); | ||
})().then(common.mustCall()); | ||
|
||
(async function() { | ||
// Test that with AbortController. | ||
const testsubdir = await fs.mkdtemp(testDir + path.sep); | ||
const file = '2.txt'; | ||
const filePath = path.join(testsubdir, file); | ||
const error = new Error(); | ||
const ac = new AbortController(); | ||
const watcher = fs.watch(testsubdir, { recursive: true, signal: ac.signal }); | ||
let interval; | ||
process.on('exit', function() { | ||
assert.ok(interval === null, 'watcher Object was not closed'); | ||
}); | ||
process.nextTick(common.mustCall(() => { | ||
interval = setInterval(() => { | ||
fsSync.writeFileSync(filePath, 'world'); | ||
}, 50); | ||
ac.abort(error); | ||
})); | ||
await assert.rejects(async () => { | ||
for await (const { eventType } of watcher) { | ||
assert.ok(eventType === 'change' || eventType === 'rename'); | ||
} | ||
}, { code: 'ABORT_ERR', cause: error }); | ||
clearInterval(interval); | ||
interval = null; | ||
})().then(common.mustCall()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
'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); | ||
|
||
|
||
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'); | ||
|
||
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()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,227 @@ | ||
'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 { pathToFileURL } = require('url'); | ||
|
||
const tmpdir = require('../common/tmpdir'); | ||
|
||
const testDir = tmpdir.path; | ||
const filenameOne = 'watch.txt'; | ||
|
||
tmpdir.refresh(); | ||
|
||
const testsubdir = fs.mkdtempSync(testDir + path.sep); | ||
const relativePathOne = path.join(path.basename(testsubdir), filenameOne); | ||
const filepathOne = path.join(testsubdir, filenameOne); | ||
(async () => { | ||
// Add a file to already watching folder | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-1'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const testFile = path.join(testDirectory, 'file-1.txt'); | ||
|
||
const watcher = fs.watch(testDirectory, { recursive: true }); | ||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'rename'); | ||
|
||
if (filename === path.basename(testFile)) { | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
await setTimeout(common.platformTimeout(100)); | ||
fs.writeFileSync(testFile, 'world'); | ||
|
||
process.once('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
(async () => { | ||
// Add a folder to already watching folder | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-2'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const testFile = path.join(testDirectory, 'folder-2'); | ||
|
||
const watcher = fs.watch(testDirectory, { recursive: true }); | ||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'rename'); | ||
|
||
if (filename === path.basename(testFile)) { | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
await setTimeout(common.platformTimeout(100)); | ||
fs.mkdirSync(testFile); | ||
|
||
process.once('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
(async () => { | ||
// Add a file to newly created folder to already watching folder | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-3'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const filePath = path.join(testDirectory, 'folder-3'); | ||
|
||
const childrenFile = 'file-4.txt'; | ||
const childrenAbsolutePath = path.join(filePath, childrenFile); | ||
const childrenRelativePath = path.join(path.basename(filePath), childrenFile); | ||
|
||
const watcher = fs.watch(testDirectory, { recursive: true }); | ||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'rename'); | ||
assert.ok(filename === path.basename(filePath) || filename === childrenRelativePath); | ||
|
||
if (filename === childrenRelativePath) { | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
await setTimeout(common.platformTimeout(100)); | ||
fs.mkdirSync(filePath); | ||
await setTimeout(common.platformTimeout(100)); | ||
fs.writeFileSync(childrenAbsolutePath, 'world'); | ||
|
||
process.once('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
(async () => { | ||
// Add a file to subfolder of a watching folder | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-4'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const file = 'folder-5'; | ||
const filePath = path.join(testDirectory, file); | ||
fs.mkdirSync(filePath); | ||
|
||
const subfolderPath = path.join(filePath, 'subfolder-6'); | ||
fs.mkdirSync(subfolderPath); | ||
|
||
const childrenFile = 'file-7.txt'; | ||
const childrenAbsolutePath = path.join(subfolderPath, childrenFile); | ||
const relativePath = path.join(file, path.basename(subfolderPath), childrenFile); | ||
|
||
const watcher = fs.watch(testDirectory, { recursive: true }); | ||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'rename'); | ||
|
||
if (filename === relativePath) { | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
await setTimeout(common.platformTimeout(100)); | ||
fs.writeFileSync(childrenAbsolutePath, 'world'); | ||
|
||
process.once('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
(async () => { | ||
// Add a file to already watching folder, and use URL as the path | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-5'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const filePath = path.join(testDirectory, 'file-8.txt'); | ||
const url = pathToFileURL(testDirectory); | ||
|
||
const watcher = fs.watch(url, { recursive: true }); | ||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'rename'); | ||
|
||
if (filename === path.basename(filePath)) { | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
await setTimeout(common.platformTimeout(100)); | ||
fs.writeFileSync(filePath, 'world'); | ||
|
||
process.on('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
(async () => { | ||
// Watch a file (not a folder) using fs.watch | ||
|
||
const rootDirectory = fs.mkdtempSync(testDir + path.sep); | ||
const testDirectory = path.join(rootDirectory, 'test-6'); | ||
fs.mkdirSync(testDirectory); | ||
|
||
const filePath = path.join(testDirectory, 'only-file.txt'); | ||
fs.writeFileSync(filePath, 'hello'); | ||
|
||
const watcher = fs.watch(filePath, { recursive: true }); | ||
let watcherClosed = false; | ||
let interval; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'change'); | ||
|
||
if (filename === path.basename(filePath)) { | ||
clearInterval(interval); | ||
interval = null; | ||
watcher.close(); | ||
watcherClosed = true; | ||
} | ||
}); | ||
|
||
if (!common.isOSX && !common.isWindows) { | ||
assert.throws(() => { fs.watch(testDir, { recursive: true }); }, | ||
{ code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM' }); | ||
return; | ||
} | ||
const watcher = fs.watch(testDir, { recursive: true }); | ||
interval = setInterval(() => { | ||
fs.writeFileSync(filePath, 'world'); | ||
}, common.platformTimeout(10)); | ||
|
||
let watcherClosed = false; | ||
watcher.on('change', function(event, filename) { | ||
assert.ok(event === 'change' || event === 'rename'); | ||
process.on('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
assert.ok(interval === null, 'interval should have been null'); | ||
}); | ||
})().then(common.mustCall()); | ||
|
||
// Ignore stale events generated by mkdir and other tests | ||
if (filename !== relativePathOne) | ||
return; | ||
(async () => { | ||
// Handle non-boolean values for options.recursive | ||
|
||
if (common.isOSX) { | ||
clearInterval(interval); | ||
if (!common.isWindows && !common.isOSX) { | ||
assert.throws(() => { | ||
const testsubdir = fs.mkdtempSync(testDir + path.sep); | ||
fs.watch(testsubdir, { recursive: '1' }); | ||
}, { | ||
code: 'ERR_INVALID_ARG_TYPE', | ||
}); | ||
} | ||
watcher.close(); | ||
watcherClosed = true; | ||
}); | ||
|
||
let interval; | ||
if (common.isOSX) { | ||
interval = setInterval(function() { | ||
fs.writeFileSync(filepathOne, 'world'); | ||
}, 10); | ||
} else { | ||
fs.writeFileSync(filepathOne, 'world'); | ||
} | ||
|
||
process.on('exit', function() { | ||
assert(watcherClosed, 'watcher Object was not closed'); | ||
}); | ||
})().then(common.mustCall()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters