• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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