The oracle SaveAfterRunRun saves Java's project after a run. The room writes what a run leaves that Java keeps - files, running servers, virtual hosts, mail servers' domains and accounts, mail programs' accounts and folders, firewalls, DHCP clients' addresses - into the document as origin 'sim', at most once a second, after each action and at the stop; writeRunState writes only what differs. The command line now goes on in a folder made before a restart, as Java's does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
188 lines
6.4 KiB
TypeScript
188 lines
6.4 KiB
TypeScript
// 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<string, unknown> {
|
||
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]);
|
||
});
|
||
});
|
||
|