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 3use common:: run; 4 5# [ test ] 6fn timers_order_tasks_microtasks_arguments_and_receivers () { 7let output =run ( 8r#" 9const order = []; 10queueMicrotask(() => order.push('microtask')); 11Promise.resolve().then(() => order.push('promise')); 12const first = setTimeout(function(a, b) { 13'use strict'; 14if (this !== globalThis || a !== 7 || b.value !== 8) throw Error('Timer arguments or receiver'); 15order.push('first'); 16queueMicrotask(() => order.push('first microtask')); 17Promise.resolve().then(() => order.push('first promise')); 18setTimeout(() => order.push('nested')); 19return {get then() {throw Error('Adopted timer return value');}}; 20}, 0, 7, {value:8}); 21const second = setTimeout(() => order.push('second')); 22if (!Number.isInteger(first) || first <= 0 || second === first) throw Error('Timer IDs'); 23await new Promise(resolve => setTimeout(resolve, 30)); 24console.log(order.join('|')); 25let adopted = false; 26queueMicrotask(function() { 27'use strict'; 28if (this !== undefined) throw Error('Microtask receiver'); 29return {then() {adopted = true;}}; 30}); 31await Promise.resolve(); 32if (adopted) throw Error('Adopted microtask return value'); 33"# , 34& [], 35); 36assert_eq! ( 37 output. stdout , 38b"microtask|promise|first|first microtask|first promise|second|nested\n" 39); 40} 41 42# [ test ] 43fn microtasks_share_fifo_order_with_promises_and_ignore_callback_results () { 44let output =run ( 45r#" 46const order = []; 47const result = queueMicrotask(function() { 48'use strict'; 49if (this !== undefined || arguments.length !== 0) throw Error('Microtask invocation'); 50order.push('first'); 51queueMicrotask(() => order.push('nested microtask')); 52Promise.resolve().then(() => order.push('nested promise')); 53return {get then() {throw Error('Inspected microtask result');}}; 54}); 55if (result !== undefined || order.length) throw Error('Synchronous microtask'); 56Promise.resolve().then(() => { 57order.push('promise'); 58queueMicrotask(() => order.push('promise microtask')); 59}); 60queueMicrotask(() => order.push('last')); 61await new Promise(resolve => setTimeout(resolve)); 62console.log(order.join('|')); 63"# , 64& [], 65); 66assert_eq! ( 67 output. stdout , 68b"first|promise|last|nested microtask|nested promise|promise microtask\n" 69); 70} 71 72# [ test ] 73fn microtasks_accept_bound_and_proxy_callbacks_without_consulting_call_properties () { 74let output =run ( 75r#" 76const receiver = {}; 77const callback = function(value) { 78if (this !== receiver || value !== 42) throw Error('Bound callback'); 79console.log('bound'); 80}.bind(receiver, 42); 81callback.call = callback.apply = () => {throw Error('Callback property');}; 82queueMicrotask(callback); 83queueMicrotask(new Proxy(() => {}, { 84apply(target, receiver, args) { 85if (receiver !== undefined || args.length !== 0) throw Error('Proxy callback'); 86console.log('proxy'); 87} 88})); 89"# , 90& [], 91); 92assert_eq! ( output. stdout , b"bound\nproxy\n" ); 93} 94 95# [ test ] 96fn cancellation_cross_clearing_and_intervals_release_host_work () { 97let output =run ( 98r#" 99const fail = () => {throw Error('Canceled timer fired');}; 100const a = setTimeout(fail, 1_000_000); 101clearInterval(a); clearTimeout(a); 102const b = setInterval(fail, 1_000_000); 103clearTimeout(b); clearInterval(b); 104let calls = 0; 105await new Promise(resolve => { 106const id = setInterval(() => { 107if (++calls === 3) {clearTimeout(id);resolve();} 108}, 1); 109}); 110await new Promise(resolve => { 111const id = setInterval(() => { 112queueMicrotask(() => {clearInterval(id);resolve();}); 113}, 0); 114}); 115const long = setTimeout(fail, 1_000_000); 116await new Promise(resolve => setTimeout(() => {clearTimeout(long);resolve();}, 0)); 117console.log(calls); 118"# , 119& [], 120); 121assert_eq! ( output. stdout , b"3\n" ); 122} 123 124# [ test ] 125fn timer_delay_and_handle_conversions_follow_webidl () { 126let output =run ( 127r#" 128const conversions = []; 129const handler = {toString() {conversions.push('handler');return 'code';}}; 130const delay = {valueOf() {conversions.push('delay');return 1;}}; 131try {setTimeout(handler, delay);throw Error('String callback accepted');} 132catch(e) {if (!e.message.includes('not yet implemented')) throw e;} 133if (conversions.join() !== 'handler,delay') throw Error('Conversion order'); 134for (const fn of [() => setTimeout(), () => setInterval(), () => queueMicrotask(), () => queueMicrotask('code'), () => setTimeout(()=>{},1n), () => clearTimeout(1n), () => setTimeout('code',Symbol())]) { 135try {fn();throw Error('Invalid input accepted');} catch(e) {if (!(e instanceof TypeError)) throw e;} 136} 137clearTimeout(); clearInterval(); clearTimeout(-1); clearTimeout(NaN); 138for (const fn of [setTimeout,setInterval,clearTimeout,clearInterval,queueMicrotask]) { 139try {new fn(()=>{});throw Error('Constructible timer API');} catch(e) {if (!(e instanceof TypeError)) throw e;} 140} 141const order = []; 142const late = setTimeout(() => order.push('late'), 20); 143for (const value of [-1,NaN,Infinity,2147483648,4294967296]) setTimeout(() => order.push('zero'), value); 144await new Promise(resolve => setTimeout(resolve,40)); 145if (order.join() !== 'zero,zero,zero,zero,zero,late') throw Error(order.join()); 146const canceled = setTimeout(() => {throw Error('Converted handle');}, 1_000_000); 147clearTimeout({valueOf() {return canceled + 4294967296;}}); 148console.log('converted'); 149"# , 150& [], 151); 152assert_eq! ( output. stdout , b"converted\n" ); 153} 154 155# [ test ] 156fn nested_timers_clamp_but_microtasks_do_not_inherit_nesting () { 157let output =run ( 158r#" 159async function probe(microtask) { 160return await new Promise(resolve => { 161let count = 0; 162function tick() { 163if (++count < 8) {setTimeout(tick);return;} 164const create = () => { 165const order = []; 166const done = label => {order.push(label);if (order.length === 2) resolve(order.join());}; 167setTimeout(() => done('four'),4); 168setTimeout(() => done('zero'),0); 169}; 170if (microtask) queueMicrotask(() => Promise.resolve().then(create)); 171else create(); 172} 173setTimeout(tick); 174}); 175} 176console.log(await probe(false),await probe(true)); 177const order = []; 178setTimeout(() => order.push('four'),4); 179await new Promise(resolve => setTimeout(() => {order.push('zero');resolve();})); 180if (order.join() !== 'zero') throw Error('Nesting escaped its task'); 181"# , 182& [], 183); 184assert_eq! ( output. stdout , b"four,zero zero,four\n" ); 185} 186 187# [ test ] 188fn timer_internals_ignore_poisoned_globals_and_promise_species () { 189let output =run ( 190r#" 191const resolved = Promise.resolve(); 192let species = 0; 193const poison = () => {throw Error('Poisoned intrinsic');}; 194Object.defineProperty(Promise, Symbol.species, {get() {species++;throw Error('Species');}, configurable:true}); 195Promise.prototype.then = poison; 196globalThis.Promise = poison; 197globalThis.Map = poison; 198Array.prototype.splice = poison; 199Array.prototype.sort = poison; 200Function.prototype.apply = poison; 201queueMicrotask(() => console.log('microtask')); 202const id = setTimeout(() => {throw Error('Canceled');}, 1_000_000); 203clearTimeout(id); 204const original = globalThis; 205setTimeout(function() { 206'use strict'; 207if (species || this !== original) throw Error('Poisoned timer'); 208console.log('timer'); 209}); 210globalThis.globalThis = null; 211await resolved; 212"# , 213& [], 214); 215assert_eq! ( output. stdout , b"microtask\ntimer\n" ); 216} 217 218# [ test ] 219fn abort_signal_timeout_uses_native_timer_and_enforce_range_conversion () { 220let output =run ( 221r#" 222const fail = () => {throw Error('Expected TypeError');}; 223for (const call of [ 224() => AbortSignal.timeout(), 225() => AbortSignal.timeout(-1), 226() => AbortSignal.timeout(Infinity), 227() => AbortSignal.timeout(NaN), 228() => AbortSignal.timeout(2 ** 64), 229() => AbortSignal.timeout(1n), 230() => AbortSignal.timeout(Symbol()), 231]) { 232try {call();fail();} catch (error) {if (!(error instanceof TypeError)) throw error;} 233} 234const previous = globalThis.setTimeout; 235globalThis.setTimeout = fail; 236Math.trunc = fail; 237const signal = AbortSignal.timeout({ 238[Symbol.toPrimitive](hint) { 239if (hint !== 'number') throw Error('Wrong conversion hint'); 240return 0.9; 241}, 242}); 243if (signal.aborted) throw Error('Timeout was synchronous'); 244for(let id=1;id<=10;id++)clearTimeout(id); 245await new Promise(resolve => signal.addEventListener('abort', resolve, {once:true})); 246if (!signal.aborted || signal.reason.name !== 'TimeoutError' || 247signal.reason.message !== 'The operation timed out' || signal.reason.code !== 23) { 248throw Error('Invalid timeout reason'); 249} 250try {signal.throwIfAborted();throw Error('Expected timeout reason');} 251catch (error) {if (error !== signal.reason) throw error;} 252globalThis.setTimeout = previous; 253console.log('timeout'); 254"# , 255& [], 256); 257assert_eq! ( output. stdout , b"timeout\n" ); 258} 259 260# [ test ] 261fn long_abort_timeout_does_not_keep_the_cli_alive () { 262let output =run ( "AbortSignal.timeout(2 ** 31); console.log('exit');" , & []); 263assert_eq! ( output. stdout , b"exit\n" ); 264} 265 266# [ test ] 267fn private_timer_liveness_tracks_public_queue_changes () { 268let promoted =run ( 269r#" 270const signal = AbortSignal.timeout(1); 271await new Promise(resolve => setTimeout(resolve, 30)); 272if (!signal.aborted || signal.reason.name !== 'TimeoutError') throw Error('Private timer did not fire while referenced'); 273console.log('promoted'); 274"# , 275& [], 276); 277assert_eq! ( promoted. stdout , b"promoted\n" ); 278 279let demoted =run ( 280r#" 281AbortSignal.timeout(2 ** 31); 282const id = setTimeout(() => {throw Error('Cleared public timer fired');}, 2 ** 31); 283clearTimeout(id); 284console.log('demoted'); 285"# , 286& [], 287); 288assert_eq! ( demoted. stdout , b"demoted\n" ); 289 290let removed =run ( 291r#" 292AbortSignal.timeout(2 ** 31); 293await new Promise(resolve => setTimeout(resolve, 0)); 294console.log('removed'); 295"# , 296& [], 297); 298assert_eq! ( removed. stdout , b"removed\n" ); 299} 300 301# [ test ] 302fn canceled_private_waits_do_not_disarm_rescheduled_public_timers () { 303let output =run ( 304r#" 305const order = []; 306AbortSignal.timeout(500); 307await new Promise(resolve => setTimeout(() => { 308order.push('first'); 309setTimeout(() => {order.push('second');resolve();}, 0); 310}, 0)); 311if (order.join() !== 'first,second') throw Error(order.join()); 312console.log(order.join()); 313"# , 314& [], 315); 316assert_eq! ( output. stdout , b"first,second\n" ); 317} 318 319# [ test ] 320fn private_timer_tasks_do_not_increase_public_timer_nesting () { 321let output =run ( 322r#" 323const order = await new Promise(resolve => { 324const result = []; 325function finish(label) { 326result.push(label); 327if (result.length === 2) resolve(result.join(',')); 328} 329function deepen(level) { 330if (level < 7) {setTimeout(() => deepen(level + 1), 0);return;} 331setTimeout(() => finish('earlier'), 0); 332const signal = AbortSignal.timeout(0); 333signal.addEventListener('abort', () => setTimeout(() => finish('timeout'), 0), {once:true}); 334} 335setTimeout(() => deepen(1), 0); 336}); 337if (order !== 'timeout,earlier') throw Error(order); 338console.log(order); 339"# , 340& [], 341); 342assert_eq! ( output. stdout , b"timeout,earlier\n" ); 343} 344 345# [ test ] 346fn callbacks_report_the_original_exception () { 347for sourcein [ 348"setTimeout(() => {throw Error('timer failure');});" , 349"queueMicrotask(() => {throw Error('microtask failure');});" , 350] { 351let ( _dir, child) = common:: spawn ( source); 352let output = common:: wait ( child); 353assert! ( !output. status . success ()); 354assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( 355if source. starts_with ( "setTimeout" ) { 356"Error: timer failure\n" 357} else { 358"Error: microtask failure\n" 359} 360)); 361} 362} 363 364# [ test ] 365fn clearing_one_timeout_does_not_disarm_other_timers () { 366let output =run ( 367r#" 368let canceled = false; 369const canceledTimer = setTimeout(() => canceled = true, 5); 370clearTimeout(canceledTimer); 371await new Promise(resolve => setTimeout(() => {console.log('awake');resolve();},20)); 372if (canceled) throw Error('Cleared timeout fired'); 373"# , 374& [], 375); 376assert_eq! ( output. stdout , b"awake\n" ); 377} 378 379# [ test ] 380fn timers_are_permissionless_and_private_modules_remain_inaccessible () { 381let dir = tempfile:: tempdir (). unwrap (); 382let path = dir. path (). join ( "main.js" ); 383 std:: fs:: write ( 384& path, 385r#" 386import {pledge} from 'avril:std'; 387pledge({net:false,read:false,jsExec:false}); 388await new Promise(resolve => setTimeout(resolve,0)); 389for (const name of ['ext:timers/main.js','ext:timers/timers.js','ext:core/tasks','ext:core/microtasks']) { 390try {await import(name);throw Error('Private module escaped');} 391catch(e) {if (!e.message.includes('private')) throw e;} 392} 393console.log('permissionless'); 394"# , 395) 396. unwrap (); 397let output = std:: process:: Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 398. arg ( "run" ) 399. args ([ "-0" , path. to_str (). unwrap ()]) 400. output () 401. unwrap (); 402assert! ( 403 output. status . success (), 404"{}" , 405String :: from_utf8_lossy ( & output. stderr ) 406); 407assert_eq! ( output. stdout , b"permissionless\n" ); 408} 409 410# [ test ] 411fn intervals_restart_after_callback_microtasks () { 412let output =run ( 413r#" 414const order = []; 415let count = 0; 416await new Promise(resolve => { 417const id = setInterval(() => { 418order.push(`I${count}`); 419if (count++ === 0) queueMicrotask(() => setTimeout(() => order.push('T'))); 420else {clearInterval(id);resolve();} 421}); 422}); 423console.log(order.join()); 424"# , 425& [], 426); 427assert_eq! ( output. stdout , b"I0,T,I1\n" ); 428}