add a few more tests

This commit is contained in:
Nystik
2026-04-09 01:02:53 +02:00
parent 2ebc1ed434
commit 70a94af62a
2 changed files with 367 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
import { describe, it, expect, vi } from "vitest";
import { ContentCache } from "./content-cache.js";
// -- Size accounting ---------------------------------------------------
describe("ContentCache size accounting", () => {
it("set increases currentBytes by data length", () => {
const cache = new ContentCache(1024);
cache.set("a.md", "hello"); // 5 bytes
expect(cache.currentBytes).toBe(5);
});
it("delete returns currentBytes to 0", () => {
const cache = new ContentCache(1024);
cache.set("a.md", "hello");
cache.delete("a.md");
expect(cache.currentBytes).toBe(0);
});
it("replacing an entry reflects the new size, not old + new", () => {
const cache = new ContentCache(1024);
cache.set("a.md", "short");
cache.set("a.md", "a much longer string");
expect(cache.currentBytes).toBe("a much longer string".length);
});
it("deleting one of several entries leaves the sum of the rest", () => {
const cache = new ContentCache(1024);
cache.set("a.md", "aaa"); // 3
cache.set("b.md", "bbbbb"); // 5
cache.set("c.md", "cc"); // 2
cache.delete("b.md");
expect(cache.currentBytes).toBe(5); // 3 + 2
});
});
// -- LRU eviction ------------------------------------------------------
describe("ContentCache LRU eviction", () => {
it("evicts the least-recently-accessed entry when full", () => {
let now = 1000;
vi.spyOn(Date, "now").mockImplementation(() => now++);
const cache = new ContentCache(10);
cache.set("a.md", "aaaa"); // 4
cache.set("b.md", "bbbb"); // 4
// At 8/10. Adding 4 more would exceed, so LRU (a.md) should be evicted.
cache.set("c.md", "cccc"); // 4
expect(cache.has("a.md")).toBe(false);
expect(cache.has("b.md")).toBe(true);
expect(cache.has("c.md")).toBe(true);
vi.restoreAllMocks();
});
it("accessing an entry refreshes it so it survives eviction", () => {
let now = 1000;
vi.spyOn(Date, "now").mockImplementation(() => now++);
const cache = new ContentCache(10);
cache.set("a.md", "aaaa"); // 4, accessedAt=1000
cache.set("b.md", "bbbb"); // 4, accessedAt=1001
// Touch a.md so b.md becomes the LRU
cache.get("a.md"); // a.md accessedAt=1002
cache.set("c.md", "cccc"); // 4 -- should evict b.md (1001), not a.md (1002)
expect(cache.has("a.md")).toBe(true);
expect(cache.has("b.md")).toBe(false);
expect(cache.has("c.md")).toBe(true);
vi.restoreAllMocks();
});
it("entry larger than maxSize still gets stored", () => {
const cache = new ContentCache(5);
cache.set("small.md", "ab"); // 2
cache.set("big.md", "abcdefghij"); // 10 -- larger than maxSize
expect(cache.has("small.md")).toBe(false);
expect(cache.has("big.md")).toBe(true);
expect(cache.currentBytes).toBe(10);
});
});
// -- Path normalization ------------------------------------------------
describe("ContentCache path normalization", () => {
it("backslash and slash variants hit the same cache entry", () => {
const cache = new ContentCache(1024);
cache.set("foo\\bar\\baz.md", "content");
expect(cache.has("foo/bar/baz.md")).toBe(true);
expect(cache.get("foo/bar/baz.md")).toBe("content");
});
});

275
src/shims/fs/fd.test.js Normal file
View File

