tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

backend-probe.mjs (10839B)


      1 // Shared real-page assertions for Chromium and stock Firefox. Test-only module.
      2 const assert={
      3   equal(a,b,message='Values differ'){if(!Object.is(a,b))throw Error(message+': '+JSON.stringify({actual:a,expected:b}));},
      4   match(value,pattern){if(!pattern.test(value))throw Error('Missing '+pattern+' in '+value);},
      5   doesNotMatch(value,pattern){if(pattern.test(value))throw Error('Unexpected '+pattern+' in '+value);},
      6 };
      7 export async function run(control, selectedBackend='haskell-wasm') {
      8   assert.equal([...document.getElementById('runtime-backend').options].some(x=>x.value===selectedBackend),true,'Selected backend must be offered by the real page');
      9   const selectBackend=async()=>{const el=document.getElementById('runtime-backend');el.value=selectedBackend;el.dispatchEvent(new Event('change'));};
     10   const compilerHash=(await (await fetch(new URL('../compiler.json',document.querySelector('script[type=module][src]').src))).json()).hash;
     11   const evaluate=async expression=>(0,eval)(expression);
     12   const requestLog=async()=> (await fetch(control+'/requests')).json();
     13   const fault=async values=>{const r=await fetch(control+'/fault',{method:'POST',body:JSON.stringify(values)});if(!r.ok)throw Error('Fault control failed');};
     14   const wait=async(expression,seconds=30)=>{
     15     const until=Date.now()+seconds*1000;
     16     while(Date.now()<until){if(await evaluate(expression))return;await new Promise(r=>setTimeout(r,25));}
     17     throw Error('Timed out '+expression+'\n'+await evaluate("document.getElementById('result').textContent"));
     18   };
     19   const status=s=>`document.getElementById('status').textContent===${JSON.stringify(s)}`;
     20   const click=async id=>evaluate(`document.getElementById(${JSON.stringify(id)}).click()`);
     21   const select=async id=>{await evaluate(`document.getElementById('program-picker').value='demo:${id}';document.getElementById('program-picker').dispatchEvent(new Event('change'))`);await wait(status('Ready'));};
     22   await wait(status('Ready'));
     23   assert.equal(document.getElementById('runtime-backend').value,'haskell-wasm','Fresh page defaults to Haskell');
     24   document.getElementById('use-guided-inputs').checked=true;document.getElementById('use-guided-inputs').dispatchEvent(new Event('change'));
     25   await wait(status('Ready'));
     26   await click('run');await wait(status('Finished'));
     27   assert.equal(await evaluate("document.getElementById('result').textContent"),'Hello, world!');
     28   assert.equal((await requestLog()).some(x=>x.endsWith('/runtime/haskell/reactor.wasm')),true,'Default actually loads Haskell');
     29   await fault({runtime:true});
     30   await selectBackend();await wait(status('Stopped'));
     31   assert.match(await evaluate("document.getElementById('result').textContent"),/WASM fetch failed|HTTP|fetch/i);
     32   assert.equal(await evaluate("document.getElementById('run').disabled"),true,'runtime failure must not fall back to JS');
     33   await fault({runtime:false});await select('language-reference');
     34   assert.equal((await requestLog()).some(x=>x.endsWith('/data/compiler.dag')||x.endsWith('/hash/'+compilerHash)),false,'packages do not load a compiler kit');
     35   await click('run');await wait(status('Finished'));
     36   assert.equal(await evaluate("document.getElementById('result').textContent"),'42');
     37   // The same real page can reinitialize each supported published package.
     38   for(const name of ['rgb','microscope','shipping','events','registration','migration']) {
     39     await select(name);await click('run');await wait(status('Finished'));
     40     assert.match(await evaluate("document.getElementById('result').textContent"),/.+/);
     41   }
     42   await select('greet');await click('run');await wait(status('Waiting for input'));
     43   await evaluate("document.getElementById('line-input').value='λ雪🙂';document.getElementById('input-form').requestSubmit()");await wait(status('Finished'));
     44   assert.match(await evaluate("document.getElementById('terminal').textContent"),/λ雪🙂/);
     45   // Stored export remains backend-independent and does not run the program.
     46   await evaluate("document.getElementById('output-target').value='definition';document.getElementById('output-target').dispatchEvent(new Event('change'))");
     47   await evaluate("document.getElementById('output-type').value='dag';document.getElementById('output-type').dispatchEvent(new Event('change'))");
     48   await click('run');await wait("!document.getElementById('copy-output').disabled");
     49   assert.match(await evaluate("document.getElementById('result').textContent"),/tree/);
     50   await evaluate("document.getElementById('output-target').value='result';document.getElementById('output-target').dispatchEvent(new Event('change'))");await select('language-reference');
     51   await evaluate("document.getElementById('source-editor').value=document.getElementById('source-editor').value.replace('first 42 7','first 43 7');document.getElementById('source-editor').dispatchEvent(new Event('input'))");
     52   await click('compile');await wait(status('Compiled'));
     53   assert.equal((await requestLog()).some(x=>x.endsWith('/data/compiler.dag')||x.endsWith('/hash/'+compilerHash)),true);
     54   assert.match(await evaluate("document.getElementById('compiler-stats').textContent"),/WASM linear memory/);
     55   assert.doesNotMatch(await evaluate("document.getElementById('compiler-stats').textContent"),/work units|heap nodes/);
     56   assert.equal(await evaluate("document.getElementById('source-editor').classList.contains('modified')"),false);
     57   for(let i=0;i<3;i++){await click('run');await wait(status('Finished'));assert.equal(await evaluate("document.getElementById('result').textContent"),'43');}
     58   await evaluate("document.getElementById('output-type').value='string'");await click('run');await wait(status('Stopped'));
     59   assert.equal(await evaluate("document.getElementById('run').disabled"),false,'recoverable run errors preserve definitions');
     60   await evaluate("document.getElementById('output-type').value='number';document.getElementById('source-editor').value+='\\n-- unapplied';document.getElementById('source-editor').dispatchEvent(new Event('input'))");
     61   assert.equal(await evaluate("document.getElementById('source-editor').classList.contains('modified')"),true);
     62   assert.equal(document.getElementById('run').disabled,true,'Unapplied edits cannot run old definitions');
     63   await wait(status('Compiled'));await click('run');await wait(status('Finished'));assert.equal(await evaluate("document.getElementById('result').textContent"),'43');
     64   await click('restore-source');await wait(status('Ready'));
     65   // A practical draft exercises explicit library bytes/pins/native name resolution.
     66   await select('shipping');await evaluate("document.getElementById('source-editor').value+='\\n-- adapter test';document.getElementById('source-editor').dispatchEvent(new Event('input'))");
     67   await click('compile');await wait(status('Compiled'));await click('run');await wait(status('Finished'));
     68   // Scratch compilation selects the runtime's authenticated default environment.
     69   await evaluate("document.querySelector('[data-compiler-example=contracts]').click()");await click('compile');await wait(status('Compiled'));
     70   await click('run');await wait(status('Finished'));assert.equal(await evaluate("document.getElementById('result').textContent"),'42');
     71   await fault({compilerPin:true});
     72   await evaluate("document.getElementById('source-editor').value+='\\n-- pin control';document.getElementById('source-editor').dispatchEvent(new Event('input'))");
     73   await click('compile');await wait(status('Compilation failed'));
     74   assert.match(await evaluate("document.getElementById('compile-diagnostic').textContent"),/Runtime compiler pin mismatch|HTTP|fetch/i);
     75   assert.equal(await evaluate("document.getElementById('run').disabled"),true);
     76   await fault({compilerPin:false});
     77   await evaluate("document.querySelector('[data-compiler-example=input]').click()");await click('compile');await wait(status('Compiled'));
     78   await click('run');await wait(status('Waiting for input'));await click('stop');await wait(status('Stopped'));
     79   assert.equal(await evaluate("document.getElementById('run').disabled"),true,'hard Stop loses persistent WASM definitions');
     80   assert.match(await evaluate("document.getElementById('result').textContent"),/compile the draft again/);
     81   await click('compile');await wait(status('Compiled'));
     82   await evaluate("document.getElementById('step-mode').checked=true");await click('run');await wait(status('Paused before action'));
     83   await click('next');await wait(status('Waiting for input'));
     84   await evaluate("document.getElementById('line-input').value='restart🙂';document.getElementById('input-form').requestSubmit()");await wait(status('Finished'));
     85   assert.equal(await evaluate("document.getElementById('result').textContent"),'restart🙂');
     86   // An actual non-returning reduction must remain stoppable from the page.
     87   await evaluate("document.getElementById('source-editor').value='id = (x : x)';document.getElementById('source-editor').dispatchEvent(new Event('input'))");
     88   await click('compile');await wait(status('Compiled'));
     89   await evaluate("document.getElementById('mode').value='pure';document.getElementById('output-type').value='tricu';document.getElementById('arguments').value=JSON.stringify([{type:'tree',value:'212110021100'},{type:'tree',value:'212110021100'}])");
     90   await click('run');await wait(status('Running'));
     91   await new Promise(r=>setTimeout(r,150));assert.equal(await evaluate("document.getElementById('status').textContent"),'Running');
     92   await click('stop');await wait(status('Stopped'));assert.equal(await evaluate("document.getElementById('run').disabled"),true);
     93   // Backend switching preserves text but never carries heaps/checkpoints across.
     94   const source=await evaluate("document.getElementById('source-editor').value");
     95   await evaluate(`document.getElementById('runtime-backend').value=${JSON.stringify(selectedBackend==='haskell-wasm'?'zig-wasm':'haskell-wasm')};document.getElementById('runtime-backend').dispatchEvent(new Event('change'))`);
     96   assert.equal(await evaluate("document.getElementById('run').disabled"),true);
     97   assert.equal(await evaluate("document.getElementById('source-editor').value"),source);
     98   await click('compile');await wait(status('Compiled'));
     99   assert.match(await evaluate("document.getElementById('compiler-stats').textContent"),/MiB WASM linear memory/);
    100   await(await import('/backend-resume-probe.mjs')).probeResume(selectedBackend);
    101   const parity=selectedBackend==='zig-wasm'?await(await import('/zig-session-probe.mjs')).run():null;
    102   return {parity,userAgent:navigator.userAgent,checks:['Haskell default startup/execution','package/IO/export','default/empty/practical compilation','persistent definitions','recoverable errors','artifact/pin failure without fallback','hard Stop/recompile including CPU-bound work','IO stepping/stale and malformed resume rejection','backend-switch isolation']};
    103 }