feat(core): virtual file system and Swift string semantics

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>
This commit is contained in:
2026-09-29 21:23:45 +00:00
co-authored by Claude Opus 5.5
parent 51a83ea68d
commit a198d10b4a
15 changed files with 5247 additions and 18 deletions
@@ -0,0 +1,166 @@
// 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);
});
});