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
+130
View File
@@ -0,0 +1,130 @@
{
"trim": [
" a ",
"\t\na\r",
"\u0085a\u0085",
"\ufeffa",
"\u00a0a\u00a0",
"\u2028a\u2029",
"\u3000a",
"\u200ba",
"\u001ca",
"\u000ba\f",
" \u0301a",
"a \u0301",
"\u180ea",
"\u1680a"
],
"lowercased": [
"ABC",
"\u03a3\u0391\u03a3",
"\u0391\u03a3 x",
"\u0130",
"\u1e9e",
"\u01c5",
"\u00c4\u00d6\u00dc",
"\u212a"
],
"equal": [
[
"\u00e9",
"e\u0301"
],
[
"\u00c5",
"A\u030a"
],
[
"\ufb01",
"fi"
],
[
"K",
"\u212a"
],
[
"\u03a9",
"\u2126"
],
[
"a",
"A"
],
[
"\u00e9",
"\u00e9"
]
],
"sort": [
[
"b",
"a",
"B"
],
[
"\uff5e",
"\ud83d\ude00"
],
[
"e\u0301x",
"\u00e9a"
],
[
"a",
"A",
"\u00e1",
"Z"
],
[
"file10",
"file2",
"File1"
]
],
"base64": [
"aGVsbG8=",
"aGVsbG8",
"aGVs bG8=",
"aGVsbG8=\n",
"aGVs=bG8=",
"====",
"",
"aGVsbG8==",
"YQ==",
"YQ",
"Y",
"!!!!YQ==",
"YQ==YQ==",
"YWJj",
"Y\u00e9Q==",
"YQ=\n=",
"Y===",
"YQ=",
"YWI=",
"YW==",
"=YQ=",
"A===",
"AA==",
"AAA=",
"AB==",
"ABC=",
"YQ===",
"YQ==\u0000",
"Y=Q=",
"YWJjZA",
"YWJjZA=",
"=",
"==",
"==="
],
"controlCharacters": [
"a",
"a\u200b",
"a\u007f",
"a\u00ad",
"a\u0085",
"\ufeff",
"a\ud83d\ude00",
"a\ue000"
]
}
File diff suppressed because it is too large Load Diff
+96
View File
@@ -0,0 +1,96 @@
// Swift String behaviour, measured in the Swift core (strings.swift.json,
// tools/swift-oracle --strings): every case must come out the same here.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import {
base64Encode,
containsSwiftControlCharacter,
swiftBase64Decode,
swiftCompare,
swiftEqual,
swiftLowercased,
swiftTrim,
} from '../src/index.js';
const here = dirname(fileURLToPath(import.meta.url));
const cases = JSON.parse(readFileSync(join(here, 'strings.cases.json'), 'utf8')) as {
trim: string[];
lowercased: string[];
equal: [string, string][];
sort: string[][];
base64: string[];
controlCharacters: string[];
};
const swift = JSON.parse(readFileSync(join(here, 'strings.swift.json'), 'utf8')) as {
trim: string[];
lowercased: string[];
equal: boolean[];
sort: string[][];
base64: (number[] | null)[];
controlCharacters: boolean[];
whitespacesAndNewlinesSet: [number, number][];
controlCharactersSet: [number, number][];
};
/** The scalars for which `test` holds, as ranges [first, last] — the oracle's shape. */
function scalarRanges(test: (scalar: string) => boolean): [number, number][] {
const ranges: [number, number][] = [];
for (let value = 0; value <= 0x10ffff; value++) {
if (value >= 0xd800 && value <= 0xdfff) continue;
if (!test(String.fromCodePoint(value))) continue;
const last = ranges.at(-1);
if (last !== undefined && last[1] === value - 1) last[1] = value;
else ranges.push([value, value]);
}
return ranges;
}
describe('Swift string semantics', () => {
it('trims like trimmingCharacters(in: .whitespacesAndNewlines)', () => {
expect(cases.trim.map(swiftTrim)).toEqual(swift.trim);
});
it('lower-cases like lowercased()', () => {
expect(cases.lowercased.map(swiftLowercased)).toEqual(swift.lowercased);
});
it('compares like ==', () => {
expect(cases.equal.map(([lhs, rhs]) => swiftEqual(lhs, rhs))).toEqual(swift.equal);
});
it('sorts like sorted()', () => {
expect(cases.sort.map((list) => [...list].sort(swiftCompare))).toEqual(swift.sort);
});
it('decodes Base64 like Data(base64Encoded:options: .ignoreUnknownCharacters)', () => {
expect(cases.base64.map((text) => {
const bytes = swiftBase64Decode(text);
return bytes === undefined ? null : [...bytes];
})).toEqual(swift.base64);
});
it('finds control characters like CharacterSet.controlCharacters', () => {
expect(cases.controlCharacters.map(containsSwiftControlCharacter)).toEqual(swift.controlCharacters);
});
it('trims exactly the scalars Swift trims, all of Unicode', () => {
expect(scalarRanges((scalar) => swiftTrim(`${scalar}a`) === 'a')).toEqual(swift.whitespacesAndNewlinesSet);
});
it('counts the same scalars as control characters, except Foundation\'s plane-14 artefact', () => {
const below = (ranges: [number, number][]) => ranges.filter(([first]) => first < 0xe0100);
expect(scalarRanges(containsSwiftControlCharacter).filter(([first]) => first < 0xe0100)).toEqual(
below(swift.controlCharactersSet).map(([first, last]) => [first, Math.min(last, 0xe00ff)]),
);
});
it('encodes Base64 so that it decodes again', () => {
for (const length of [0, 1, 2, 3, 4, 5, 255]) {
const bytes = Uint8Array.from({ length }, (_, index) => (index * 37) & 0xff);
expect(swiftBase64Decode(base64Encode(bytes))).toEqual(bytes);
}
});
});
+569
View File
@@ -0,0 +1,569 @@
[
{
"name": "whitespace components and case-insensitive siblings (Swift test)",
"steps": [
{
"op": "createDirectory",
"path": "/docs"
},
{
"op": "writeText",
"path": "/docs/Readme.txt",
"text": "one"
},
{
"op": "writeText",
"path": "/docs/ note.txt",
"text": "two"
},
{
"op": "writeText",
"path": "/DOCS/readme.TXT",
"text": "two",
"overwrite": true
},
{
"op": "text",
"path": "/docs/Readme.txt"
},
{
"op": "entry",
"path": "/DOCS/README.TXT"
},
{
"op": "writeText",
"path": "/docs/README.txt",
"text": "three",
"overwrite": false
},
{
"op": "fromEntries",
"entries": [
{
"path": "/docs",
"kind": "directory"
},
{
"path": "/docs/Readme.txt",
"kind": "text",
"text": "one"
},
{
"path": "/docs/readme.TXT",
"kind": "text",
"text": "two"
}
]
}
]
},
{
"name": "quotas are explicit and atomic (Swift test)",
"steps": [
{
"op": "writeBinary",
"path": "/oversized.bin",
"size": 8388609,
"byte": 65
},
{
"op": "contains",
"path": "/oversized.bin"
},
{
"op": "writeBinary",
"path": "/file-0.bin",
"size": 8388608,
"byte": 66
},
{
"op": "writeBinary",
"path": "/file-1.bin",
"size": 8388608,
"byte": 66
},
{
"op": "writeBinary",
"path": "/file-2.bin",
"size": 8388608,
"byte": 66
},
{
"op": "writeBinary",
"path": "/file-3.bin",
"size": 8388608,
"byte": 66
},
{
"op": "writeBinary",
"path": "/file-4.bin",
"size": 8388608,
"byte": 66
},
{
"op": "contains",
"path": "/file-4.bin"
}
]
},
{
"name": "overwrite uses the final state's quota, move is atomic (Swift test)",
"steps": [
{
"op": "writeBinary",
"path": "/source.bin",
"size": 8388608,
"byte": 65,
"mediaType": "application/octet-stream"
},
{
"op": "writeBinary",
"path": "/destination.bin",
"size": 8388608,
"byte": 65,
"mediaType": "application/octet-stream"
},
{
"op": "writeBinary",
"path": "/third.bin",
"size": 8388608,
"byte": 65,
"mediaType": "application/octet-stream"
},
{
"op": "writeBinary",
"path": "/fourth.bin",
"size": 8388608,
"byte": 65,
"mediaType": "application/octet-stream"
},
{
"op": "copy",
"path": "/SOURCE.BIN",
"to": "/DESTINATION.BIN",
"overwrite": true
},
{
"op": "copy",
"path": "/source.bin",
"to": "/new.bin",
"overwrite": true
},
{
"op": "move",
"path": "/SOURCE.BIN",
"to": "/DESTINATION.BIN",
"overwrite": true
},
{
"op": "contains",
"path": "/source.bin"
},
{
"op": "contains",
"path": "/destination.bin"
}
]
},
{
"name": "path normalization",
"steps": [
{
"op": "normalize",
"path": "a"
},
{
"op": "normalize",
"path": "/"
},
{
"op": "normalize",
"path": "/a/./b/../c"
},
{
"op": "normalize",
"path": "/.."
},
{
"op": "normalize",
"path": "/a/../.."
},
{
"op": "normalize",
"path": "\\a\\b"
},
{
"op": "normalize",
"path": "/a/ b"
},
{
"op": "normalize",
"path": "/a/b "
},
{
"op": "normalize",
"path": "/a/b\u0007"
},
{
"op": "normalize",
"path": "/a/b\u200b"
},
{
"op": "normalize",
"path": "//a//b//"
},
{
"op": "normalize",
"path": "/a/..."
},
{
"op": "normalize",
"path": "/a/\u0085b"
},
{
"op": "normalize",
"path": "/a/\ufeffb"
},
{
"op": "normalize",
"path": "/a/\u00a0b\u00a0"
},
{
"op": "normalize",
"path": "/\u00e9/e\u0301"
}
]
},
{
"name": "canonical equivalence and scalar-wise lower case",
"steps": [
{
"op": "createDirectory",
"path": "/\u00e9"
},
{
"op": "entry",
"path": "/e\u0301"
},
{
"op": "writeText",
"path": "/E\u0301/x.txt",
"text": "1"
},
{
"op": "createDirectory",
"path": "/e\u0301"
},
{
"op": "createDirectory",
"path": "/\u039f\u0394\u039f\u03a3"
},
{
"op": "entry",
"path": "/\u03bf\u03b4\u03bf\u03c3"
},
{
"op": "entry",
"path": "/\u03bf\u03b4\u03bf\u03c2"
},
{
"op": "createDirectory",
"path": "/Stra\u00dfe"
},
{
"op": "createDirectory",
"path": "/STRASSE"
},
{
"op": "createDirectory",
"path": "/\u212aelvin"
},
{
"op": "entry",
"path": "/kelvin"
}
]
},
{
"name": "listing order",
"steps": [
{
"op": "writeText",
"path": "/b.txt",
"text": "b.txt"
},
{
"op": "writeText",
"path": "/a.txt",
"text": "a.txt"
},
{
"op": "writeText",
"path": "/Zeta",
"text": "Zeta"
},
{
"op": "writeText",
"path": "/\u00e9mile",
"text": "\u00e9mile"
},
{
"op": "writeText",
"path": "/file10",
"text": "file10"
},
{
"op": "writeText",
"path": "/file2",
"text": "file2"
},
{
"op": "writeText",
"path": "/\uff5e",
"text": "\uff5e"
},
{
"op": "writeText",
"path": "/\ud83d\ude00",
"text": "\ud83d\ude00"
},
{
"op": "createDirectory",
"path": "/zdir"
},
{
"op": "createDirectory",
"path": "/Adir"
},
{
"op": "entries",
"path": "/"
}
]
},
{
"name": "copy, move, rename and delete trees",
"steps": [
{
"op": "createDirectory",
"path": "/a/b/c",
"recursive": true
},
{
"op": "writeText",
"path": "/a/b/c/f.txt",
"text": "f"
},
{
"op": "writeText",
"path": "/a/g.txt",
"text": "g"
},
{
"op": "copy",
"path": "/a",
"to": "/a/b/x"
},
{
"op": "copy",
"path": "/a/b",
"to": "/"
},
{
"op": "copy",
"path": "/A/B",
"to": "/copy"
},
{
"op": "entries",
"path": "/copy/c"
},
{
"op": "move",
"path": "/copy",
"to": "/a/moved"
},
{
"op": "move",
"path": "/a",
"to": "/a/b"
},
{
"op": "rename",
"path": "/a/moved",
"name": "Renamed"
},
{
"op": "rename",
"path": "/a/g.txt",
"name": "x/y"
},
{
"op": "delete",
"path": "/a"
},
{
"op": "delete",
"path": "/",
"recursive": true
},
{
"op": "copy",
"path": "/a/g.txt",
"to": "/a/b",
"overwrite": true
},
{
"op": "copy",
"path": "/a/b",
"to": "/a/g.txt",
"overwrite": true
},
{
"op": "delete",
"path": "/A/b",
"recursive": true
},
{
"op": "writeText",
"path": "/a",
"text": "x"
},
{
"op": "text",
"path": "/a"
},
{
"op": "createDirectory",
"path": "/a/g.txt/x"
},
{
"op": "createDirectory",
"path": "/q/r"
}
]
},
{
"name": "file kinds",
"steps": [
{
"op": "writeImage",
"path": "/i.png",
"size": 10,
"byte": 1,
"mediaType": "image/png"
},
{
"op": "text",
"path": "/i.png"
},
{
"op": "writeText",
"path": "/",
"text": "x"
},
{
"op": "writeText",
"path": "/missing/x.txt",
"text": "x"
},
{
"op": "entry",
"path": "/i.png"
}
]
},
{
"name": "defaults of a new device",
"steps": [
{
"op": "default"
},
{
"op": "entries",
"path": "/images"
}
]
},
{
"name": "entries need their parents",
"steps": [
{
"op": "fromEntries",
"entries": [
{
"path": "/a/b",
"kind": "text",
"text": "x"
}
]
},
{
"op": "fromEntries",
"entries": [
{
"path": "/",
"kind": "directory"
}
]
},
{
"op": "fromEntries",
"entries": [
{
"path": "/b",
"kind": "directory"
},
{
"path": "/b/c",
"kind": "text",
"text": "c"
},
{
"path": "/a",
"kind": "text",
"text": "a"
}
]
}
]
},
{
"name": "canonically equal entries are one path",
"steps": [
{
"op": "fromEntries",
"entries": [
{
"path": "/\u00e9",
"kind": "text",
"text": "composed"
},
{
"path": "/e\u0301",
"kind": "text",
"text": "decomposed"
}
]
},
{
"op": "fromEntries",
"entries": [
{
"path": "/\u00e9",
"kind": "directory"
},
{
"path": "/e\u0301/x",
"kind": "text",
"text": "x"
}
]
},
{
"op": "entries",
"path": "/"
}
]
}
]
+863
View File
@@ -0,0 +1,863 @@
[
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/docs"
},
{
"kind" : "text",
"path" : "\/docs\/Readme.txt",
"text" : "two"
}
],
"name" : "whitespace components and case-insensitive siblings (Swift test)",
"results" : [
{
"ok" : true
},
{
"ok" : true
},
{
"error" : "invalidPathComponent",
"values" : [
" note.txt"
]
},
{
"ok" : true
},
{
"ok" : "two"
},
{
"ok" : {
"kind" : "text",
"path" : "\/docs\/Readme.txt",
"text" : "two"
}
},
{
"error" : "caseInsensitiveSiblingCollision",
"values" : [
"\/docs\/Readme.txt",
"\/docs\/README.txt"
]
},
{
"error" : "caseInsensitiveSiblingCollision",
"values" : [
"\/docs\/Readme.txt",
"\/docs\/readme.TXT"
]
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : null,
"path" : "\/file-0.bin",
"sha256" : "001224bdbc0a675a104bc57050e10365bce70ab7ca449685f8142460b0dd5ba5"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : null,
"path" : "\/file-1.bin",
"sha256" : "001224bdbc0a675a104bc57050e10365bce70ab7ca449685f8142460b0dd5ba5"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : null,
"path" : "\/file-2.bin",
"sha256" : "001224bdbc0a675a104bc57050e10365bce70ab7ca449685f8142460b0dd5ba5"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : null,
"path" : "\/file-3.bin",
"sha256" : "001224bdbc0a675a104bc57050e10365bce70ab7ca449685f8142460b0dd5ba5"
}
],
"name" : "quotas are explicit and atomic (Swift test)",
"results" : [
{
"error" : "fileSizeQuotaExceeded",
"values" : [
"\/oversized.bin",
8388609,
8388608
]
},
{
"ok" : false
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"error" : "deviceSizeQuotaExceeded",
"values" : [
41943040,
33554432
]
},
{
"ok" : false
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : "application\/octet-stream",
"path" : "\/destination.bin",
"sha256" : "b16bd32b101132fd0102461bc75ea65442c37293ac881ae953486c8ac26a7388"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : "application\/octet-stream",
"path" : "\/fourth.bin",
"sha256" : "b16bd32b101132fd0102461bc75ea65442c37293ac881ae953486c8ac26a7388"
},
{
"bytes" : 8388608,
"kind" : "binary",
"mediaType" : "application\/octet-stream",
"path" : "\/third.bin",
"sha256" : "b16bd32b101132fd0102461bc75ea65442c37293ac881ae953486c8ac26a7388"
}
],
"name" : "overwrite uses the final state's quota, move is atomic (Swift test)",
"results" : [
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"error" : "deviceSizeQuotaExceeded",
"values" : [
41943040,
33554432
]
},
{
"ok" : true
},
{
"ok" : false
},
{
"ok" : true
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
}
],
"name" : "path normalization",
"results" : [
{
"error" : "pathMustBeAbsolute",
"values" : [
"a"
]
},
{
"ok" : "\/"
},
{
"ok" : "\/a\/c"
},
{
"error" : "pathEscapesRoot",
"values" : [
"\/.."
]
},
{
"error" : "pathEscapesRoot",
"values" : [
"\/a\/..\/.."
]
},
{
"ok" : "\/a\/b"
},
{
"error" : "invalidPathComponent",
"values" : [
" b"
]
},
{
"error" : "invalidPathComponent",
"values" : [
"b "
]
},
{
"error" : "invalidPathComponent",
"values" : [
"b\u0007"
]
},
{
"error" : "invalidPathComponent",
"values" : [
"b​"
]
},
{
"ok" : "\/a\/b"
},
{
"ok" : "\/a\/..."
},
{
"error" : "invalidPathComponent",
"values" : [
"…b"
]
},
{
"error" : "invalidPathComponent",
"values" : [
"b"
]
},
{
"error" : "invalidPathComponent",
"values" : [
" b "
]
},
{
"ok" : "\/é\/é"
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/Kelvin"
},
{
"kind" : "directory",
"path" : "\/STRASSE"
},
{
"kind" : "directory",
"path" : "\/Straße"
},
{
"kind" : "directory",
"path" : "\/é"
},
{
"kind" : "text",
"path" : "\/é\/x.txt",
"text" : "1"
},
{
"kind" : "directory",
"path" : "\/ΟΔΟΣ"
}
],
"name" : "canonical equivalence and scalar-wise lower case",
"results" : [
{
"ok" : true
},
{
"ok" : {
"kind" : "directory",
"path" : "\/é"
}
},
{
"ok" : true
},
{
"error" : "itemAlreadyExists",
"values" : [
"\/é"
]
},
{
"ok" : true
},
{
"ok" : {
"kind" : "directory",
"path" : "\/ΟΔΟΣ"
}
},
{
"error" : "itemNotFound",
"values" : [
"\/οδος"
]
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : {
"kind" : "directory",
"path" : "\/Kelvin"
}
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/Adir"
},
{
"kind" : "text",
"path" : "\/Zeta",
"text" : "Zeta"
},
{
"kind" : "text",
"path" : "\/a.txt",
"text" : "a.txt"
},
{
"kind" : "text",
"path" : "\/b.txt",
"text" : "b.txt"
},
{
"kind" : "text",
"path" : "\/file10",
"text" : "file10"
},
{
"kind" : "text",
"path" : "\/file2",
"text" : "file2"
},
{
"kind" : "directory",
"path" : "\/zdir"
},
{
"kind" : "text",
"path" : "\/émile",
"text" : "émile"
},
{
"kind" : "text",
"path" : "\/~",
"text" : "~"
},
{
"kind" : "text",
"path" : "\/😀",
"text" : "😀"
}
],
"name" : "listing order",
"results" : [
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : [
{
"kind" : "directory",
"path" : "\/Adir"
},
{
"kind" : "directory",
"path" : "\/zdir"
},
{
"kind" : "text",
"path" : "\/Zeta",
"text" : "Zeta"
},
{
"kind" : "text",
"path" : "\/a.txt",
"text" : "a.txt"
},
{
"kind" : "text",
"path" : "\/b.txt",
"text" : "b.txt"
},
{
"kind" : "text",
"path" : "\/file10",
"text" : "file10"
},
{
"kind" : "text",
"path" : "\/file2",
"text" : "file2"
},
{
"kind" : "text",
"path" : "\/émile",
"text" : "émile"
},
{
"kind" : "text",
"path" : "\/~",
"text" : "~"
},
{
"kind" : "text",
"path" : "\/😀",
"text" : "😀"
}
]
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/a"
},
{
"kind" : "directory",
"path" : "\/a\/Renamed"
},
{
"kind" : "directory",
"path" : "\/a\/Renamed\/c"
},
{
"kind" : "text",
"path" : "\/a\/Renamed\/c\/f.txt",
"text" : "f"
},
{
"kind" : "text",
"path" : "\/a\/g.txt",
"text" : "g"
}
],
"name" : "copy, move, rename and delete trees",
"results" : [
{
"ok" : true
},
{
"ok" : true
},
{
"ok" : true
},
{
"error" : "cannotMoveDirectoryIntoItself",
"values" : [
"\/a",
"\/a\/b\/x"
]
},
{
"error" : "itemAlreadyExists",
"values" : [
"\/"
]
},
{
"ok" : true
},
{
"ok" : [
{
"kind" : "text",
"path" : "\/copy\/c\/f.txt",
"text" : "f"
}
]
},
{
"ok" : true
},
{
"error" : "cannotMoveDirectoryIntoItself",
"values" : [
"\/a",
"\/a\/b"
]
},
{
"ok" : "\/a\/Renamed"
},
{
"error" : "invalidPathComponent",
"values" : [
"x\/y"
]
},
{
"error" : "directoryNotEmpty",
"values" : [
"\/a"
]
},
{
"error" : "cannotMutateRoot",
"values" : [
]
},
{
"error" : "expectedFile",
"values" : [
"\/a\/b"
]
},
{
"error" : "expectedDirectory",
"values" : [
"\/a\/g.txt"
]
},
{
"ok" : true
},
{
"error" : "expectedFile",
"values" : [
"\/a"
]
},
{
"error" : "expectedTextFile",
"values" : [
"\/a"
]
},
{
"error" : "parentDirectoryNotFound",
"values" : [
"\/a\/g.txt\/x"
]
},
{
"error" : "parentDirectoryNotFound",
"values" : [
"\/q\/r"
]
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"bytes" : 10,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/i.png",
"sha256" : "ffadf8d89d37b3b55fe1847b513cf92e3be87e4c168708c7851845df96fb36be"
}
],
"name" : "file kinds",
"results" : [
{
"ok" : true
},
{
"error" : "expectedTextFile",
"values" : [
"\/i.png"
]
},
{
"error" : "expectedFile",
"values" : [
"\/"
]
},
{
"error" : "itemNotFound",
"values" : [
"\/missing"
]
},
{
"ok" : {
"bytes" : 10,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/i.png",
"sha256" : "ffadf8d89d37b3b55fe1847b513cf92e3be87e4c168708c7851845df96fb36be"
}
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/home"
},
{
"kind" : "text",
"path" : "\/home\/lab-notes.txt",
"text" : "Document deterministic runtime notes here."
},
{
"kind" : "text",
"path" : "\/home\/topology-exports.csv",
"text" : "timestamp,event,detail\n"
},
{
"kind" : "directory",
"path" : "\/images"
},
{
"bytes" : 2352,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/network-map.png",
"sha256" : "4105bdb0131a539791f3b0f00e43341de6a5e04ab21b0cda19ac49548cd0cdd1"
},
{
"bytes" : 1478,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/switch-backplane.png",
"sha256" : "8ae2492515a66cd69d729d70d449ece4e6c23ee151f98da44c6b7862058c463f"
},
{
"bytes" : 6195,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/traffic-heatmap.png",
"sha256" : "6b541170a55a139281170fcf1176ce0b962f1ab58b1f26b9d2cff7558711825b"
},
{
"kind" : "directory",
"path" : "\/var"
},
{
"kind" : "directory",
"path" : "\/var\/log"
},
{
"kind" : "text",
"path" : "\/var\/log\/runtime-events.log",
"text" : "Virtual runtime initialized.\n"
}
],
"name" : "defaults of a new device",
"results" : [
{
"ok" : true
},
{
"ok" : [
{
"bytes" : 2352,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/network-map.png",
"sha256" : "4105bdb0131a539791f3b0f00e43341de6a5e04ab21b0cda19ac49548cd0cdd1"
},
{
"bytes" : 1478,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/switch-backplane.png",
"sha256" : "8ae2492515a66cd69d729d70d449ece4e6c23ee151f98da44c6b7862058c463f"
},
{
"bytes" : 6195,
"kind" : "image",
"mediaType" : "image\/png",
"path" : "\/images\/traffic-heatmap.png",
"sha256" : "6b541170a55a139281170fcf1176ce0b962f1ab58b1f26b9d2cff7558711825b"
}
]
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "text",
"path" : "\/a",
"text" : "a"
},
{
"kind" : "directory",
"path" : "\/b"
},
{
"kind" : "text",
"path" : "\/b\/c",
"text" : "c"
}
],
"name" : "entries need their parents",
"results" : [
{
"error" : "parentDirectoryNotFound",
"values" : [
"\/a\/b"
]
},
{
"error" : "itemAlreadyExists",
"values" : [
"\/"
]
},
{
"ok" : true
}
]
},
{
"entries" : [
{
"kind" : "directory",
"path" : "\/"
},
{
"kind" : "directory",
"path" : "\/é"
},
{
"kind" : "text",
"path" : "\/é\/x",
"text" : "x"
}
],
"name" : "canonically equal entries are one path",
"results" : [
{
"error" : "itemAlreadyExists",
"values" : [
"\/é"
]
},
{
"ok" : true
},
{
"ok" : [
{
"kind" : "directory",
"path" : "\/é"
}
]
}
]
}
]
@@ -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);
});
});