char/avril

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

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

Charlotte Somgenerate std typedefs from typescript impl38d7f6c

main
18.3 KiB347 linesraw
1mod common;
2
3use std::io::{BufRead, BufReader};
4
5#[test]
6fn tcp_streams_support_large_transfers_byob_and_half_close() {
7    let output = common::run(
8        r#"
9        import {listen,connect,Conn,Listener} from 'avril:std';
10        const listener=listen({port:0});
11        if(!(listener instanceof Listener)||!Object.isFrozen(listener.addr))throw Error('listener');
12        const accepting=listener.accept();
13        const client=await connect({port:listener.addr.port});
14        const server=await accepting;
15        if(!(client instanceof Conn)||client.remoteAddr.port!==listener.addr.port||server.remoteAddr.port!==client.localAddr.port)throw Error('addresses');
16        const payload=new Uint8Array(1024*1024+7);payload.fill(42);
17        const consume=(async()=>{
18            let count=0;const reader=server.readable.getReader({mode:'byob'});
19            while(true){const {value,done}=await reader.read(new Uint8Array(8192));if(done)break;for(const byte of value)if(byte!==42)throw Error('corrupt');count+=value.length;}
20            reader.releaseLock();if(count!==payload.length)throw Error('truncated');
21            const writer=server.writable.getWriter();await writer.write(new Uint8Array([1,2,3]));await writer.close();writer.releaseLock();
22        })();
23        const writer=client.writable.getWriter();await writer.write(payload);await writer.close();writer.releaseLock();
24        const reader=client.readable.getReader();let total=0;
25        while(true){const {value,done}=await reader.read();if(done)break;for(const byte of value){if(byte!==++total)throw Error('reply');}}
26        reader.releaseLock();if(total!==3)throw Error('reply length');
27        await consume;client.close();server.close();listener.close();console.log('streams');
28    "#,
29        &[],
30    );
31    assert_eq!(output.stdout, b"streams\n");
32}
33
34#[test]
35fn eof_does_not_close_the_write_half_and_writes_snapshot_buffers() {
36    let output = common::run(
37        r#"
38        import {listen,connect,errors} from 'avril:std';
39        const listener=listen({port:0}),accept=listener.accept();
40        const client=await connect({port:listener.addr.port}),server=await accept;
41        await server.closeWrite();await server.closeWrite();
42        if(await client.read(new Uint8Array(4))!==null)throw Error('EOF');
43        if(await client.read(new Uint8Array())!==0)throw Error('empty read');
44        const bytes=new Uint8Array([7,8,9]);const written=client.write(bytes);bytes.fill(0);
45        if(await written!==3)throw Error('write');await client.closeWrite();
46        const buffer=new Uint8Array(10);let offset=0,count;
47        while((count=await server.read(buffer.subarray(offset)))!==null)offset+=count;
48        if(buffer.subarray(0,offset).join()!=='7,8,9')throw Error('snapshot');
49        try{await client.write(new Uint8Array([1]));throw Error('write after FIN');}catch(e){if(!(e instanceof errors.BadResource))throw e;}
50        client.close();server.close();listener.close();console.log('half');
51    "#,
52        &[],
53    );
54    assert_eq!(output.stdout, b"half\n");
55}
56
57#[test]
58fn closing_cancels_accept_read_and_connect_and_disposal_is_idempotent() {
59    let output = common::run(
60        r#"
61        import {listen,connect,errors} from 'avril:std';
62        const listener=listen({port:0});const pending=listener.accept().catch(e=>e);listener.close();
63        if(!((await pending) instanceof errors.BadResource))throw Error('accept cancellation');
64        listener[Symbol.dispose]();
65        const next=listen({port:0}),accept=next.accept(),client=await connect({port:next.addr.port}),server=await accept;
66        const reading=client.read(new Uint8Array(1)).catch(e=>e);client.close();
67        if(!((await reading) instanceof errors.BadResource))throw Error('read cancellation');
68        client[Symbol.dispose]();server.close();next.close();
69        const controller=new AbortController();controller.abort('stopped');
70        try{await connect({port:1,signal:controller.signal});throw Error('aborted connect');}catch(e){if(e!=='stopped')throw e;}
71        const active=new AbortController();const connecting=connect({port:1,signal:active.signal}).catch(e=>e);active.abort('cancel');
72        if(await connecting!=='cancel')throw Error('pending connect');
73        console.log('canceled');
74    "#,
75        &[],
76    );
77    assert_eq!(output.stdout, b"canceled\n");
78}
79
80#[test]
81fn listener_iteration_closes_on_break_without_closing_accepted_connections() {
82    let output = common::run(
83        r#"
84        import {listen,connect} from 'avril:std';
85        const listener=listen({port:0});let server;
86        const consuming=(async()=>{for await(const value of listener){server=value;break;}})();
87        const client=await connect({port:listener.addr.port});await consuming;
88        const receive=server.read(new Uint8Array(1));await client.write(new Uint8Array([8]));if(await receive!==1)throw Error('accepted connection closed');
89        client.close();server.close();
90        const iterator=listener[Symbol.asyncIterator]();if(!(await iterator.next()).done)throw Error('closed iteration');
91        console.log('iterator');
92    "#,
93        &[],
94    );
95    assert_eq!(output.stdout, b"iterator\n");
96}
97
98#[test]
99fn revocation_blocks_existing_connections_queued_writes_and_new_operations() {
100    let output = common::run(
101        r#"
102        import {listen,connect,pledge,errors} from 'avril:std';
103        const listener=listen({port:0}),accept=listener.accept(),client=await connect({port:listener.addr.port}),server=await accept;
104        const written=client.write(new Uint8Array([1]));pledge({net:false});
105        for(const operation of [()=>written,()=>client.read(new Uint8Array(1)),()=>client.closeWrite(),()=>listener.accept(),()=>connect({port:listener.addr.port}),()=>listen({port:0})]){
106            try{await operation();throw Error('authority escaped');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;}
107        }
108        client.close();server.close();listener.close();console.log('denied');
109    "#,
110        &[],
111    );
112    assert_eq!(output.stdout, b"denied\n");
113}
114
115#[test]
116fn raw_sockets_use_captured_intrinsics_and_are_not_cloneable() {
117    let output = common::run(
118        r#"
119        import {listen,connect} from 'avril:std';
120        const then=Promise.prototype.then,PromiseCtor=Promise;
121        function observe(p){Object.defineProperty(p,'constructor',{value:undefined});const result=Reflect.apply(then,p,[v=>v,e=>{throw e;}]);Object.defineProperty(result,'constructor',{value:PromiseCtor});return result;}
122        const chunk=new Uint8Array([9]);
123        const poison=()=>{throw Error('guest hook');};
124        Object.defineProperty(Promise.prototype,'constructor',{get:poison,configurable:true});Object.defineProperty(Promise,Symbol.species,{get:poison});Promise.prototype.then=poison;Array.prototype[Symbol.iterator]=poison;
125        const listener=listen({port:0}),accept=listener.accept(),client=await observe(connect({port:listener.addr.port})),server=await observe(accept);
126        for(let i=0;i<2;i++){try{structuredClone(i?client:listener);throw Error('cloned');}catch(e){if(e.name!=='DataCloneError')throw e;}}
127        const read=server.read(new Uint8Array(1));await observe(client.write(chunk));if(await observe(read)!==1)throw Error('read');
128        if(await observe(client.closeWrite())!==undefined)throw Error('closeWrite result');client.close();server.close();listener.close();console.log('safe');
129    "#,
130        &[],
131    );
132    assert_eq!(output.stdout, b"safe\n");
133}
134
135#[test]
136fn validation_and_illegal_receivers_fail_without_opening_resources() {
137    let output = common::run(
138        r#"
139        import {listen,connect,Conn,Listener} from 'avril:std';
140        for(const port of [-1,65536,NaN,1.5]){try{listen({port});throw Error('invalid port');}catch(e){if(!(e instanceof RangeError))throw e;}}
141        for(const ctor of [Conn,Listener]){try{new ctor();throw Error('constructed');}catch(e){if(!(e instanceof TypeError))throw e;}}
142        try{listen({transport:'udp',port:0});throw Error('transport');}catch(e){if(!(e instanceof TypeError))throw e;}
143        try{await connect({port:0});throw Error('connect port');}catch(e){if(!(e instanceof RangeError))throw e;}
144        console.log('validated');
145    "#,
146        &[],
147    );
148    assert_eq!(output.stdout, b"validated\n");
149}
150
151#[test]
152fn worker_termination_releases_a_pending_accept() {
153    let directory = tempfile::tempdir().unwrap();
154    let path = directory.path().join("worker.js");
155    std::fs::write(&path,"import {listen} from 'avril:std';const listener=listen({port:0});postMessage(listener.addr.port);await listener.accept();").unwrap();
156    let url = url::Url::from_file_path(&path).unwrap();
157    let output = common::run(
158        &format!(
159            r#"
160        import {{listen}} from 'avril:std';
161        const worker=new Worker('{url}',{{type:'module'}});const port=await new Promise(resolve=>worker.onmessage=e=>resolve(e.data));worker.terminate();
162        let listener;for(let i=0;i<100&&!listener;i++){{try{{listener=listen({{port}});}}catch{{await new Promise(resolve=>setTimeout(resolve,5));}}}}
163        if(!listener)throw Error('leaked listener');listener.close();console.log('terminated');
164    "#
165        ),
166        &[],
167    );
168    assert_eq!(output.stdout, b"terminated\n");
169}
170
171#[test]
172fn incoming_data_is_not_delivered_after_authority_is_revoked() {
173    let (_directory, mut child) = common::spawn(
174        r#"
175        import {listen,pledge,errors} from 'avril:std';
176        const listener=listen({port:0});console.log(listener.addr.port);const connection=await listener.accept();
177        const reading=connection.read(new Uint8Array(1));await new Promise(resolve=>setTimeout(resolve,10));pledge({net:false});console.log('narrowed');
178        try{await reading;throw Error('received without authority');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;}
179        connection.close();listener.close();console.log('revoked');
180    "#,
181    );
182    let mut stdout = BufReader::new(child.stdout.take().unwrap());
183    let mut port = String::new();
184    stdout.read_line(&mut port).unwrap();
185    let mut socket =
186        std::net::TcpStream::connect(("127.0.0.1", port.trim().parse::<u16>().unwrap())).unwrap();
187    let mut marker = String::new();
188    stdout.read_line(&mut marker).unwrap();
189    assert_eq!(marker, "narrowed\n");
190    std::io::Write::write_all(&mut socket, b"x").unwrap();
191    marker.clear();
192    stdout.read_line(&mut marker).unwrap();
193    assert_eq!(marker, "revoked\n");
194    drop(stdout);
195    let output = common::wait(child);
196    assert!(
197        output.status.success(),
198        "{}",
199        String::from_utf8_lossy(&output.stderr)
200    );
201    assert!(output.stdout.is_empty());
202}
203
204#[test]
205fn socket_iterators_return_promises_and_finish_pending_reads() {
206    let output = common::run(
207        r#"
208        import {listen,listenDatagram} from 'avril:std';
209        for(const open of [listen,listenDatagram]) {
210            const socket=open({port:0}),iterator=socket[Symbol.asyncIterator]();
211            const pending=iterator.next();
212            const result=await iterator.return().then(value=>value);
213            if(!result.done||result.value!==undefined)throw Error('return result');
214            if(!(await pending).done)throw Error('pending iteration');
215            if(!(await iterator.return().then(value=>value)).done)throw Error('repeated return');
216            if(!(await iterator.next()).done)throw Error('closed iteration');
217        }
218        console.log('iterator promises');
219    "#,
220        &[],
221    );
222    assert_eq!(output.stdout, b"iterator promises\n");
223}
224
225#[test]
226fn tcp_writev_completes_vectors_snapshots_and_orders_shutdown() {
227    let output = common::run(
228        r#"
229        import {listen,connect,errors} from 'avril:std';
230        const listener=listen({port:0});
231        async function pair(){const accept=listener.accept(),client=await connect({port:listener.addr.port}),server=await accept;return [client,server];}
232        async function readAll(server,length){
233            const buf=new Uint8Array(length);let offset=0,count;
234            while(offset<length&&(count=await server.read(buf.subarray(offset)))!==null)offset+=count;
235            if(offset!==length)throw Error('Truncated '+offset+'/'+length);
236            return buf;
237        }
238        let [client,server]=await pair();
239        if(await client.writev([new Uint8Array([10,20]),new Uint8Array(0),new Uint8Array([30])])!==3)throw Error('Count');
240        if(await client.writev([])!==0)throw Error('Empty');
241        await client.write(new Uint8Array([40]));
242        await client.closeWrite();
243        if((await readAll(server,4)).join()!=='10,20,30,40')throw Error('Order');
244        try{await client.writev([new Uint8Array([1])]);throw Error('FIN');}catch(e){if(!(e instanceof errors.BadResource))throw e;}
245        try{await client.writev([]);throw Error('Empty FIN');}catch(e){if(!(e instanceof errors.BadResource))throw e;}
246        client.close();server.close();
247        [client,server]=await pair();
248        const big=[];for(let i=0;i<40;i++){const view=new Uint8Array(5000);view.fill(i);big.push(view);}
249        const reading=readAll(server,200000);
250        const sending=client.writev(big);
251        for(const view of big)view.fill(255);
252        big[big.length-1]=new Uint8Array([0]);
253        const shutdown=client.closeWrite();
254        if(await sending!==200000)throw Error('Total');
255        await shutdown;
256        const got=await reading;
257        for(let i=0;i<40;i++)for(let j=0;j<5000;j++)if(got[i*5000+j]!==i)throw Error('Bytes '+i);
258        client.close();server.close();
259        [client,server]=await pair();
260        const small=[];for(let i=0;i<3000;i++)small.push(new Uint8Array([i&255]));
261        const gathering=readAll(server,3000);
262        if(await client.writev(small)!==3000)throw Error('Small total');
263        await client.closeWrite();
264        const back=await gathering;
265        for(let i=0;i<3000;i++)if(back[i]!==(i&255))throw Error('Small order '+i);
266        client.close();server.close();
267        [client,server]=await pair();
268        const wide=[];for(let i=0;i<20;i++){const view=new Uint8Array(5000);view.fill(200+i);wide.push(view);}
269        const following=readAll(server,100003);
270        const vector=client.writev(wide);
271        if(await client.write(new Uint8Array([7,7,7]))!==3)throw Error('Later write');
272        await client.closeWrite();
273        if(await vector!==100000)throw Error('Wide');
274        const ordered=await following;
275        for(let i=0;i<20;i++)for(let j=0;j<5000;j++)if(ordered[i*5000+j]!==200+i)throw Error('Wide order '+i);
276        if(ordered[100000]!==7||ordered[100001]!==7||ordered[100002]!==7)throw Error('Later order');
277        client.close();server.close();
278        [client,server]=await pair();
279        const first=new Uint8Array(100000);first.fill(7);
280        const one=client.write(first),parts=[new Uint8Array([1,2]),new Uint8Array([3])];
281        const two=client.writev(parts);
282        parts[0].fill(9);parts[1]=new Uint8Array([4]);parts.push(new Uint8Array([0]));
283        const n1=await one;
284        if(!(n1>0&&n1<=65536))throw Error('Queued count');
285        if(await two!==3)throw Error('Vector total');
286        await client.closeWrite();
287        const tail=await readAll(server,n1+3);
288        if(tail[n1]!==1||tail[n1+1]!==2||tail[n1+2]!==3)throw Error('Snapshot');
289        client.close();server.close();listener.close();console.log('writev');
290    "#,
291        &[],
292    );
293    assert_eq!(output.stdout, b"writev\n");
294}
295
296#[test]
297fn tcp_writev_validates_vectors_and_honors_close_and_revocation() {
298    let output = common::run(
299        r#"
300        import {listen,connect,pledge,errors} from 'avril:std';
301        const listener=listen({port:0}),accept=listener.accept();
302        const client=await connect({port:listener.addr.port}),server=await accept;
303        async function rejects(vector){
304            try{await client.writev(vector);}catch(e){if(e instanceof TypeError)return;throw e;}
305            throw Error('Accepted vector');
306        }
307        for(const bad of ['bytes',42,null,undefined,{},new Uint8Array([1]),new Set([new Uint8Array([1])]),(function*(){yield new Uint8Array([1]);})()])await rejects(bad);
308        for(const bad of [[new Uint8Array([1]),'x'],[[1,2]],[new DataView(new ArrayBuffer(1))],[new Uint16Array([1])],[new Uint8Array([1]),undefined],[null]])await rejects(bad);
309        const detached=new Uint8Array([1]);
310        structuredClone(detached.buffer,{transfer:[detached.buffer]});
311        await rejects([detached]);
312        const resizable=new ArrayBuffer(4,{maxByteLength:8}),outOfBounds=new Uint8Array(resizable,1,2);
313        resizable.resize(0);
314        await rejects([outOfBounds]);
315        // An invalid later element rejects before anything is sent.
316        await rejects([new Uint8Array([5,5]),'x']);
317        const late=[];for(let i=0;i<20;i++)late.push(new Uint8Array(5000));late.push('x');
318        await rejects(late);
319        const marker=client.writev([new Uint8Array([9])]);
320        await client.closeWrite();
321        const first=new Uint8Array(2);let offset=0,count;
322        while(offset<1&&(count=await server.read(first.subarray(offset)))!==null)offset+=count;
323        if(await marker!==1||offset!==1||first[0]!==9)throw Error('Prefix sent');
324        if(await server.read(new Uint8Array(1))!==null)throw Error('EOF');
325        try{await client.writev([new Uint8Array([1])]);throw Error('FIN');}catch(e){if(!(e instanceof errors.BadResource))throw e;}
326        client.close();server.close();
327        const accept3=listener.accept(),client3=await connect({port:listener.addr.port}),server3=await accept3;
328        const ok=new Uint8Array([1,2]),into=new Uint8Array(2);
329        const RealArray=Uint8Array,realPush=Array.prototype.push;
330        globalThis.Uint8Array=()=>{throw Error('Mutable intrinsic');};
331        Array.prototype.push=()=>{throw Error('Mutable intrinsic');};
332        if(await client3.writev([ok])!==2)throw Error('Captured');
333        let done=0,part;
334        while(done<2&&(part=await server3.read(into.subarray(done)))!==null)done+=part;
335        if(done!==2||into.join()!=='1,2')throw Error('Poisoned bytes');
336        client3.close();server3.close();
337        globalThis.Uint8Array=RealArray;Array.prototype.push=realPush;
338        const accept2=listener.accept(),client2=await connect({port:listener.addr.port}),server2=await accept2;
339        const queued=client2.writev([new Uint8Array([1,2,3])]);
340        pledge({net:false});
341        try{await queued;throw Error('authority escaped');}catch(e){if(!(e instanceof errors.PermissionDenied))throw e;}
342        client2.close();server2.close();listener.close();console.log('writev-guards');
343    "#,
344        &[],
345    );
346    assert_eq!(output.stdout, b"writev-guards\n");
347}