1// Flags: --experimental-abortcontroller 2'use strict'; 3const common = require('../common'); 4 5if (common.isIBMi) 6 common.skip('IBMi does not support `fs.watch()`'); 7 8const { watch } = require('fs/promises'); 9const fs = require('fs'); 10const assert = require('assert'); 11const { join } = require('path'); 12const tmpdir = require('../common/tmpdir'); 13 14class WatchTestCase { 15 constructor(shouldInclude, dirName, fileName, field) { 16 this.dirName = dirName; 17 this.fileName = fileName; 18 this.field = field; 19 this.shouldSkip = !shouldInclude; 20 } 21 get dirPath() { return join(tmpdir.path, this.dirName); } 22 get filePath() { return join(this.dirPath, this.fileName); } 23} 24 25const kCases = [ 26 // Watch on a directory should callback with a filename on supported systems 27 new WatchTestCase( 28 common.isLinux || common.isOSX || common.isWindows || common.isAIX, 29 'watch1', 30 'foo', 31 'filePath' 32 ), 33 // Watch on a file should callback with a filename on supported systems 34 new WatchTestCase( 35 common.isLinux || common.isOSX || common.isWindows, 36 'watch2', 37 'bar', 38 'dirPath' 39 ), 40]; 41 42tmpdir.refresh(); 43 44for (const testCase of kCases) { 45 if (testCase.shouldSkip) continue; 46 fs.mkdirSync(testCase.dirPath); 47 // Long content so it's actually flushed. 48 const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4); 49 fs.writeFileSync(testCase.filePath, content1); 50 51 async function test() { 52 const watcher = watch(testCase[testCase.field]); 53 for await (const { eventType, filename } of watcher) { 54 assert.strictEqual(['rename', 'change'].includes(eventType), true); 55 assert.strictEqual(filename, testCase.fileName); 56 break; 57 } 58 59 // Waiting on it again is a non-op 60 // eslint-disable-next-line no-unused-vars 61 for await (const p of watcher) { 62 assert.fail('should not run'); 63 } 64 } 65 66 // Long content so it's actually flushed. toUpperCase so there's real change. 67 const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4); 68 setImmediate(() => { 69 fs.writeFileSync(testCase.filePath, ''); 70 fs.writeFileSync(testCase.filePath, content2); 71 }); 72 73 test().then(common.mustCall()); 74} 75 76assert.rejects( 77 // eslint-disable-next-line no-unused-vars 78 async () => { for await (const _ of watch(1)) {} }, 79 { code: 'ERR_INVALID_ARG_TYPE' }); 80 81assert.rejects( 82 // eslint-disable-next-line no-unused-vars 83 async () => { for await (const _ of watch(__filename, 1)) {} }, 84 { code: 'ERR_INVALID_ARG_TYPE' }); 85 86assert.rejects( 87 // eslint-disable-next-line no-unused-vars 88 async () => { for await (const _ of watch('', { persistent: 1 })) {} }, 89 { code: 'ERR_INVALID_ARG_TYPE' }); 90 91assert.rejects( 92 // eslint-disable-next-line no-unused-vars 93 async () => { for await (const _ of watch('', { recursive: 1 })) {} }, 94 { code: 'ERR_INVALID_ARG_TYPE' }); 95 96assert.rejects( 97 // eslint-disable-next-line no-unused-vars 98 async () => { for await (const _ of watch('', { encoding: 1 })) {} }, 99 { code: 'ERR_INVALID_ARG_VALUE' }); 100 101assert.rejects( 102 // eslint-disable-next-line no-unused-vars 103 async () => { for await (const _ of watch('', { signal: 1 })) {} }, 104 { code: 'ERR_INVALID_ARG_TYPE' }); 105 106(async () => { 107 const ac = new AbortController(); 108 const { signal } = ac; 109 setImmediate(() => ac.abort()); 110 try { 111 // eslint-disable-next-line no-unused-vars 112 for await (const _ of watch(__filename, { signal })) {} 113 } catch (err) { 114 assert.strictEqual(err.name, 'AbortError'); 115 } 116})().then(common.mustCall()); 117