char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsnapshot: embed shared main and worker startup in the CLI688e96e

main
8.7 KiB183 linesraw
1mod common;
2
3use common::run;
4
5#[test]
6fn message_channel_schedules_fifo_events_and_preserves_event_brands() {
7    let output = run(
8        r#"
9        if (!(new MessageChannel() instanceof Object) || !(new MessageEvent('message') instanceof Event)) {
10            throw Error('Message interfaces');
11        }
12        try { new MessagePort(); throw Error('MessagePort constructor'); }
13        catch (error) { if (!(error instanceof TypeError)) throw error; }
14        const legacy = new MessageEvent('old', {data: 1, ports: []});
15        legacy.initMessageEvent('new', false, true, 2);
16        if (legacy.type !== 'new' || legacy.data !== 2 || !legacy.cancelable) throw Error('initMessageEvent');
17        const channel = new MessageChannel(), seen = [];
18        if (Object.hasOwn(channel, 'port1') || Object.hasOwn(channel, 'port2') ||
19            channel.port1 === channel.port2) throw Error('MessageChannel attributes');
20        try { structuredClone(channel); throw Error('Cloned MessageChannel'); }
21        catch (error) { if (error.name !== 'DataCloneError') throw error; }
22        channel.port2.addEventListener('message', event => {
23            const data = event.data;
24            event.initMessageEvent('changed', false, false, 'changed');
25            if (event.type !== 'message' || event.data !== data) throw Error('initMessageEvent during dispatch');
26            seen.push([data, event.isTrusted, event.target === channel.port2,
27                event.currentTarget === channel.port2, event.ports]);
28        });
29        const snapshot = {value: 1};
30        channel.port1.postMessage(snapshot);
31        snapshot.value = 3;
32        channel.port1.postMessage(2);
33        await new Promise(resolve => setTimeout(resolve, 0));
34        if (seen.length !== 0) throw Error('addEventListener started the port');
35        channel.port2.start();
36        await new Promise(resolve => setTimeout(resolve, 10));
37        if (seen.length !== 2 || seen[0][0].value !== 1 || seen[1][0] !== 2
38            || seen.some(event => !event[1] || !event[2] || !event[3] || !Array.isArray(event[4])
39                || Object.getPrototypeOf(event[4]) !== Array.prototype || !Object.isFrozen(event[4]))) {
40            throw Error('Message task order or event state');
41        }
42        console.log('message FIFO');
43        "#,
44        &[],
45    );
46    assert_eq!(output.stdout, b"message FIFO\n");
47}
48
49#[test]
50fn message_port_transfer_preserves_graph_identity_and_moves_queued_messages() {
51    let output = run(
52        r#"
53        const channel = new MessageChannel(), peer = new MessageChannel();
54        const payload = {port: peer.port1};
55        payload.self = payload;
56        channel.port2.onmessage = event => {
57            if (event.data !== event.data.self || event.ports.length !== 1
58                || event.ports[0] !== event.data.port || !Object.isFrozen(event.ports)) {
59                throw Error('Transferred port identity');
60            }
61            event.ports[0].postMessage('reply');
62        };
63        peer.port2.onmessage = event => {
64            if (event.data !== 'reply') throw Error('Transferred endpoint');
65            console.log('message transfer');
66        };
67        channel.port1.postMessage(payload, [peer.port1]);
68        peer.port1.postMessage('detached source');
69        try { structuredClone({}, {transfer: [peer.port1]}); throw Error('Transferred port reused'); }
70        catch (error) { if (error.name !== 'DataCloneError') throw error; }
71
72        const queued = new MessageChannel(), relay = new MessageChannel();
73        queued.port1.postMessage('queued before transfer');
74        relay.port2.onmessage = event => {
75            const received = event.data;
76            received.onmessage = message => {
77                if (message.data !== 'queued before transfer') throw Error('Queued endpoint message');
78                console.log('queued transfer');
79            };
80        };
81        relay.port1.postMessage(queued.port2, [queued.port2]);
82        await new Promise(resolve => setTimeout(resolve, 20));
83        "#,
84        &[],
85    );
86    assert_eq!(output.stdout, b"message transfer\nqueued transfer\n");
87}
88
89#[test]
90fn message_port_clone_failures_leave_transferables_usable_and_close_drops_messages() {
91    let output = run(
92        r#"
93        const channel = new MessageChannel(), buffer = new ArrayBuffer(2), movable = new MessageChannel();
94        let stillAttached = false;
95        try { channel.port1.postMessage({bad: () => {}}, [buffer, movable.port2]); throw Error('Clone failure'); }
96        catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; }
97        if (buffer.detached) throw Error('Clone failure detached buffer');
98        movable.port2.onmessage = event => {
99            if (event.data !== 'still attached') throw Error('Clone failure moved port');
100            stillAttached = true;
101        };
102        movable.port1.postMessage('still attached');
103        try { channel.port1.postMessage(channel.port1, [channel.port1]); throw Error('Self transfer'); }
104        catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; }
105        const other = new MessageChannel();
106        for (const options of [undefined, [other.port2, other.port2]]) {
107            try { channel.port1.postMessage(other.port2, options); throw Error('Port transfer validation'); }
108            catch (error) { if (!(error instanceof DOMException) || error.name !== 'DataCloneError') throw error; }
109        }
110        other.port2.onmessage = event => {
111            if (event.data !== 'still entangled') throw Error('Failed transfer changed port');
112        };
113        other.port1.postMessage('still entangled');
114        channel.port2.onmessage = () => { throw Error('Closed message dispatched'); };
115        channel.port1.postMessage('discarded');
116        channel.port2.close();
117        await new Promise(resolve => setTimeout(resolve, 10));
118        if (buffer.byteLength !== 2 || !stillAttached) throw Error('Transfer changed');
119        console.log('message failure and close');
120        "#,
121        &[],
122    );
123    assert_eq!(output.stdout, b"message failure and close\n");
124}
125
126#[test]
127fn transferred_ports_restart_queues_and_closed_ports_still_serialize() {
128    let out = run(
129        r#"
130        const channel=new MessageChannel();
131        let called=0;
132        channel.port2.onmessage=()=>called++;
133        const moved=structuredClone(channel.port2,{transfer:[channel.port2]});
134        moved.addEventListener('message',event=>{
135            if(event.data!==undefined)throw Error('Undefined message changed');
136            called++;
137        });
138        channel.port1.postMessage(undefined);
139        await new Promise(r=>setTimeout(r,5));
140        if(called!==0)throw Error('Transferred queue already enabled');
141        moved.start();
142        await new Promise(r=>setTimeout(r,5));
143        if(called!==1)throw Error('Restarted queue not delivered');
144        moved.close();
145        const buffer=new ArrayBuffer(1);
146        channel.port1.postMessage(null,[buffer]);
147        if(!buffer.detached)throw Error('Closed port did not serialize');
148        try{channel.port1.postMessage(()=>{});throw Error('Closed port skipped clone');}
149        catch(e){if(e.name!=='DataCloneError')throw e;}
150        const order=[],attributes=new MessageChannel();
151        attributes.port2.onmessage=()=>order.push('old');
152        attributes.port2.addEventListener('message',()=>order.push('listener'));
153        attributes.port2.onmessage=()=>order.push('replacement');
154        attributes.port1.postMessage(1);
155        await new Promise(r=>setTimeout(r,5));
156        if(order.join()!=='replacement,listener')throw Error('Handler replacement changed position');
157        const event=new MessageEvent('message',{get data(){called++;return called;},origin:'\ud800'});
158        if(called!==2||event.data!==2||event.origin!=='\ufffd')throw Error('Dictionary conversion');
159        console.log('port lifecycle');
160    "#,
161        &[],
162    );
163    assert_eq!(out.stdout, b"port lifecycle\n");
164}
165
166#[test]
167fn host_message_delivery_does_not_consult_guest_dictionary_or_iterator_prototypes() {
168    let out = run(
169        r#"
170        const channel=new MessageChannel();
171        channel.port2.onmessage=event=>{
172            if(event.data!=='safe'||event.bubbles||event.cancelable||event.composed||event.ports.length!==0)throw Error('Guest conversion of host event');
173            console.log('private delivery');
174        };
175        channel.port1.postMessage('safe');
176        for(const key of ['bubbles','cancelable','composed','ports'])Object.defineProperty(Object.prototype,key,{get(){throw Error('Guest dictionary');},configurable:true});
177        Array.prototype[Symbol.iterator]=()=>{throw Error('Guest iterator');};
178        await new Promise(r=>setTimeout(r,5));
179    "#,
180        &[],
181    );
182    assert_eq!(out.stdout, b"private delivery\n");
183}