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 }