io-output.test.mjs (3544B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import {OutputBuffer} from '../dist/io-output.js'; 4 const encode=text=>[...new TextEncoder().encode(text)]; 5 const reconstruct=view=>view.tokens.flatMap(t=>t.kind==='text'?encode(t.text):t.bytes); 6 test('valid UTF8 remains text including BOM, NUL, CRLF, combining and literal escape-looking text',()=>{ 7 const text='\ufeff\0\r\nλ😀e\u0301\\xff <script>\x1b[31m',buffer=new OutputBuffer(); 8 buffer.append('output',encode(text));buffer.finish();const out=buffer.snapshot().output; 9 assert.deepEqual(out.tokens,[{kind:'text',offset:0,text}]);assert.deepEqual(reconstruct(out),encode(text)); 10 }); 11 test('all byte values survive exact raw, hex and token reconstruction',()=>{ 12 const bytes=Array.from({length:256},(_,i)=>i),buffer=new OutputBuffer();buffer.append('output',bytes);buffer.finish(); 13 const out=buffer.snapshot().output;assert.deepEqual(out.bytes,bytes);assert.deepEqual(reconstruct(out),bytes); 14 assert.deepEqual(out.hex.split(' ').map(s=>parseInt(s,16)),bytes); 15 assert(out.tokens.some(t=>t.kind==='bytes'&&t.reason==='invalid')); 16 }); 17 test('partitioning never changes final presentation and valid text after bad prefixes survives',()=>{ 18 const bytes=[...encode('a😀λ'),0xe0,0x80,65,0xed,0xa0,0x80,0xf4,0x90,0x80,0x80,66,0xf0,0x9f]; 19 const all=new OutputBuffer();all.append('output',bytes);all.finish();const expected=all.snapshot(); 20 for(let split=1;split<bytes.length;split++){ 21 const b=new OutputBuffer();b.append('output',bytes.slice(0,split)); 22 assert.deepEqual(reconstruct(b.snapshot().output),bytes.slice(0,split)); 23 b.append('output',bytes.slice(split));b.finish();assert.deepEqual(b.snapshot(),expected); 24 } 25 assert(expected.output.tokens.some(t=>t.kind==='text'&&t.text==='A')); 26 assert.equal(expected.output.tokens.at(-1).reason,'incomplete'); 27 }); 28 test('pending suffixes resolve across writes but never across stdout/stderr',()=>{ 29 const b=new OutputBuffer();b.append('output',[0xf0,0x9f]);b.append('error',[0x98,0x80]); 30 assert.equal(b.snapshot().output.tokens[0].reason,'pending'); 31 assert(b.snapshot().error.tokens.every(t=>t.reason==='invalid')); 32 b.append('output',[0x98,0x80]);assert.equal(b.snapshot().output.tokens[0].text,'😀'); 33 b.append('error',[0xc2]);b.finish();b.finish();assert.equal(b.snapshot().error.tokens.at(-1).reason,'incomplete'); 34 assert.throws(()=>b.append('output',[65]),/ended/); 35 }); 36 test('accepted-byte bound is shared and rejection is atomic; copies have no aliases',()=>{ 37 const b=new OutputBuffer(3),bytes=[65,66];b.append('output',bytes);bytes[0]=0; 38 const snapshot=b.snapshot();snapshot.output.bytes[0]=0;snapshot.output.tokens[0].text='changed'; 39 assert.equal(b.snapshot().output.tokens[0].text,'AB');b.append('error',[255]); 40 const before=b.snapshot();assert.throws(()=>b.append('error',[1]),/limit/);assert.deepEqual(b.snapshot(),before); 41 for(const bytes of [[256],[-1],[1.5],Array(1),[],Array(4097).fill(0)])assert.throws(()=>b.append('output',bytes),/bytes/); 42 assert.deepEqual(b.snapshot(),before); 43 for(const limit of [-1,16385,NaN,1.5])assert.throws(()=>new OutputBuffer(limit),/limit/); 44 }); 45 test('many independent malformed streams reconstruct exactly without replacement scalars',()=>{ 46 let state=12345;for(let run=0;run<128;run++){ 47 const bytes=Array.from({length:128},()=>{state=(Math.imul(state,1664525)+1013904223)>>>0;return state>>>24;}); 48 const b=new OutputBuffer();for(const byte of bytes)b.append('output',[byte]);b.finish(); 49 assert.deepEqual(reconstruct(b.snapshot().output),bytes); 50 } 51 });