Yield recovery container encoding without changing durable cuts

This commit is contained in:
2026-11-11 04:04:24 +08:00
parent 57dab91a9c
commit b4c40d3087
7 changed files with 195 additions and 18 deletions
+41
View File
@@ -31,3 +31,44 @@ test('sealed image byte sizes match UTF-8 JSON for reused data and all codec nor
const {sealedRecoveryByteLength}=await import('../../src/lib/canvas-runtime/recovery-encoding.ts'),shared=freeze({text:'中文😀\ud800\n"\\',items:[null,1,true]}),payloads=[null,'😀',-0,{shared,omitted:undefined,array:[,undefined,NaN]},new Date('2026-01-01Z'),{['__proto__']:'保留'},Object.freeze({get text(){return '动态😀';}})];
for(const payload of payloads)for(let revision=0;revision<3;revision++){const image=sealRecoveryImage(17,revision,revision,payload);assert.equal(sealedRecoveryByteLength(image),new TextEncoder().encode(JSON.stringify(image)).byteLength);assert.equal(sealedRecoveryByteLength(structuredClone(image)),undefined);}
});
test('cooperative sealing captures unknown values before yielding and retains exact bytes',async()=>{
const {dataArray,dataRecord}=await import('../../src/lib/canvas-core/data-containers.ts');
const {sealRecoveryImageAsync,sealedRecoveryByteLength}=await import('../../src/lib/canvas-runtime/recovery-encoding.ts');
let current='before',calls=[];
const mutable={title:'before'},shallow=Object.freeze({mutable}),date=Object.freeze(new Date('2026-01-01Z'));
const custom=Object.freeze({toJSON(key){calls.push(key);return {key,current};}});
const getter=Object.freeze({get title(){calls.push('getter');return current;}});
const rows=dataArray(Array.from({length:1000},(_,i)=>dataRecord({id:i,text:'中文😀\ud800'})));
const payload=dataRecord({rows,shallow,date,getter,left:custom,right:custom,['__proto__']:{safe:true}});
const expected=sealRecoveryImage(17,2,1,payload);calls=[];let yields=0;
const pending=sealRecoveryImageAsync(17,2,1,dataRecord({...payload,rows:dataArray(rows)}),{now:(()=>{let t=0;return()=>t++;})(),frameBudgetMs:1,yieldTask:async()=>{yields++;current='after';mutable.title='after';date.setUTCFullYear(2027);}});
assert.deepEqual(calls,['getter','left','right']);
mutable.title='outside';current='outside';
const image=await pending;
// A second uncached table below exercises the work ceiling with a coarse clock.
assert.ok(yields>0);assert.deepEqual(image,expected);assert.equal(isSealedRecoveryImage(image),true);
assert.equal(sealedRecoveryByteLength(image),new TextEncoder().encode(JSON.stringify(image)).byteLength);
const beforeCoarse=yields;
const large=dataArray(Array.from({length:20000},(_,i)=>i));
const more=await sealRecoveryImageAsync(17,3,2,large,{now:()=>100,frameBudgetMs:1,yieldTask:async()=>{yields++;}});
assert.ok(yields>beforeCoarse);assert.deepEqual(more.payload,large);
});
test('cooperative preparation evaluates every unsafe occurrence and rejects errors without an image',async()=>{
const {dataArray,dataRecord}=await import('../../src/lib/canvas-core/data-containers.ts');
const {sealRecoveryImageAsync}=await import('../../src/lib/canvas-runtime/recovery-encoding.ts');
let value=0;
const dynamic=Object.freeze({get title(){return ++value;}}),shared=dataRecord({dynamic});
const image=await sealRecoveryImageAsync(17,1,1,dataRecord({left:shared,right:shared}));
assert.deepEqual(image.payload,{left:{dynamic:{title:1}},right:{dynamic:{title:2}}});
let ticks=0;
await assert.rejects(sealRecoveryImageAsync(17,1,1,dataArray(Array.from({length:1000},(_,i)=>i)),{now:()=>ticks++,frameBudgetMs:1,yieldTask:async()=>{throw Error('yield_failed');}}),/yield_failed/);
const nested=(n,leaf)=>{for(let i=0;i<n;i++)leaf=dataRecord({child:leaf});return leaf;};
const leaf=freeze({value:1});encodeRecoveryValue(leaf);
await assert.rejects(sealRecoveryImageAsync(17,1,1,nested(64,leaf)),/invalid_recovery_payload/);
await assert.rejects(sealRecoveryImageAsync(17,1,1,nested(64,dataArray([undefined]))),/invalid_recovery_payload/);
assert.ok(await sealRecoveryImageAsync(17,1,1,nested(64,dataRecord({omitted:undefined}))));
await assert.rejects(sealRecoveryImageAsync(17,0,1,{}),/corrupt_recovery_image/);
});