this repo has no description
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>