mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
a7004c7df1
Reuse the positional range owner for target metadata, Hopper Mach-O selection and Kuna spans. Preserve physical failures, completed span evidence and cleanup ownership across cancellation and short reads. Validated 147 focused file and lifecycle tests; ten new cases reproduced failures against the prior implementation.
90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
import { open, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect, it, onTestFinished, vi } from "vitest";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { readFileHandleRange } from "../../../src/filesystem/readFileHandleRange.js";
|
|
|
|
const source = async () => {
|
|
const root = await createTestTempDirectory("rea-file-window-");
|
|
const path = join(root, "bytes");
|
|
await writeFile(path, "0123456789");
|
|
const handle = await open(path, "r");
|
|
onTestFinished(() => handle.close());
|
|
const read = handle.read.bind(handle);
|
|
const spy = vi.spyOn(handle, "read").mockImplementation((async (
|
|
...args: unknown[]
|
|
) => {
|
|
args[2] = Math.min(args[2] as number, 2);
|
|
return Reflect.apply(read, handle, args);
|
|
}) as typeof handle.read);
|
|
return { handle, spy };
|
|
};
|
|
|
|
it("fills positional windows across positive short reads and stops only at EOF", async () => {
|
|
const { handle, spy } = await source();
|
|
expect((await readFileHandleRange(handle, 3, 5)).toString()).toBe("34567");
|
|
expect(spy.mock.calls.map((call) => (call as unknown[])[3])).toEqual([
|
|
3, 5, 7,
|
|
]);
|
|
expect((await readFileHandleRange(handle, 8, 5)).toString()).toBe("89");
|
|
expect(await readFileHandleRange(handle, 12, 2)).toHaveLength(0);
|
|
// The helper borrows the descriptor; it remains usable after EOF.
|
|
expect((await handle.stat()).size).toBe(10);
|
|
});
|
|
|
|
it("validates ranges and checks cancellation even for empty windows", async () => {
|
|
const { handle, spy } = await source();
|
|
expect(await readFileHandleRange(handle, 2, 0)).toHaveLength(0);
|
|
expect(spy).not.toHaveBeenCalled();
|
|
for (const [offset, length] of [
|
|
[-1, 2],
|
|
[0, -1],
|
|
[0.5, 2],
|
|
[Number.MAX_SAFE_INTEGER, 1],
|
|
])
|
|
await expect(readFileHandleRange(handle, offset!, length!)).rejects.toThrow(
|
|
RangeError,
|
|
);
|
|
const cancelled = new Error("cancelled");
|
|
await expect(
|
|
readFileHandleRange(handle, 0, 0, () => {
|
|
throw cancelled;
|
|
}),
|
|
).rejects.toBe(cancelled);
|
|
});
|
|
|
|
it("checks cancellation after each fulfilled read and retains native failures", async () => {
|
|
const { handle, spy } = await source();
|
|
const cancelled = new Error("cancelled");
|
|
await expect(
|
|
readFileHandleRange(handle, 0, 4, () => {
|
|
if (spy.mock.calls.length > 0) throw cancelled;
|
|
}),
|
|
).rejects.toBe(cancelled);
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const native = new Error("native I/O failure");
|
|
spy.mockRejectedValueOnce(native);
|
|
await expect(readFileHandleRange(handle, 0, 4)).rejects.toBe(native);
|
|
});
|
|
|
|
it("returns explicit EOF for the caller to interpret before late cancellation", async () => {
|
|
const { handle, spy } = await source();
|
|
expect(
|
|
await readFileHandleRange(handle, 10, 4, () => {
|
|
if (spy.mock.calls.length > 0) throw new Error("late cancellation");
|
|
}),
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it("reports complete acquisition so the owner can finalize bytes after a late abort", async () => {
|
|
const { handle, spy } = await source();
|
|
const checkpoints: number[] = [];
|
|
const bytes = await readFileHandleRange(handle, 0, 4, (acquired) => {
|
|
checkpoints.push(acquired);
|
|
if (spy.mock.calls.length >= 2 && acquired < 4)
|
|
throw new Error("cancelled before full acquisition");
|
|
});
|
|
expect(bytes.toString()).toBe("0123");
|
|
expect(checkpoints).toEqual([0, 0, 2, 2, 4]);
|
|
});
|