1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
// modules/test/test-vfs-checkpoint.js — run: node modules/test/test-vfs-checkpoint.js
// Proves the name a checkpoint is SAVED under is the name the UI prints and the name
// `rollback` resolves. RED on v4.6.101: two independent new Date().toISOString() calls
// made the printed name differ from the stored one, so `checkpoint` then
// `rollback <printed name>` failed with "checkpoint not found or invalid".
const fsMod = require('fs'), path = require('path');
const ROOT = path.join(__dirname, '..', 'core');
// ── guarded stubs for the browser globals the VFS touches ──────────────────
const ls = {};
global.localStorage = {
getItem: k => (k in ls ? ls[k] : null),
setItem: (k, v) => { ls[k] = String(v); },
removeItem: k => { delete ls[k]; },
};
global.indexedDB = {
open() { throw new Error('indexedDB not available in test'); },
deleteDatabase() { throw new Error('indexedDB not available in test'); },
};
global.window = global;
function loadClass(file, clsName) {
const src = fsMod.readFileSync(path.join(ROOT, file), 'utf8');
return new Function(src + '\nreturn ' + clsName + ';')();
}
const VirtualFileSystem = loadClass('virtual-fs.js', 'VirtualFileSystem');
const SystemModule = loadClass('module-system.js', 'SystemModule');
function mkFs() {
const fs = new VirtualFileSystem();
fs.saveToStorage = () => true; // isolate from timers / compression
return fs;
}
let fails = 0, passes = 0;
function ok(name, cond, detail) {
if (cond) { passes++; return; }
fails++;
console.log('FAIL ' + name + (detail ? ' — ' + detail : ''));
}
// A. checkpoint('') must return the resolved name, and two calls must differ
{
const fs = mkFs();
const a = fs.checkpoint('');
const b = fs.checkpoint('');
ok('A1 checkpoint returns a name string', typeof a === 'string' && a.length > 0, 'got ' + typeof a + ' ' + a);
ok('A2 second name also a string', typeof b === 'string' && b.length > 0, 'got ' + typeof b);
ok('A3 two unnamed checkpoints get distinct names', a !== b, 'both=' + a);
ok('A4 stored under the returned name', !!fs.fsReadJSON('/_snapshots/checkpoint-' + a + '.json'));
}
// B. rollback with the returned name must succeed
{
const fs = mkFs();
fs.mkdirRecursive('/marker');
const name = fs.checkpoint('');
ok('B1 checkpoint-list shows the returned name', fs.listCheckpoints().includes(name), 'name=' + name);
ok('B2 rollback(returned name) is true', fs.rollback(name) === true, 'name=' + name);
}
// C. the `checkpoint` command prints exactly the name it created
{
const fsv = mkFs();
const out = [];
const sys = new SystemModule();
sys.terminal = { print: m => out.push(String(m)), fs: fsv };
sys.registerCommands();
out.length = 0;
sys.commands['checkpoint']([]);
const line = out.join('\n');
const printed = (line.match(/Checkpoint created:\s*(.+)$/m) || [])[1];
ok('C1 command prints a created name', !!printed, 'output=' + JSON.stringify(line));
const listed = fsv.listCheckpoints();
ok('C2 printed name exists on disk', !!printed && listed.includes(printed.trim()),
'printed=' + printed + ' listed=' + JSON.stringify(listed));
ok('C3 rollback of the printed name succeeds', !!printed && fsv.rollback(printed.trim()) === true);
// C4: an explicit name is echoed verbatim
out.length = 0;
sys.commands['checkpoint'](['myname']);
ok('C4 explicit name echoed verbatim', /Checkpoint created:\s*myname\s*$/.test(out.join('\n')),
'output=' + JSON.stringify(out.join('\n')));
}
// D. same-millisecond calls must not overwrite each other
{
const fs = mkFs();
const names = [];
// 4 calls: one more than the cap of 3, so every name is still stored when the next one is
// minted (at 6+, prune legitimately frees a name and it may be reused).
for (let i = 0; i < 4; i++) names.push(fs.checkpoint(''));
ok('D1 four unnamed checkpoints are four distinct names', new Set(names).size === 4,
'names=' + JSON.stringify(names));
ok('D2 prune still caps the store at three', fs.listCheckpoints().length === 3,
'stored=' + JSON.stringify(fs.listCheckpoints()));
}
// E. prune keeps the NEWEST three, not the alphabetically last
{
const fs = mkFs();
fs.checkpoint('aaa-oldest');
fs.checkpoint('');
fs.checkpoint('');
fs.checkpoint(''); // 4th -> prune
const left = fs.listCheckpoints();
ok('E1 oldest custom-named checkpoint was pruned', !left.includes('aaa-oldest'), 'left=' + JSON.stringify(left));
ok('E2 three checkpoints remain', left.length === 3, 'left=' + JSON.stringify(left));
ok('E3 rollback still works after pruning', fs.rollback(left[0]) === true);
}
// F. rollback must not destroy scaffolding
{
const fs = mkFs();
fs.mkdirRecursive('/_sessions/agent1');
fs.writeFile('/_sessions/agent1/log.json', '{"turns":3}');
fs.mkdirRecursive('/.trash');
fs.writeFile('/.trash/junk.txt', 'x');
fs.writeFile('/work.txt', 'before');
const name = fs.checkpoint('keep-me');
fs.writeFile('/work.txt', 'after');
ok('F1 rollback succeeds', fs.rollback(name) === true);
ok('F2 file restored', fs.readFile('/work.txt') === 'before', 'got ' + fs.readFile('/work.txt'));
ok('F3 checkpoint survived rollback', fs.listCheckpoints().includes('keep-me'),
'list=' + JSON.stringify(fs.listCheckpoints()));
ok('F4 sessions survived rollback', !!fs.readDir('/_sessions'));
ok('F5 trash survived rollback', !!fs.readDir('/.trash'));
ok('F6 rollback is repeatable', fs.rollback('keep-me') === true);
}
// G. a name with a slash is refused, not silently unlistable
{
const fs = mkFs();
let threw = false;
try { fs.checkpoint('a/b'); } catch (e) { threw = true; }
ok('G1 slash in name throws', threw);
ok('G2 nothing was written', fs.listCheckpoints().length === 0,
'list=' + JSON.stringify(fs.listCheckpoints()));
}
if (fails === 0) {
console.log('test-vfs-checkpoint: ' + passes + ' passed, 0 failed');
} else {
console.log('test-vfs-checkpoint: ' + passes + ' passed, ' + fails + ' FAILED');
process.exit(1);
}
|