tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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 });