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:
@@ -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
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user