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 broadcast_fanout_clones_once_and_preserves_fifo_tasks () { 5let output = common:: run ( 6r#" 7const sender = new BroadcastChannel('test'), a = new BroadcastChannel('test'), b = new BroadcastChannel('test'); 8const other = new BroadcastChannel('other'); 9sender.onmessage = other.onmessage = () => {throw Error('Wrong recipient');}; 10let reads = 0; 11const values = {get value() {reads++; return 1;}}; 12sender.postMessage(values); 13sender.postMessage({value: 2}); 14const seen = []; 15a.onmessage = event => { 16if (event.target !== a || !(event instanceof MessageEvent) || !event.isTrusted || 17event.source !== null || event.ports.length !== 0 || event.origin !== '') throw Error('MessageEvent'); 18seen.push(event.data.value); event.data.value = 9; 19queueMicrotask(() => seen.push('microtask')); 20}; 21const received = new Promise(resolve => b.onmessage = event => { 22if (event.data.value !== 1 && event.data.value !== 2) throw Error('Shared clone'); 23if (event.data.value === 2) resolve(); 24}); 25queueMicrotask(() => seen.push('before')); 26await received; 27await new Promise(resolve => setTimeout(resolve, 10)); 28if (reads !== 1 || seen.join() !== 'before,1,microtask,2,microtask') throw Error('Clone/task order ' + seen); 29for (const channel of [sender,a,b,other]) channel.close(); 30console.log('broadcast'); 31"# , 32& [], 33); 34assert_eq! ( output. stdout , b"broadcast\n" ); 35} 36 37# [ test ] 38fn broadcast_brands_clone_errors_and_close () { 39let output = common:: run ( 40r#" 41class Sub extends BroadcastChannel {} 42const a = new Sub({toString() {return 'name';}}), b = new BroadcastChannel('name'); 43if (a.name !== 'name' || !(a instanceof EventTarget) || !(a instanceof Sub) || 44Object.prototype.toString.call(a) !== '[object BroadcastChannel]' || a.onmessage !== null || a.onmessageerror !== null) throw Error('Surface'); 45for (const operation of [() => new BroadcastChannel(), () => new BroadcastChannel(Symbol()), 46() => BroadcastChannel.prototype.close.call({}), () => a.postMessage()]) { 47try {operation(); throw Error('Accepted invalid input');} catch (error) {if (!(error instanceof TypeError)) throw error;} 48} 49const buffer = new Uint8Array([1]).buffer; 50for (const value of [() => {}, a, new MessageChannel().port1]) { 51try {a.postMessage(value); throw Error('Clone accepted');} catch (error) {if (error.name !== 'DataCloneError') throw error;} 52} 53a.postMessage(buffer, [buffer]); 54if (buffer.byteLength !== 1) throw Error('Unexpected transfer'); 55b.onmessage = () => {throw Error('Closed destination received a message');}; 56b.close(); b.close(); 57a.close(); 58try {a.postMessage(() => {}); throw Error('Closed channel posted');} catch (error) {if (error.name !== 'InvalidStateError') throw error;} 59const lonely = new BroadcastChannel('lonely'); 60try {lonely.postMessage(() => {}); throw Error('No-recipient clone skipped');} catch (error) {if (error.name !== 'DataCloneError') throw error;} 61lonely.close(); 62await new Promise(resolve => setTimeout(resolve, 10)); 63console.log('closed'); 64"# , 65& [], 66); 67assert_eq! ( output. stdout , b"closed\n" ); 68} 69 70# [ test ] 71fn broadcasts_cross_worker_isolates_and_clone_host_objects () { 72let dir = tempfile:: tempdir (). unwrap (); 73 std:: fs:: write ( dir. path (). join ( "worker.js" ), r#" 74const channel = new BroadcastChannel('workers'); 75channel.onmessage = async event => { 76if (!(event.data.blob instanceof Blob) || await event.data.blob.text() !== 'blob' || event.data.map.get('key') !== 4) throw Error('Host clone'); 77channel.postMessage('reply'); 78channel.close(); close(); 79}; 80postMessage('ready'); 81"# ). unwrap (); 82 std:: fs:: write ( dir. path (). join ( "main.js" ), r#" 83const channel = new BroadcastChannel('workers'); 84const worker = new Worker(new URL('./worker.js', import.meta.url), {type:'module'}); 85await new Promise((resolve, reject) => {worker.onmessage = resolve; worker.onerror = reject;}); 86const reply = new Promise(resolve => channel.onmessage = event => resolve(event.data)); 87const post = BroadcastChannel.prototype.postMessage, close = BroadcastChannel.prototype.close; 88MessagePort.prototype.postMessage = () => {throw Error('Guest port');}; 89globalThis.structuredClone = () => {throw Error('Guest clone');}; 90post.call(channel, {blob:new Blob(['blob']), map:new Map([['key',4]])}); 91if (await reply !== 'reply') throw Error('Worker reply'); 92close.call(channel); worker.terminate(); 93console.log('workers'); 94"# ). unwrap (); 95let output = common:: wait ( 96 std:: process:: Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 97. arg ( "run" ) 98. arg ( dir. path (). join ( "main.js" )) 99. stdout ( std:: process:: Stdio :: piped ()) 100. stderr ( std:: process:: Stdio :: piped ()) 101. spawn () 102. unwrap (), 103); 104assert! ( 105 output. status . success (), 106"{}" , 107String :: from_utf8_lossy ( & output. stderr ) 108); 109assert_eq! ( output. stdout , b"workers\n" ); 110} 111 112# [ test ] 113fn broadcast_names_preserve_exact_domstring_code_units () { 114let output = common:: run ( 115r#" 116const names = ['\uD800','\uDC00','\uFFFD','valid😀','nul\0name']; 117const senders = names.map(name => new BroadcastChannel(name)); 118const receivers = names.map(name => new BroadcastChannel(name)); 119const counts = names.map(() => 0); 120const completed = new Promise(resolve => receivers.forEach((channel,i) => { 121if (channel.name !== names[i]) throw Error('Name conversion'); 122channel.onmessage = event => { 123if (event.data !== i || ++counts[i] !== 1) throw Error('Name crosstalk'); 124if (counts.every(count => count === 1)) resolve(); 125}; 126})); 127senders.forEach((channel,i) => channel.postMessage(i)); 128await completed; 129await new Promise(resolve => setTimeout(resolve,10)); 130[...senders,...receivers].forEach(channel => channel.close()); 131console.log('names'); 132"# , 133& [], 134); 135assert_eq! ( output. stdout , b"names\n" ); 136}