this repo has no description
at main 242 lines 10 kB view raw
1<!DOCTYPE html> 2<html> 3<head> 4 <title>Preloaded Package Integration Test</title> 5 <style> 6 body { font-family: monospace; padding: 20px; } 7 #status { margin-bottom: 20px; } 8 .pass { color: green; } 9 .fail { color: red; } 10 </style> 11</head> 12<body> 13 <h1>Preloaded Package Integration Test</h1> 14 <div id="status">Running tests...</div> 15 <pre id="log"></pre> 16 17 <script> 18 // Capture console.log for display 19 const logEl = document.getElementById('log'); 20 const originalLog = console.log; 21 console.log = function(...args) { 22 originalLog.apply(console, args); 23 const text = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' '); 24 logEl.textContent += text + '\n'; 25 }; 26 </script> 27 <script> 28 (async function() { 29 const results = { total: 3, passed: 0, failed: 0, done: false }; 30 window.testResults = results; 31 32 // Helper: send JSON message to worker 33 function send(worker, msg) { 34 worker.postMessage(JSON.stringify(msg)); 35 } 36 37 // Helper: wait for a specific message type from worker 38 function waitFor(worker, predicate, timeoutMs) { 39 timeoutMs = timeoutMs || 30000; 40 return new Promise(function(resolve, reject) { 41 var timer = setTimeout(function() { 42 reject(new Error('Timeout waiting for message')); 43 }, timeoutMs); 44 function handler(ev) { 45 var msg = typeof ev.data === 'string' ? JSON.parse(ev.data) : ev.data; 46 if (predicate(msg)) { 47 clearTimeout(timer); 48 worker.removeEventListener('message', handler); 49 resolve(msg); 50 } 51 } 52 worker.addEventListener('message', handler); 53 }); 54 } 55 56 // Helper: evaluate code and wait for output or eval_error 57 function evalCode(worker, cellId, code) { 58 return new Promise(function(resolve, reject) { 59 var accumulated = ''; 60 var timer = setTimeout(function() { 61 reject(new Error('Timeout evaluating: ' + code)); 62 }, 30000); 63 64 function handler(ev) { 65 var msg = typeof ev.data === 'string' ? JSON.parse(ev.data) : ev.data; 66 if (msg.type === 'output_at' && msg.cell_id === cellId) { 67 accumulated += (msg.caml_ppf || ''); 68 } else if (msg.type === 'output' && msg.cell_id === cellId) { 69 clearTimeout(timer); 70 worker.removeEventListener('message', handler); 71 // Merge accumulated output 72 if (accumulated && (!msg.caml_ppf || msg.caml_ppf === '')) { 73 msg.caml_ppf = accumulated; 74 } 75 resolve(msg); 76 } else if (msg.type === 'eval_error' && msg.cell_id === cellId) { 77 clearTimeout(timer); 78 worker.removeEventListener('message', handler); 79 resolve(msg); 80 } 81 } 82 worker.addEventListener('message', handler); 83 send(worker, { type: 'eval', cell_id: cellId, env_id: 'default', code: code }); 84 }); 85 } 86 87 try { 88 // Create worker via blob URL (same pattern as ocaml-worker.js) 89 var baseUrl = new URL('integ_worker.bc.js', window.location.href).href; 90 var baseDir = baseUrl.replace(/\/integ_worker\.bc\.js$/, ''); 91 var blobContent = 'globalThis.__global_rel_url="' + baseDir + '";\nimportScripts("' + baseUrl + '");'; 92 var blobUrl = URL.createObjectURL(new Blob([blobContent], { type: 'text/javascript' })); 93 var worker = new Worker(blobUrl); 94 95 worker.onerror = function(e) { 96 console.log('WORKER ERROR: ' + e.message); 97 }; 98 99 // Listen for all messages for debugging 100 worker.addEventListener('message', function(ev) { 101 var msg = typeof ev.data === 'string' ? JSON.parse(ev.data) : ev.data; 102 console.log('worker msg: ' + msg.type + (msg.cell_id !== undefined ? ' cell=' + msg.cell_id : '')); 103 }); 104 105 // Send init 106 console.log('Sending init...'); 107 var readyPromise = waitFor(worker, function(msg) { 108 return msg.type === 'ready' || msg.type === 'init_error'; 109 }, 60000); 110 111 send(worker, { 112 type: 'init', 113 findlib_requires: [], 114 findlib_index: 'test_findlib_index.json', 115 stdlib_dcs: '_universe/lib/ocaml/dynamic_cmis.json' 116 }); 117 118 var initMsg = await readyPromise; 119 if (initMsg.type === 'init_error') { 120 throw new Error('Init failed: ' + initMsg.message); 121 } 122 console.log('Worker ready!'); 123 124 // ========================================================= 125 // Test 1: Preloaded package (yojson) 126 // yojson is linked into the worker binary via js_top_worker-web. 127 // #require "yojson" should succeed WITHOUT fetching yojson.cma.js. 128 // ========================================================= 129 console.log('\n--- Test 1: Preloaded package (yojson) ---'); 130 var r1 = await evalCode(worker, 1, '#require "yojson";; Yojson.Safe.from_string "[1,2,3]";;'); 131 console.log('Result type: ' + r1.type); 132 console.log('stdout: ' + (r1.stdout || '')); 133 console.log('stderr: ' + (r1.stderr || '')); 134 console.log('caml_ppf: ' + (r1.caml_ppf || '')); 135 if (r1.type === 'output') { 136 console.log('TEST 1 PASSED: yojson loaded as preloaded package'); 137 results.passed++; 138 } else { 139 console.log('TEST 1 FAILED: expected output, got ' + r1.type + ': ' + (r1.message || '')); 140 results.failed++; 141 } 142 143 // ========================================================= 144 // Test 2: Normal package (stringext) 145 // stringext is NOT in the worker binary. 146 // #require "stringext" should fetch and load stringext.cma.js. 147 // ========================================================= 148 console.log('\n--- Test 2: Normal package (stringext) ---'); 149 var r2 = await evalCode(worker, 2, '#require "stringext";; Stringext.string_after "hello world" 6;;'); 150 console.log('Result type: ' + r2.type); 151 console.log('stdout: ' + (r2.stdout || '')); 152 console.log('stderr: ' + (r2.stderr || '')); 153 console.log('caml_ppf: ' + (r2.caml_ppf || '')); 154 if (r2.type === 'output') { 155 console.log('TEST 2 PASSED: stringext loaded from universe'); 156 results.passed++; 157 } else { 158 console.log('TEST 2 FAILED: expected output, got ' + r2.type + ': ' + (r2.message || '')); 159 results.failed++; 160 } 161 162 // ========================================================= 163 // Test 3: CRC mismatch (crc_conflict) 164 // crc_conflict claims module Yojson with a bogus CRC. 165 // Since Yojson IS in the binary but CRCs differ, 166 // is_package_preloaded should raise Crc_mismatch. 167 // 168 // The exception propagates to Lwt.async's unhandled handler 169 // (logged to console) rather than returning as eval_error. 170 // We detect this by: (a) the #require produces no response, 171 // and (b) a subsequent eval confirms the worker is still alive. 172 // ========================================================= 173 console.log('\n--- Test 3: CRC mismatch (crc_conflict) ---'); 174 175 // Race: wait for eval response OR 5s timeout 176 var test3result = await Promise.race([ 177 evalCode(worker, 3, '#require "crc_conflict";;').then(function(r) { return { kind: 'response', data: r }; }), 178 new Promise(function(resolve) { setTimeout(function() { resolve({ kind: 'timeout' }); }, 5000); }) 179 ]); 180 181 var test3passed = false; 182 if (test3result.kind === 'response') { 183 var r3 = test3result.data; 184 console.log('Got response type: ' + r3.type); 185 // If we got an eval_error with Crc_mismatch, that's a pass 186 if (r3.type === 'eval_error' && r3.message && r3.message.indexOf('Crc_mismatch') >= 0) { 187 test3passed = true; 188 } 189 // If output contains the error, that's also a pass 190 if (r3.type === 'output') { 191 var allOutput = (r3.stderr || '') + (r3.caml_ppf || '') + (r3.stdout || ''); 192 if (allOutput.indexOf('Crc_mismatch') >= 0 || allOutput.indexOf('CRC mismatch') >= 0) { 193 test3passed = true; 194 } 195 } 196 } else { 197 // Timeout: exception escaped to Lwt.async (expected behavior). 198 // The Crc_mismatch exception was logged to the worker's console 199 // (verified by Playwright in run_integ.js). From the page, we verify 200 // the worker is still alive — proving the error was non-fatal. 201 console.log('No response (exception escaped to Lwt.async). Probing worker...'); 202 try { 203 var probe = await evalCode(worker, 99, '1 + 1;;'); 204 if (probe.type === 'output') { 205 console.log('Worker still alive after Crc_mismatch (probe returned: ' + (probe.caml_ppf || '') + ')'); 206 test3passed = true; 207 } 208 } catch (probeErr) { 209 console.log('Worker probe failed: ' + probeErr.message); 210 } 211 } 212 213 if (test3passed) { 214 console.log('TEST 3 PASSED: CRC mismatch correctly detected'); 215 results.passed++; 216 } else { 217 console.log('TEST 3 FAILED: expected Crc_mismatch error'); 218 results.failed++; 219 } 220 221 } catch(e) { 222 console.log('FATAL ERROR: ' + e.message); 223 console.log(e.stack); 224 results.failed = results.total - results.passed; 225 } 226 227 // Report final results 228 results.done = true; 229 console.log('\n========================================'); 230 console.log('Results: ' + results.passed + '/' + results.total + ' passed'); 231 console.log('========================================'); 232 233 var status = document.getElementById('status'); 234 if (results.failed === 0) { 235 status.innerHTML = '<span class="pass">All ' + results.passed + ' tests passed!</span>'; 236 } else { 237 status.innerHTML = '<span class="fail">' + results.failed + ' tests failed</span> (' + results.passed + ' passed)'; 238 } 239 })(); 240 </script> 241</body> 242</html>