aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGravatar dave caruso <me@paperdave.net> 2023-09-05 17:41:39 -0700
committerGravatar GitHub <noreply@github.com> 2023-09-05 17:41:39 -0700
commitacfd028e8f859a0e8139b7adab5d319e326c2373 (patch)
treef6bf98b2e40fcbcc036348bf7e0556e9edfa3b4e /test
parent6f8a3934923198cbadae64cda24201e2de2655c1 (diff)
downloadbun-acfd028e8f859a0e8139b7adab5d319e326c2373.tar.gz
bun-acfd028e8f859a0e8139b7adab5d319e326c2373.tar.zst
bun-acfd028e8f859a0e8139b7adab5d319e326c2373.zip
feat(runtime): Implement `fs.watchFile` (#4467)
* really lame prototype * uses threads but badly * it works i guess * unwatchFile but lame * it works * test * a * aomitcs * fix unwatching race condition * use hasPendingActivity and GC stuff better * test * revert this
Diffstat (limited to 'test')
-rw-r--r--test/js/node/watch/fs.watchFile.test.ts121
1 files changed, 121 insertions, 0 deletions
diff --git a/test/js/node/watch/fs.watchFile.test.ts b/test/js/node/watch/fs.watchFile.test.ts
new file mode 100644
index 000000000..25cd0c8c5
--- /dev/null
+++ b/test/js/node/watch/fs.watchFile.test.ts
@@ -0,0 +1,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);
+});