// The virtual file system against the Swift core: every case in // vfs.cases.json runs in Swift (vfs.swift.json, tools/swift-oracle --vfs); // each step's result or error and the final entries must match. The cases // include the three Swift tests on the file system, ported as cases. import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import { VirtualFileSystem, VirtualFileSystemError, normalizedAbsolutePath, type VirtualFileContent, type VirtualFileEntry, } from '../src/index.js'; interface Step { op: string; path?: string; to?: string; name?: string; text?: string; size?: number; byte?: number; mediaType?: string; recursive?: boolean; overwrite?: boolean; entries?: { path: string; kind: string; text?: string }[]; } const here = dirname(fileURLToPath(import.meta.url)); const cases = JSON.parse(readFileSync(join(here, 'vfs.cases.json'), 'utf8')) as { name: string; steps: Step[] }[]; const swift = JSON.parse(readFileSync(join(here, 'vfs.swift.json'), 'utf8')) as { name: string; results: unknown[]; entries: unknown[]; }[]; function describeEntries(entries: VirtualFileEntry[]): unknown[] { return entries.map(({ path, content }) => ({ path, ...describeContent(content) })); } function describeContent(content: VirtualFileContent): Record { switch (content.kind) { case 'directory': return { kind: 'directory' }; case 'text': return { kind: 'text', text: content.text }; case 'binary': case 'image': return { kind: content.kind, bytes: content.data.length, mediaType: content.mediaType ?? null, sha256: createHash('sha256').update(content.data).digest('hex'), }; } } function run(steps: Step[]) { let fileSystem = new VirtualFileSystem(); const results: unknown[] = []; for (const step of steps) { const path = step.path ?? '/'; const bytes = () => new Uint8Array(step.size ?? 0).fill(step.byte ?? 0); try { switch (step.op) { case 'createDirectory': fileSystem.createDirectory(path, step.recursive ?? false); break; case 'writeText': fileSystem.writeTextFile(path, step.text ?? '', step.overwrite ?? true); break; case 'writeBinary': fileSystem.writeBinaryFile(path, bytes(), step.mediaType, step.overwrite ?? true); break; case 'writeImage': fileSystem.writeImageFile(path, bytes(), step.mediaType ?? 'image/png', step.overwrite ?? true); break; case 'copy': fileSystem.copyItem(path, step.to ?? '/', step.overwrite ?? false); break; case 'move': fileSystem.moveItem(path, step.to ?? '/', step.overwrite ?? false); break; case 'rename': results.push({ ok: fileSystem.renameItem(path, step.name ?? '') }); continue; case 'delete': fileSystem.deleteItem(path, step.recursive ?? false); break; case 'entry': results.push({ ok: describeEntries([fileSystem.entry(path)])[0] }); continue; case 'entries': results.push({ ok: describeEntries(fileSystem.entries(path)) }); continue; case 'text': results.push({ ok: fileSystem.textFile(path) }); continue; case 'contains': results.push({ ok: fileSystem.contains(path) }); continue; case 'normalize': results.push({ ok: normalizedAbsolutePath(path) }); continue; case 'default': fileSystem = VirtualFileSystem.defaultForDevice(); break; case 'fromEntries': fileSystem = VirtualFileSystem.fromEntries( (step.entries ?? []).map((entry) => ({ path: entry.path, content: entry.kind === 'directory' ? { kind: 'directory' } : { kind: 'text', text: entry.text ?? '' }, })), ); break; default: throw new Error(`unknown step ${step.op}`); } results.push({ ok: true }); } catch (error) { if (!(error instanceof VirtualFileSystemError)) throw error; results.push({ error: error.code, values: error.values }); } } return { results, entries: describeEntries(fileSystem.allEntries()) }; } describe('virtual file system against Swift', () => { it('covers every case', () => { expect(swift.map((result) => result.name)).toEqual(cases.map((item) => item.name)); }); cases.forEach((item, index) => { it(item.name, () => { const { results, entries } = run(item.steps); const expected = swift[index]!; results.forEach((result, step) => { expect(result, `step ${step + 1}: ${JSON.stringify(item.steps[step])}`).toEqual(expected.results[step]); }); expect(entries).toEqual(expected.entries); }); }); }); describe('virtual file system', () => { it('leaves itself unchanged when a copy breaks a quota (Swift test)', () => { const fileSystem = new VirtualFileSystem(); const maximum = new Uint8Array(8 * 1024 * 1024).fill(0x41); for (const name of ['source', 'destination', 'third', 'fourth']) { fileSystem.writeBinaryFile(`/${name}.bin`, maximum, 'application/octet-stream'); } const before = fileSystem.clone(); expect(() => fileSystem.copyItem('/source.bin', '/new.bin', true)).toThrow(VirtualFileSystemError); expect(fileSystem.equals(before)).toBe(true); }); it('upgrades the 1×1 black images of older versions, and only those', () => { const fileSystem = VirtualFileSystem.defaultForDevice(); expect(fileSystem.upgradeLegacyDefaultImages()).toBe(0); }); }); describe("a file system's revision", () => { it('changes with every change, a removal too, and not with a reading', () => { const files = new VirtualFileSystem(); const seen = [files.revision]; files.createDirectory('/a'); seen.push(files.revision); files.writeTextFile('/a/b.txt', 'x'); seen.push(files.revision); files.renameInPlace('/a/b.txt', 'c.txt'); seen.push(files.revision); files.deleteItem('/a/c.txt'); seen.push(files.revision); files.allEntries(); files.contains('/a'); seen.push(files.revision); expect(new Set(seen.slice(0, 5)).size).toBe(5); expect(seen[5]).toBe(seen[4]); }); });