aboutsummaryrefslogtreecommitdiff
path: root/test/js/node/watch/fs.watchFile.test.ts
blob: 25cd0c8c54193f1002b30c83d9f2789b093bab3f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import fs, { FSWatcher } from "node:fs";
import path from "path";
import { tempDirWithFiles, bunRun, bunRunAsScript } from "harness";
import { pathToFileURL } from "bun";

import { describe, expect, test } from "bun:test";
// Because macOS (and possibly other operating systems) can return a watcher
// before it is actually watching, we need to repeat the operation to avoid
// a race condition.
function repeat(fn: any) {
  const interval = setInterval(fn, 20);
  return interval;
}
const encodingFileName = `新建文夹件.txt`;
const testDir = tempDirWithFiles("watch", {
  "watch.txt": "hello",
  [encodingFileName]: "hello",
});

describe("fs.watchFile", () => {
  test("zeroed stats if does not exist", async () => {
    let entries: any = [];
    fs.watchFile(path.join(testDir, "does-not-exist"), (curr, prev) => {
      entries.push([curr, prev]);
    });

    await Bun.sleep(35);

    fs.unwatchFile(path.join(testDir, "does-not-exist"));

    expect(entries.length).toBe(1);
    expect(entries[0][0].size).toBe(0);
    expect(entries[0][0].mtimeMs).toBe(0);
    expect(entries[0][1].size).toBe(0);
    expect(entries[0][1].mtimeMs).toBe(0);
  });
  test("it watches a file", async () => {
    let entries: any = [];
    fs.watchFile(path.join(testDir, "watch.txt"), { interval: 50 }, (curr, prev) => {
      entries.push([curr, prev]);
    });
    await Bun.sleep(100);
    fs.writeFileSync(path.join(testDir, "watch.txt"), "hello2");
    await Bun.sleep(100);

    fs.unwatchFile(path.join(testDir, "watch.txt"));

    expect(entries.length).toBeGreaterThan(0);

    expect(entries[0][0].size).toBe(6);
    expect(entries[0][1].size).toBe(5);
    expect(entries[0][0].mtimeMs).toBeGreaterThan(entries[0][1].mtimeMs);
  });
  test("unicode file name", async () => {
    let entries: any = [];
    fs.watchFile(path.join(testDir, encodingFileName), { interval: 50 }, (curr, prev) => {
      entries.push([curr, prev]);
    });
    await Bun.sleep(100);
    fs.writeFileSync(path.join(testDir, encodingFileName), "hello2");
    await Bun.sleep(100);

    fs.unwatchFile(path.join(testDir, encodingFileName));

    expect(entries.length).toBeGreaterThan(0);

    expect(entries[0][0].size).toBe(6);
    expect(entries[0][1].size).toBe(5);
    expect(entries[0][0].mtimeMs).toBeGreaterThan(entries[0][1].mtimeMs);
  });
  test("bigint stats", async () => {
    let entries: any = [];
    fs.watchFile(path.join(testDir, encodingFileName), { interval: 50, bigint: true }, (curr, prev) => {
      entries.push([curr, prev]);
    });
    await Bun.sleep(100);
    fs.writeFileSync(path.join(testDir, encodingFileName), "hello2");
    await Bun.sleep(100);

    fs.unwatchFile(path.join(testDir, encodingFileName));

    expect(entries.length).toBeGreaterThan(0);

    expect(typeof entries[0][0].mtimeMs === "bigint").toBe(true);
  });

  test("StatWatcherScheduler stress test (1000 watchers with random times)", async () => {
    const EventEmitter = require("events");
    let defaultMaxListeners = EventEmitter.defaultMaxListeners;
    try {
      EventEmitter.defaultMaxListeners = 1000;
      // This tests StatWatcher's scheduler for add/remove race conditions,
      // as the actual stat()ing is done on another thread using a specialized linked list implementation
      // so we're testing that here, less so that stats will properly notify js, since that code is already known to be very threadsafe.
      const set = new Set<string>();
      const { promise, resolve } = Promise.withResolvers();
      for (let i = 0; i < 1000; i++) {
        const file = path.join(testDir, i + ".txt");
        setTimeout(() => {
          let first = true;
          fs.watchFile(file, { interval: 500 }, (curr, prev) => {
            set.add(file);
            if (first) {
              first = false;
              setTimeout(() => {
                fs.unwatchFile(file);

                if (set.size === 1000) resolve();
              }, Math.random() * 2000);
            }
          });
        }, Math.random() * 2000);
      }
      await promise;

      expect(set.size).toBe(1000);
    } finally {
      EventEmitter.defaultMaxListeners = defaultMaxListeners;
    }
  }, 20000);
});