@@ -0,0 +1,275 @@
import { describe, it, expect } from "vitest";
import { createFdOps } from "./fd.js";
function makeStubs(files = {}) {
const meta = {
has(p) {
return p in files;
},
toStat(p) {
if (!(p in files)) {
return null;
}
return {
size: files[p].length,
isFile: () => true,
isDirectory: () => false,
};
},
};
const content = {
_store: {},
get(p) {
return this._store[p] ?? null;
},
set(p, data) {
this._store[p] = data;
},
};
const transport = {
readFileSync(p) {
return files[p] ?? null;
},
};
// Pre-populate content cache so ensureData doesn't hit transport
for (const [p, data] of Object.entries(files)) {
content.set(p, data);
}
return { meta, content, transport };
}
function makeOps(files = {}) {
const { meta, content, transport } = makeStubs(files);
return createFdOps(meta, content, transport);
}
// -- openSync / closeSync lifecycle ------------------------------------
describe("fd openSync / closeSync lifecycle", () => {
it("open returns an integer fd", () => {
const ops = makeOps({ "a.md": new Uint8Array([1, 2, 3]) });
const fd = ops.openSync("a.md", "r");
expect(typeof fd).toBe("number");
expect(Number.isInteger(fd)).toBe(true);
});
it("multiple opens return distinct fds", () => {
const ops = makeOps({ "a.md": new Uint8Array([1]) });
const fd1 = ops.openSync("a.md", "r");
const fd2 = ops.openSync("a.md", "r");
expect(fd1).not.toBe(fd2);
});
it("close removes the fd", () => {
const ops = makeOps({ "a.md": new Uint8Array([1]) });
const fd = ops.openSync("a.md", "r");
ops.closeSync(fd);
expect(() => ops.readSync(fd, new Uint8Array(1), 0, 1, 0)).toThrow(
"EBADF",
);
});
it("open throws ENOENT for missing path", () => {
const ops = makeOps({});
expect(() => ops.openSync("nope.md", "r")).toThrow("ENOENT");
});
it("accessing a closed fd throws EBADF", () => {
const ops = makeOps({ "a.md": new Uint8Array([1]) });
const fd = ops.openSync("a.md", "r");
ops.closeSync(fd);
expect(() => ops.fstatSync(fd)).toThrow("EBADF");
});
});
// -- readSync ----------------------------------------------------------
describe("fd readSync", () => {
const data = new Uint8Array([10, 20, 30, 40, 50]);
function openData() {
const ops = makeOps({ "f.bin": data });
const fd = ops.openSync("f.bin", "r");
return { ops, fd };
}
it("read from position 0, full length", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(5);
const n = ops.readSync(fd, buf, 0, 5, 0);
expect(n).toBe(5);
expect(buf).toEqual(data);
});
it("read from mid-file position", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(3);
const n = ops.readSync(fd, buf, 0, 3, 2);
expect(n).toBe(3);
expect(buf).toEqual(new Uint8Array([30, 40, 50]));
});
it("read with length exceeding remaining data", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(10);
const n = ops.readSync(fd, buf, 0, 10, 3);
expect(n).toBe(2);
expect(buf[0]).toBe(40);
expect(buf[1]).toBe(50);
});
it("read at position === data.length returns 0 bytes", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(5);
const n = ops.readSync(fd, buf, 0, 5, 5);
expect(n).toBe(0);
});
it("read at position > data.length returns 0 bytes", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(5);
const n = ops.readSync(fd, buf, 0, 5, 100);
expect(n).toBe(0);
});
it("read with offset > 0 places data at correct position", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(6);
buf.fill(0);
const n = ops.readSync(fd, buf, 3, 2, 0);
expect(n).toBe(2);
expect(buf).toEqual(new Uint8Array([0, 0, 0, 10, 20, 0]));
});
it("target buffer is actually modified", () => {
const { ops, fd } = openData();
const buf = new Uint8Array(3);
buf.fill(255);
ops.readSync(fd, buf, 0, 2, 0);
expect(buf[0]).toBe(10);
expect(buf[1]).toBe(20);
expect(buf[2]).toBe(255); // untouched
});
});
// -- fstatSync ---------------------------------------------------------
describe("fd fstatSync", () => {
it("returns metadata from cache when available", () => {
const ops = makeOps({ "a.md": new Uint8Array([1, 2, 3]) });
const fd = ops.openSync("a.md", "r");
const stat = ops.fstatSync(fd);
expect(stat.size).toBe(3);
expect(stat.isFile()).toBe(true);
expect(stat.isDirectory()).toBe(false);
});
it("falls back to buffer length when metadata is missing", () => {
const { meta, content, transport } = makeStubs({
"a.md": new Uint8Array([1, 2, 3, 4]),
});
// Override toStat to return null, simulating missing metadata
meta.toStat = () => null;
const ops = createFdOps(meta, content, transport);
const fd = ops.openSync("a.md", "r");
const stat = ops.fstatSync(fd);
expect(stat.size).toBe(4);
expect(stat.isFile()).toBe(true);
});
});
// -- Async wrappers ----------------------------------------------------
describe("fd async wrappers", () => {
it("open() calls callback asynchronously", async () => {
const ops = makeOps({ "a.md": new Uint8Array([1]) });
let called = false;
const promise = new Promise((resolve, reject) => {
ops.open("a.md", "r", (err, fd) => {
called = true;
if (err) {
reject(err);
} else {
resolve(fd);
}
});
});
// Callback should not have fired synchronously
expect(called).toBe(false);
const fd = await promise;
expect(typeof fd).toBe("number");
});
it("open() error path calls cb(err)", async () => {
const ops = makeOps({});
const err = await new Promise((resolve) => {
ops.open("nope.md", "r", (e) => resolve(e));
});
expect(err).toBeTruthy();
expect(err.code).toBe("ENOENT");
});
it("read() delivers results via callback", async () => {
const ops = makeOps({ "a.md": new Uint8Array([10, 20]) });
const fd = await new Promise((resolve, reject) => {
ops.open("a.md", "r", (err, fd) => (err ? reject(err) : resolve(fd)));
});
const buf = new Uint8Array(2);
const bytesRead = await new Promise((resolve, reject) => {
ops.read(fd, buf, 0, 2, 0, (err, n) =>
err ? reject(err) : resolve(n),
);
});
expect(bytesRead).toBe(2);
expect(buf).toEqual(new Uint8Array([10, 20]));
});
it("close() calls callback asynchronously", async () => {
const ops = makeOps({ "a.md": new Uint8Array([1]) });
const fd = ops.openSync("a.md", "r");
const result = await new Promise((resolve) => {
ops.close(fd, (err) => resolve(err));
});
expect(result).toBe(null);
});
it("fstat() calls callback asynchronously", async () => {
const ops = makeOps({ "a.md": new Uint8Array([1, 2, 3]) });
const fd = ops.openSync("a.md", "r");
const stat = await new Promise((resolve, reject) => {
ops.fstat(fd, (err, s) => (err ? reject(err) : resolve(s)));
});
expect(stat.size).toBe(3);
});
it("fstat() error path calls cb(err) for bad fd", async () => {
const ops = makeOps({});
const err = await new Promise((resolve) => {
ops.fstat(99999, (e) => resolve(e));
});
expect(err).toBeTruthy();
expect(err.code).toBe("EBADF");
});
});