char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1mod common; 2 3use std:: io::{ BufRead , Write }; 4use std:: net:: TcpListener ; 5use std:: thread; 6use std:: time:: Duration ; 7 8use common:: run; 9 10# [ test ] 11fn wasm_streaming_compiles_and_instantiates_incrementally () { 12let output =run ( 13r#" 14const bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]); 15function response(body = bytes, headers = {'content-type': 'application/wasm'}, status = 200) { 16return new Response(body, {headers, status}); 17} 18const chunks = new ReadableStream({ 19start(controller) { this.controller = controller; this.index = 0; }, 20pull(controller) { 21if (this.index === bytes.length) controller.close(); 22else controller.enqueue(bytes.slice(this.index, ++this.index)); 23}, 24}); 25const streamed = response(chunks, {'content-type': 'application/wasm; charset=binary'}); 26const module = await WebAssembly.compileStreaming(Promise.resolve(streamed), {}); 27if (!(module instanceof WebAssembly.Module) || !streamed.bodyUsed) throw Error('Streaming compile'); 28let controller, settled = false; 29const pendingResponse = response(new ReadableStream({start(c) { controller = c; }})); 30const pending = WebAssembly.compileStreaming(pendingResponse).then(value => { settled = true; return value; }); 31controller.enqueue(bytes); 32for (let i = 0; i < 10 && !pendingResponse.bodyUsed; i++) await Promise.resolve(); 33if (settled || !pendingResponse.bodyUsed) throw Error('Streaming waited for body aggregation'); 34controller.close(); 35if (!(await pending instanceof WebAssembly.Module)) throw Error('Incremental completion'); 36const instantiatedResponse = response(); 37const result = await WebAssembly.instantiateStreaming(instantiatedResponse, {}, {}); 38if (!(result.module instanceof WebAssembly.Module) || !(result.instance instanceof WebAssembly.Instance)) { 39throw Error('Streaming instantiate result'); 40} 41if (WebAssembly.compileStreaming.length !== 1 || WebAssembly.instantiateStreaming.length !== 2) { 42throw Error('Streaming function lengths'); 43} 44for (const fn of [WebAssembly.compileStreaming, WebAssembly.instantiateStreaming]) { 45try { new fn(); throw Error('Constructible streaming method'); } 46catch (error) { if (!(error instanceof TypeError)) throw error; } 47} 48if (WebAssembly.compileStreaming.name !== 'compileStreaming' || WebAssembly.instantiateStreaming.name !== 'instantiateStreaming') { 49throw Error('Streaming function names'); 50} 51for (const storage of [ 52new ArrayBuffer(12), 53new ArrayBuffer(12, {maxByteLength: 24}), 54new SharedArrayBuffer(12), 55new SharedArrayBuffer(12, {maxByteLength: 24}), 56]) { 57const view = new Uint8Array(storage, 2, bytes.length); 58view.set(bytes); 59const body = new ReadableStream({ 60start(c) {c.enqueue(view); c.close();} 61}); 62if (!(await WebAssembly.compileStreaming(response(body)) instanceof WebAssembly.Module)) { 63throw Error('Buffer storage and view bounds'); 64} 65} 66console.log('wasm stream success'); 67"# , 68& [], 69); 70assert_eq! ( output. stdout , b"wasm stream success\n" ); 71} 72 73# [ test ] 74fn wasm_streaming_consumes_chunked_http_responses_incrementally () { 75let listener =TcpListener :: bind ( "127.0.0.1:0" ). unwrap (); 76let url =format! ( "http://{}" , listener. local_addr (). unwrap ()); 77let server = thread:: spawn ( move ||{ 78let ( mut socket, _) = listener. accept (). unwrap (); 79let mut request = std:: io:: BufReader :: new ( socket. try_clone (). unwrap ()); 80let mut line =String :: new (); 81 request. read_line ( & mut line). unwrap (); 82loop { 83 line. clear (); 84 request. read_line ( & mut line). unwrap (); 85if line =="\r\n" { 86break ; 87} 88} 89 socket. write_all ( b"HTTP/1.1 200 OK\r\nContent-Type: application/wasm\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n4\r\n\x00asm\r\n" ). unwrap (); 90 socket. flush (). unwrap (); 91 thread:: sleep ( Duration :: from_millis ( 100 )); 92 socket 93. write_all ( b"4\r\n\x01\x00\x00\x00\r\n0\r\n\r\n" ) 94. unwrap (); 95}); 96let source =format! ( 97r#" 98const response = await fetch('{url}'); 99let settled = false; 100const compiling = WebAssembly.compileStreaming(response).then(module => {{ settled = true; return module; }}); 101for (let i = 0; i < 100 && !response.bodyUsed; i++) await new Promise(resolve => setTimeout(resolve, 2)); 102if (!response.bodyUsed || settled) throw Error('HTTP body was buffered before streaming'); 103if (!(await compiling instanceof WebAssembly.Module)) throw Error('Chunked HTTP module'); 104console.log('wasm http stream'); 105"# 106); 107assert_eq! ( run ( & source, & []). stdout , b"wasm http stream\n" ); 108 server. join (). unwrap (); 109} 110 111# [ test ] 112fn wasm_streaming_validates_responses_and_preserves_body_errors () { 113let output =run ( 114r#" 115const bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]); 116const importingModule = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2, 7, 1, 1, 109, 1, 102, 0, 0]); 117async function rejects(promise, Type, expected) { 118try { await promise; throw Error('Expected rejection'); } 119catch (error) { 120if (Type !== null && !(error instanceof Type)) throw error; 121if (expected !== undefined && error !== expected) throw Error('Rejection identity changed'); 122} 123} 124async function rejectsBeforeSource(promise, expected) { 125let settled = false, error; 126promise.then(() => { settled = true; }, reason => { settled = true; error = reason; }); 127for (let i = 0; i < 20 && !settled; i++) await Promise.resolve(); 128if (!settled) throw Error('Native argument validation waited for the source'); 129if (expected === undefined ? !(error instanceof TypeError) : error !== expected) throw error; 130} 131const never = new Promise(() => {}); 132await rejectsBeforeSource(WebAssembly.instantiateStreaming(never, 42)); 133await rejectsBeforeSource(WebAssembly.instantiateStreaming(Promise.reject('source failure'), 42)); 134const optionsReason = {badOptions: true}; 135await rejectsBeforeSource(WebAssembly.compileStreaming(never, {get builtins() { throw optionsReason; }}), optionsReason); 136await rejects(WebAssembly.compileStreaming({}), TypeError); 137await rejects(WebAssembly.compileStreaming(Promise.reject('source failure')), null, 'source failure'); 138const wrongMime = new Response(bytes); 139await rejects(WebAssembly.compileStreaming(wrongMime), TypeError); 140if (wrongMime.bodyUsed) throw Error('Rejected MIME response body was consumed'); 141await rejects(WebAssembly.compileStreaming(new Response(bytes, {headers: {'content-type': 'application/wasm'}, status: 404})), TypeError); 142const locked = new Response(bytes, {headers: {'content-type': 'application/wasm'}}); 143locked.body.getReader(); 144await rejects(WebAssembly.compileStreaming(locked), TypeError); 145const used = new Response(bytes, {headers: {'content-type': 'application/wasm'}}); 146await used.arrayBuffer(); 147await rejects(WebAssembly.compileStreaming(used), TypeError); 148const reason = {failure: true}; 149const errored = new Response(new ReadableStream({start(controller) { controller.error(reason); }}), { 150headers: {'content-type': 'application/wasm'}, 151}); 152await rejects(WebAssembly.compileStreaming(errored), Object, reason); 153const undefinedError = new Response(new ReadableStream({start(controller) { controller.error(undefined); }}), { 154headers: {'content-type': 'application/wasm'}, 155}); 156let gotUndefined = false; 157await WebAssembly.compileStreaming(undefinedError).then( 158() => { throw Error('Accepted errored response'); }, 159error => { gotUndefined = error === undefined; }, 160); 161if (!gotUndefined) throw Error('Undefined stream error was not preserved'); 162const invalidChunk = new Response(new ReadableStream({start(controller) { controller.enqueue('not bytes'); }}), { 163headers: {'content-type': 'application/wasm'}, 164}); 165await rejects(WebAssembly.compileStreaming(invalidChunk), TypeError); 166const truncated = new Response(bytes.subarray(0, 6), {headers: {'content-type': 'application/wasm'}}); 167await rejects(WebAssembly.compileStreaming(truncated), WebAssembly.CompileError); 168const missingImport = new Response(importingModule, {headers: {'content-type': 'application/wasm'}}); 169await rejects(WebAssembly.instantiateStreaming(missingImport, {m: {}}), WebAssembly.LinkError); 170const providedImport = new Response(importingModule, {headers: {'content-type': 'application/wasm'}}); 171const imported = await WebAssembly.instantiateStreaming(providedImport, {m: {f() {}}}); 172if (!(imported.instance instanceof WebAssembly.Instance)) throw Error('Streaming imports'); 173console.log('wasm stream errors'); 174"# , 175& [], 176); 177assert_eq! ( output. stdout , b"wasm stream errors\n" ); 178} 179 180# [ test ] 181fn ignored_streaming_compilation_failures_remain_unhandled () { 182let ( _dir, child) = common:: spawn ( 183"WebAssembly.compileStreaming(new Response(new Uint8Array([0]),{headers:{'content-type':'application/wasm'}}));" , 184); 185let out = common:: wait ( child); 186assert! ( !out. status . success ()); 187assert! ( String :: from_utf8_lossy ( & out. stderr ). contains ( "CompileError" )); 188}