diff options
author | James M Snell <jasnell@gmail.com> | 2021-02-02 02:53:36 +0300 |
---|---|---|
committer | Danielle Adams <adamzdanielle@gmail.com> | 2021-02-16 17:16:19 +0300 |
commit | 88d3f74c85eaa62b4d721a880cb6bc8ea2237bc3 (patch) | |
tree | 11ad4a45a8e1ed6ee6940d545b7aa11ebf902b85 /test | |
parent | b2b64113b19074691084700a6a9a6b1cede93435 (diff) |
fs: add fsPromises.watch()
An alternative to `fs.watch()` that returns an `AsyncIterator`
```js
const { watch } = require('fs/promises');
(async () => {
const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 10000);
const watcher = watch('file.txt', { signal });
for await (const { eventType, filename } of watcher) {
console.log(eventType, filename);
}
})()
```
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/37179
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Diffstat (limited to 'test')
-rw-r--r-- | test/parallel/test-bootstrap-modules.js | 3 | ||||
-rw-r--r-- | test/parallel/test-fs-promises-watch.js | 115 |
2 files changed, 118 insertions, 0 deletions
diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 4d47de41c29..236c95457fe 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -17,6 +17,7 @@ const expectedModules = new Set([ 'Internal Binding credentials', 'Internal Binding fs', 'Internal Binding fs_dir', + 'Internal Binding fs_event_wrap', 'Internal Binding messaging', 'Internal Binding module_wrap', 'Internal Binding native_module', @@ -31,6 +32,7 @@ const expectedModules = new Set([ 'Internal Binding types', 'Internal Binding url', 'Internal Binding util', + 'Internal Binding uv', 'Internal Binding worker', 'NativeModule buffer', 'NativeModule events', @@ -51,6 +53,7 @@ const expectedModules = new Set([ 'NativeModule internal/fs/utils', 'NativeModule internal/fs/promises', 'NativeModule internal/fs/rimraf', + 'NativeModule internal/fs/watchers', 'NativeModule internal/idna', 'NativeModule internal/linkedlist', 'NativeModule internal/modules/run_main', diff --git a/test/parallel/test-fs-promises-watch.js b/test/parallel/test-fs-promises-watch.js new file mode 100644 index 00000000000..4603b934c4b --- /dev/null +++ b/test/parallel/test-fs-promises-watch.js @@ -0,0 +1,115 @@ +'use strict'; +const common = require('../common'); + +if (common.isIBMi) + common.skip('IBMi does not support `fs.watch()`'); + +const { watch } = require('fs/promises'); +const fs = require('fs'); +const assert = require('assert'); +const { join } = require('path'); +const tmpdir = require('../common/tmpdir'); + +class WatchTestCase { + constructor(shouldInclude, dirName, fileName, field) { + this.dirName = dirName; + this.fileName = fileName; + this.field = field; + this.shouldSkip = !shouldInclude; + } + get dirPath() { return join(tmpdir.path, this.dirName); } + get filePath() { return join(this.dirPath, this.fileName); } +} + +const kCases = [ + // Watch on a directory should callback with a filename on supported systems + new WatchTestCase( + common.isLinux || common.isOSX || common.isWindows || common.isAIX, + 'watch1', + 'foo', + 'filePath' + ), + // Watch on a file should callback with a filename on supported systems + new WatchTestCase( + common.isLinux || common.isOSX || common.isWindows, + 'watch2', + 'bar', + 'dirPath' + ) +]; + +tmpdir.refresh(); + +for (const testCase of kCases) { + if (testCase.shouldSkip) continue; + fs.mkdirSync(testCase.dirPath); + // Long content so it's actually flushed. + const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4); + fs.writeFileSync(testCase.filePath, content1); + + async function test() { + const watcher = watch(testCase[testCase.field]); + for await (const { eventType, filename } of watcher) { + assert.strictEqual(['rename', 'change'].includes(eventType), true); + assert.strictEqual(filename, testCase.fileName); + break; + } + + // Waiting on it again is a non-op + // eslint-disable-next-line no-unused-vars + for await (const p of watcher) { + assert.fail('should not run'); + } + } + + // Long content so it's actually flushed. toUpperCase so there's real change. + const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4); + setImmediate(() => { + fs.writeFileSync(testCase.filePath, ''); + fs.writeFileSync(testCase.filePath, content2); + }); + + test().then(common.mustCall()); +} + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch(1)) {} }, + { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch(__filename, 1)) {} }, + { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch('', { persistent: 1 })) {} }, + { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch('', { recursive: 1 })) {} }, + { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch('', { encoding: 1 })) {} }, + { code: 'ERR_INVALID_ARG_VALUE' }); + +assert.rejects( + // eslint-disable-next-line no-unused-vars + async () => { for await (const _ of watch('', { signal: 1 })) {} }, + { code: 'ERR_INVALID_ARG_TYPE' }); + +(async () => { + const ac = new AbortController(); + const { signal } = ac; + setImmediate(() => ac.abort()); + try { + // eslint-disable-next-line no-unused-vars + for await (const _ of watch(__filename, { signal })) {} + } catch (err) { + assert.strictEqual(err.name, 'AbortError'); + } +})().then(common.mustCall()); |