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 3# [ test ] 4fn exposed_constructors_ignore_reparenting_and_preserve_subclassing () { 5let output = common:: run ( 6r#" 7import { errors } from 'avril:std'; 8let calls = 0; 9function Other() { calls++; } 10for (const parent of [Other, null]) { 11for (const ctor of [DOMException, CustomEvent, ErrorEvent, PromiseRejectionEvent, MessageEvent, AbortSignal, MessagePort, ...Object.values(errors)]) { 12Object.setPrototypeOf(ctor, parent); 13} 14const exception = new DOMException('message', 'AbortError'); 15if (!(exception instanceof Error) || exception.message !== 'message' || exception.code !== 20) throw Error('Exception initialization'); 16const custom = new CustomEvent('custom', { detail: 42, cancelable: true }); 17const error = new ErrorEvent('error', { message: 'message', lineno: 7 }); 18const promise = Promise.resolve(); 19const rejection = new PromiseRejectionEvent('rejection', { promise, reason: 9 }); 20const message = new MessageEvent('message', { data: 3, origin: 'origin' }); 21if (custom.detail !== 42 || !custom.cancelable || error.message !== 'message' || error.lineno !== 7 || rejection.promise !== promise || rejection.reason !== 9 || message.data !== 3 || message.origin !== 'origin') throw Error('Event initialization'); 22const target = new EventTarget(); 23let events = 0; 24target.addEventListener('custom', () => events++); 25target.dispatchEvent(custom); 26if (events !== 1 || custom.target !== target) throw Error('Event base state'); 27const controller = new AbortController(); 28let aborted = false; 29controller.signal.addEventListener('abort', () => aborted = true); 30controller.abort(11); 31if (!aborted || controller.signal.reason !== 11 || !AbortSignal.abort().aborted) throw Error('Signal base state'); 32const channel = new MessageChannel(); 33let delivered = false; 34channel.port2.onmessage = event => { 35if (event.data !== 12 || event.type !== 'message' || event.target !== channel.port2) throw Error('Port delivery'); 36delivered = true; 37}; 38channel.port1.postMessage(12); 39await new Promise(resolve => setTimeout(resolve, 10)); 40if (!delivered) throw Error('MessagePort base state'); 41channel.port1.close(); channel.port2.close(); 42for (const [name, ctor] of Object.entries(errors)) { 43const error = new ctor('failure'); 44if (!(error instanceof Error) || error.name !== name || error.message !== 'failure') throw Error('File error initialization'); 45} 46class Child extends CustomEvent { #value = 13; get value() { return this.#value; } } 47const child = new Child('child', { detail: 14 }); 48if (!(child instanceof Child) || !(child instanceof CustomEvent) || !(child instanceof Event) || child.type !== 'child' || child.detail !== 14 || child.value !== 13) throw Error('Guest subclass'); 49} 50if (calls) throw Error('Guest superclass called'); 51console.log('constructors safe'); 52"# , 53& [], 54); 55assert_eq! ( output. stdout , b"constructors safe\n" ); 56} 57 58# [ test ] 59fn message_event_initialization_uses_the_captured_base_method () { 60let output = common:: run ( 61r#" 62Event.prototype.initEvent = () => { throw Error('Guest base method'); }; 63const event = new MessageEvent('before'); 64event.initMessageEvent('after', true, true, 42); 65if (event.type !== 'after' || !event.bubbles || !event.cancelable || event.data !== 42) throw Error('Event initialization'); 66console.log('base method safe'); 67"# , 68& [], 69); 70assert_eq! ( output. stdout , b"base method safe\n" ); 71} 72 73# [ test ] 74fn file_write_options_do_not_iterate_internal_arrays () { 75let output = common:: run ( 76r#" 77import { writeTextFile, readTextFile } from 'avril:fs'; 78Array.prototype[Symbol.iterator] = () => { throw Error('Guest array iterator'); }; 79const path = new URL('./data', import.meta.url); 80await writeTextFile(path, 'safe'); 81await writeTextFile(path, ' append', { append: true }); 82if (await readTextFile(path) !== 'safe append') throw Error('Write flags'); 83console.log('file options safe'); 84"# , 85& [], 86); 87assert_eq! ( output. stdout , b"file options safe\n" ); 88} 89 90const PROMISE_TEST : & str =r#" 91import { stdin } from 'avril:std'; 92import * as fs from 'avril:fs'; 93const originalPromise = Promise, originalThen = Promise.prototype.then; 94function observe(promise) { 95Object.defineProperty(promise, 'constructor', { value: undefined, configurable: true }); 96const result = Reflect.apply(originalThen, promise, [value => value, error => { throw error; }]); 97Object.defineProperty(result, 'constructor', { value: originalPromise }); 98return result; 99} 100let hooks = 0; 101const poison = () => { hooks++; throw Error('Guest promise hook'); }; 102POISON 103const response = new Response(new Blob(['hello']).stream()); 104if (await observe(response.text()) !== 'hello') throw Error('Body consumption'); 105const source = new ReadableStream({ 106start(controller) { controller.enqueue('chunk'); controller.close(); }, 107}); 108const clone = structuredClone(source, { transfer: [source] }); 109const reader = clone.getReader(); 110if ((await observe(reader.read())).value !== 'chunk' || !(await observe(reader.read())).done) throw Error('Stream transfer'); 111reader.releaseLock(); 112const iterator = new ReadableStream({ start(controller) { controller.close(); } }).values(); 113if (!(await observe(iterator.next())).done || !(await observe(iterator.next())).done || !(await observe(iterator.return())).done) throw Error('Closed stream iterator'); 114let writes = ''; 115const stream = new ReadableStream({ start(controller) { controller.enqueue('pipe'); controller.close(); } }); 116await observe(stream.pipeTo(new WritableStream({ write(chunk) { writes += chunk; return new originalPromise(resolve => resolve()); } }))); 117if (writes !== 'pipe') throw Error('Promise callback assimilation'); 118const abortController = new AbortController(); 119const piping = new ReadableStream().pipeTo(new WritableStream(), { signal: abortController.signal }); 120abortController.abort(42); 121try { await observe(piping); throw Error('Abort accepted'); } catch (error) { if (error !== 42) throw error; } 122const bytes = await observe(new Response(new Blob(['hello']).stream().pipeThrough(new CompressionStream('gzip'))).bytes()); 123if (await observe(new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).text()) !== 'hello') throw Error('Codec promises'); 124const path = new URL('./data', import.meta.url); 125await observe(fs.writeTextFile(path, 'file')); 126if (await observe(fs.readTextFile(path)) !== 'file') throw Error('File promises'); 127const input = stdin.getReader(); 128if (new TextDecoder().decode((await observe(input.read())).value) !== 'stdin') throw Error('Stdio promises'); 129input.releaseLock(); 130const wasm = new Response(new Uint8Array([0,97,115,109,1,0,0,0]), { headers: { 'content-type': 'application/wasm' } }); 131if (!(await observe(WebAssembly.compileStreaming(new originalPromise(resolve => resolve(wasm)))) instanceof WebAssembly.Module)) throw Error('Wasm promises'); 132let thenableCalls = 0; 133await observe(new WritableStream({ start() { return { then(resolve) { thenableCalls++; resolve(); } }; } }).close()); 134if (thenableCalls !== 1 || hooks !== 0) throw Error('Promise protocol'); 135console.log('promises safe'); 136"# ; 137 138# [ test ] 139fn internal_reactions_ignore_promise_species () { 140promises ( "Object.defineProperty(Promise, Symbol.species, { get: poison });" ); 141} 142 143# [ test ] 144fn internal_awaits_ignore_promise_constructor_getters () { 145promises ( 146"Object.defineProperty(Promise.prototype, 'constructor', { get: poison, configurable: true });" , 147); 148} 149 150# [ test ] 151fn internal_assimilation_ignores_replaced_promise_then () { 152promises ( 153"Promise.prototype.then = poison; Promise.resolve = poison; Promise.reject = poison; Promise.all = poison;" , 154); 155} 156 157fn promises ( poison : & str ) { 158let source =PROMISE_TEST . replace ( "POISON" , poison); 159let output = common:: run ( & source, b"stdin" ); 160assert_eq! ( output. stdout , b"promises safe\n" ); 161}