The core gains Swift's virtual file system (paths, case-insensitive lookup that keeps the spelling, quotas, atomic changes, the defaults of a new device) and the string behaviour the ported code relies on without saying so. Both are measured in the Swift oracle: 11 file system cases (the three Swift tests among them) step by step, and trimming, lower case, equality, ordering and Base64 case by case — whitespace and control characters over all of Unicode. Found by measuring: - Swift trims NEL and ZERO WIDTH SPACE, not the BOM; JS trim() does the opposite. The .fls import now trims with swiftTrim. - lowercased() maps scalar by scalar: a word-final sigma stays σ. - Foundation decodes "====" and "Y===" as empty data. - Foundation on Linux counts 24,735 plane-14 scalars as control characters, a table artefact; the port keeps Unicode Cc+Cf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
5.7 KiB
TypeScript
167 lines
5.7 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);
|
||
});
|
||
});
|