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