char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
61e3521
main
1mod common; 2# [ path = "common/registry.rs" ] 3mod fixture; 4 5use std:: io:: Write ; 6use std:: process::{ Command , Output , Stdio }; 7use std:: thread; 8use std:: time:: Duration ; 9 10/// Resets SIGINT/SIGTERM to their defaults in the child, so results never 11/// depend on dispositions the test runner itself ignores. 12# [ cfg ( unix )] 13fn inherit_defaults ( mut command : Command ) ->Command { 14use std:: os:: unix:: process:: CommandExt ; 15unsafe { 16 command. pre_exec ( ||{ 17 libc:: signal ( libc:: SIGINT , libc:: SIG_DFL ); 18 libc:: signal ( libc:: SIGTERM , libc:: SIG_DFL ); 19Ok (()) 20}); 21} 22 command 23} 24 25fn start_repl ( directory : & std:: path:: Path , arguments : & [ & str ]) -> std:: process:: Child { 26# [ cfg ( unix )] 27let mut command =inherit_defaults ( Command :: new ( env! ( "CARGO_BIN_EXE_avril" ))); 28# [ cfg ( not ( unix ))] 29let mut command =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )); 30 command 31. args ( arguments) 32. current_dir ( directory) 33. stdin ( Stdio :: piped ()) 34. stdout ( Stdio :: piped ()) 35. stderr ( Stdio :: piped ()) 36. spawn () 37. unwrap () 38} 39 40fn finish_repl ( mut child : std:: process:: Child , input : & str ) ->Output { 41 child 42. stdin 43. take () 44. unwrap () 45. write_all ( input. as_bytes ()) 46. unwrap (); 47 common:: wait ( child) 48} 49 50# [ cfg ( unix )] 51# [ test ] 52fn repl_can_run_synchronous_processes_after_await_without_pumping_js () { 53let directory = tempfile:: tempdir (). unwrap (); 54let output =finish_repl ( 55start_repl ( directory. path (), & [ "repl" ]), 56concat! ( 57"const {runSync} = await import('avril:std');\n" , 58"await new Promise(resolve => setTimeout(resolve, 1)); " , 59"let pumped = false; queueMicrotask(() => pumped = true); " , 60"const result = runSync('/bin/sh', ['-c', 'cat; printf stderr >&2'], " , 61"{input: 'repl sync', encoding: 'utf-8'}); " , 62"if (pumped) throw Error('runSync pumped JS'); " , 63"if (result.stderr !== 'stderr') throw Error('Missing stderr'); " , 64"result.stdout\n.exit\n" , 65), 66); 67assert! ( output. status . success (), "{output:?}" ); 68assert_eq! ( output. stdout , b"undefined\n\"repl sync\"\n" ); 69assert! ( output. stderr . is_empty (), "{output:?}" ); 70} 71 72# [ test ] 73fn repl_persists_bindings_multiline_await_and_recovers_from_errors () { 74let directory = tempfile:: tempdir (). unwrap (); 75let output =finish_repl ( 76start_repl ( directory. path (), & [ "repl" , "-0" ]), 77"await Promise.resolve(5)\nconst interval = setInterval(() => {}, 0)\nawait new Promise(resolve => setTimeout(() => { clearInterval(interval); resolve(11) }, 20))\nlet value = 6\nvalue\nfunction add(input) {\n return input + value\n}\nadd(1)\ntry { await import('ext:core/ops') } catch (error) { error.message }\nthrow Error('expected')\nconst = 1\n8\n.exit\n" , 78); 79assert! ( output. status . success ()); 80let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 81assert_eq! ( 82 stdout, 83"5\nundefined\n11\nundefined\n6\nundefined\n7\n\"Extension modules are private\"\n8\n" 84); 85let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 86assert! ( stderr. contains ( "expected" ), "{stderr}" ); 87assert! ( stderr. contains ( "SyntaxError" ), "{stderr}" ); 88} 89 90# [ test ] 91fn repl_underscore_tracks_successful_results_and_preserves_them_after_errors () { 92let directory = tempfile:: tempdir (). unwrap (); 93let output =finish_repl ( 94start_repl ( directory. path (), & [ "repl" , "-0" ]), 95"_\n6 * 7\n_ + 1\nawait Promise.resolve(_ + 1)\nthrow Error('expected')\n_\nconst = 1\n_\nawait Promise.reject(Error('rejected'))\n_\nlet value = 1\n_\n.exit\n" , 96); 97assert! ( output. status . success ()); 98assert_eq! ( 99String :: from_utf8 ( output. stdout ). unwrap (), 100"undefined\n42\n43\n44\n44\n44\n44\nundefined\nundefined\n" 101); 102let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 103for messagein [ "expected" , "SyntaxError" , "rejected" ] { 104assert! ( stderr. contains ( message), "{stderr}" ); 105} 106} 107 108# [ test ] 109fn repl_underscore_retains_identity_and_non_json_values () { 110let directory = tempfile:: tempdir (). unwrap (); 111let output =finish_repl ( 112start_repl ( directory. path (), & [ "repl" , "-0" ]), 113r#"const object = { answer: 42 } 114object 115_ === object 116const fn = () => 7 117fn 118_ === fn && _() === 7 119const symbol = Symbol('result') 120symbol 121_ === symbol 12211n 123_ + 1n 124NaN 125Number.isNaN(_) 126-0 127Object.is(_, -0) 128null 129_ === null 130void 0 131_ === undefined 132.exit 133"# , 134); 135assert! ( output. status . success ()); 136let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 137assert! ( 138 stdout. starts_with ( "undefined\n{ answer: 42 }\ntrue\n" ), 139"{stdout}" 140); 141assert_eq! ( 142 stdout. lines (). filter ( |line|* line =="true" ). count (), 1437 , 144"{stdout}" 145); 146assert! ( 147 stdout. contains ( "11n\n12n\nNaN\ntrue\n0\ntrue\nnull\ntrue\nundefined\ntrue\n" ), 148"{stdout}" 149); 150assert! ( 151 output. stderr . is_empty (), 152"{}" , 153String :: from_utf8_lossy ( & output. stderr ) 154); 155} 156 157# [ test ] 158fn repl_import_declarations_support_all_binding_forms_and_persist () { 159let directory = tempfile:: tempdir (). unwrap (); 160 std:: fs:: write ( 161 directory. path (). join ( "local.js" ), 162"globalThis.loads = (globalThis.loads ?? 0) + 1; export default 42; export let count = 1; export function bump() { count++; } const odd = 7; export { odd as 'odd-name', odd as '__proto__' };" , 163) 164. unwrap (); 165let output =finish_repl ( 166start_repl ( directory. path (), & [ "repl" , "-0" , "--allow-js-exec=." ]), 167r#"import answer from './local.js' 168answer 169import { 170count as snapshot, 171bump, 172'odd-name' as odd, 173'__proto__' as proto 174} from './local.js' 175[snapshot, odd, proto] 176import * as namespace from './local.js' 177import combined, {count as named} from './local.js' 178import other, * as both from './local.js' 179[combined, named, other, both === namespace] 180bump(); [snapshot, namespace.count] 181import './local.js' 182import {} from './local.js' 183loads 184import answer from './local.js' 185answer 186.exit 187"# , 188); 189assert! ( output. status . success ()); 190assert_eq! ( String :: from_utf8_lossy ( & output. stderr ), "" ); 191assert_eq! ( 192String :: from_utf8_lossy ( & output. stdout ), 193"undefined\n42\nundefined\n[ 1, 7, 7 ]\nundefined\nundefined\nundefined\n[ 42, 1, 42, true ]\n[ 1, 2 ]\nundefined\nundefined\n1\nundefined\n42\n" 194); 195} 196 197# [ test ] 198fn repl_imports_preserve_statement_order_completion_values_and_line_numbers () { 199let directory = tempfile:: tempdir (). unwrap (); 200 std:: fs:: write ( 201 directory. path (). join ( "local.js" ), 202"export default 42; export const count = 1;" , 203) 204. unwrap (); 205let output =finish_repl ( 206start_repl ( directory. path (), & []), 207r#"import answer from './local.js'; import {count} from './local.js'; [answer, count] 2081; import './local.js' 2092; import {} from './local.js' 2103; import other from './local.js' 211import aliased from './local.js' /* 212*/ (aliased) 213import { 214count as another 215} from './local.js'; null.x 216'import declarations stay inside strings' 217.exit 218"# , 219); 220assert! ( output. status . success ()); 221assert_eq! ( 222String :: from_utf8_lossy ( & output. stdout ), 223"[ 42, 1 ]\n1\n2\n3\n42\n\"import declarations stay inside strings\"\n" 224); 225let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 226assert! ( stderr. contains ( ".avril-repl.js:3:" ), "{stderr}" ); 227} 228 229# [ test ] 230fn imports_enqueued_after_microtask_only_await_wake_the_repl () { 231let directory = tempfile:: tempdir (). unwrap (); 232let output =finish_repl ( 233start_repl ( directory. path (), & [ "repl" , "-0" ]), 234"await 0; import * as std from 'avril:std'; import {inspect} from 'avril:std'; inspect(42)\nawait 0; (await import('avril:std')).inspect(43)\n.exit\n" , 235); 236assert! ( output. status . success ()); 237assert! ( 238 output. stderr . is_empty (), 239"{}" , 240String :: from_utf8_lossy ( & output. stderr ) 241); 242assert_eq! ( output. stdout , b"\"42\"\n\"43\"\n" ); 243} 244 245# [ test ] 246fn native_ops_enqueued_after_microtask_only_await_wake_the_repl () { 247let directory = tempfile:: tempdir (). unwrap (); 248 std:: fs:: write ( directory. path (). join ( "hello.txt" ), "hello" ). unwrap (); 249let output =finish_repl ( 250start_repl ( directory. path (), & [ "repl" , "-0" , "--allow-read=." ]), 251"import {readTextFile} from 'avril:fs'\nawait 0; await readTextFile('./hello.txt')\n.exit\n" , 252); 253assert! ( output. status . success ()); 254assert! ( 255 output. stderr . is_empty (), 256"{}" , 257String :: from_utf8_lossy ( & output. stderr ) 258); 259assert_eq! ( output. stdout , b"undefined\n\"hello\"\n" ); 260} 261 262# [ test ] 263fn repl_import_declarations_resolve_builtins_and_config_aliases () { 264let directory = tempfile:: tempdir (). unwrap (); 265 std:: fs:: write ( directory. path (). join ( "local.js" ), "export default 42;" ). unwrap (); 266 std:: fs:: write ( 267 directory. path (). join ( "avril.conf" ), 268"imports: { helper: './local.js' }" , 269) 270. unwrap (); 271let output =finish_repl ( 272start_repl ( directory. path (), & []), 273"import { inspect as show } from 'avril:std'\nimport answer from 'helper'\nshow(answer)\n.exit\n" , 274); 275assert! ( output. status . success ()); 276assert! ( 277 output. stderr . is_empty (), 278"{}" , 279String :: from_utf8_lossy ( & output. stderr ) 280); 281assert_eq! ( output. stdout , b"undefined\nundefined\n\"42\"\n" ); 282} 283 284# [ test ] 285fn repl_import_declarations_enforce_permissions_and_recover_from_failures () { 286let directory = tempfile:: tempdir (). unwrap (); 287for filein [ "local.js" , ".avril-repl.js" ] { 288 std:: fs:: write ( directory. path (). join ( file), "export default 42;" ). unwrap (); 289} 290let denied =finish_repl ( 291start_repl ( directory. path (), & [ "repl" , "-0" ]), 292"import answer from './local.js'\nimport './.avril-repl.js'\nimport 'ext:core/ops'\n6 * 7\n.exit\n" , 293); 294assert! ( denied. status . success ()); 295assert_eq! ( denied. stdout , b"42\n" ); 296let stderr =String :: from_utf8 ( denied. stderr ). unwrap (); 297assert_eq! ( 298 stderr. matches ( "PermissionDenied: js-exec" ). count (), 2992 , 300"{stderr}" 301); 302assert! ( stderr. contains ( "Extension modules are private" ), "{stderr}" ); 303 304let revoked =finish_repl ( 305start_repl ( directory. path (), & [ "repl" , "-0" , "--allow-js-exec=." ]), 306"import answer from './local.js'\nimport { pledge } from 'avril:std'\npledge({ jsExec: false })\nimport * as cached from './local.js'\nanswer\n.exit\n" , 307); 308assert! ( revoked. status . success ()); 309assert! ( String :: from_utf8_lossy ( & revoked. stderr ). contains ( "PermissionDenied: js-exec" )); 310assert! ( revoked. stdout . ends_with ( b"42\n" )); 311} 312 313# [ test ] 314fn repl_does_not_rewrite_invalid_or_nested_import_declarations () { 315let directory = tempfile:: tempdir (). unwrap (); 316 std:: fs:: write ( 317 directory. path (). join ( "side.js" ), 318"globalThis.loaded = true;" , 319) 320. unwrap (); 321let output =finish_repl ( 322start_repl ( directory. path (), & []), 323r#"import './side.js'; const = 1 324function invalid() { import './side.js'; } 325'import fake from "./side.js"' 326typeof loaded 327.exit 328"# , 329); 330assert! ( output. status . success ()); 331assert! ( output. stdout . ends_with ( b"\"undefined\"\n" )); 332let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 333assert_eq! ( stderr. matches ( "SyntaxError" ). count (), 2 , "{stderr}" ); 334} 335 336# [ test ] 337fn repl_import_attributes_are_not_silently_discarded () { 338let directory = tempfile:: tempdir (). unwrap (); 339 std:: fs:: write ( 340 directory. path (). join ( "side.js" ), 341"globalThis.loaded = true;" , 342) 343. unwrap (); 344let output =finish_repl ( 345start_repl ( directory. path (), & []), 346"import './side.js' with { type: 'json' }\ntypeof loaded\n.exit\n" , 347); 348assert! ( output. status . success ()); 349assert_eq! ( output. stdout , b"\"undefined\"\n" ); 350let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 351assert! ( 352 stderr. contains ( "Import attributes are not supported yet" ), 353"{stderr}" 354); 355} 356 357# [ test ] 358fn repl_imports_use_live_execution_capabilities_without_an_entry_grant () { 359let directory = tempfile:: tempdir (). unwrap (); 360 std:: fs:: write ( directory. path (). join ( "local.js" ), "export default 42;" ). unwrap (); 361 std:: fs:: write ( 362 directory. path (). join ( ".avril-repl.js" ), 363"export default 24;" , 364) 365. unwrap (); 366 367let denied =finish_repl ( 368start_repl ( directory. path (), & [ "repl" , "-0" ]), 369"try { await import('./local.js') } catch (error) { error.message }\ntry { await import('./.avril-repl.js') } catch (error) { error.message }\n.exit\n" , 370); 371assert! ( denied. status . success ()); 372let stdout =String :: from_utf8 ( denied. stdout ). unwrap (); 373assert_eq! ( 374 stdout. matches ( "PermissionDenied: js-exec" ). count (), 3752 , 376"{stdout}" 377); 378 379let bare =finish_repl ( 380start_repl ( directory. path (), & []), 381"(await import('./local.js')).default\n.exit\n" , 382); 383assert! ( bare. status . success ()); 384assert_eq! ( bare. stdout , b"42\n" ); 385 386let allowed =finish_repl ( 387start_repl ( directory. path (), & [ "repl" , "-0" , "--allow-js-exec=." ]), 388"(await import('./local.js')).default\n.exit\n" , 389); 390assert! ( allowed. status . success ()); 391assert_eq! ( allowed. stdout , b"42\n" ); 392 393let revoked =finish_repl ( 394start_repl ( directory. path (), & [ "repl" , "-0" , "--allow-js-exec=." ]), 395"(await import('avril:std')).pledge({ jsExec: false })\ntry { await import('./local.js') } catch (error) { error.message }\n.exit\n" , 396); 397assert! ( revoked. status . success ()); 398assert! ( 399String :: from_utf8 ( revoked. stdout ) 400. unwrap () 401. contains ( "PermissionDenied: js-exec" ) 402); 403} 404 405# [ test ] 406fn zero_capability_repl_has_no_ambient_read_or_network_access () { 407let directory = tempfile:: tempdir (). unwrap (); 408 std:: fs:: write ( directory. path (). join ( "secret.txt" ), "secret" ). unwrap (); 409let output =finish_repl ( 410start_repl ( directory. path (), & [ "repl" , "-0" ]), 411"try { await (await import('avril:fs')).readTextFile('./secret.txt') } catch (error) { error.message }\ntry { await fetch('https://example.com') } catch (error) { error.message }\n.exit\n" , 412); 413assert! ( output. status . success ()); 414let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 415assert! ( stdout. contains ( "read ./secret.txt" ), "{stdout}" ); 416assert! ( stdout. contains ( "PermissionDenied: net" ), "{stdout}" ); 417} 418 419# [ test ] 420fn repl_disables_guest_stdin_while_it_owns_the_input_stream () { 421let directory = tempfile:: tempdir (). unwrap (); 422let output =finish_repl ( 423start_repl ( directory. path (), & [ "repl" , "-0" ]), 424"try { await (await import('avril:std')).stdin.getReader().read() } catch (error) { error.message }\n.exit\n" , 425); 426assert! ( output. status . success ()); 427assert! ( 428String :: from_utf8 ( output. stdout ) 429. unwrap () 430. contains ( "stdin in the REPL is not yet implemented" ) 431); 432} 433 434# [ cfg ( unix )] 435# [ test ] 436fn ctrl_c_interrupts_running_javascript_without_ending_the_session () { 437let directory = tempfile:: tempdir (). unwrap (); 438let mut child =start_repl ( directory. path (), & [ "repl" , "-0" ]); 439let mut stdin = child. stdin . take (). unwrap (); 440 stdin. write_all ( b"while (true) {}\n" ). unwrap (); 441 thread:: sleep ( Duration :: from_millis ( 500 )); 442assert_eq! ( unsafe { libc:: kill ( child. id () as i32 , libc:: SIGINT ) }, 0 ); 443 thread:: sleep ( Duration :: from_millis ( 50 )); 444 stdin. write_all ( b"2 + 2\n.exit\n" ). unwrap (); 445drop ( stdin); 446let output = common:: wait ( child); 447assert! ( output. status . success ()); 448assert! ( String :: from_utf8 ( output. stdout ). unwrap (). contains ( "4\n" )); 449assert! ( 450String :: from_utf8 ( output. stderr ) 451. unwrap () 452. contains ( "Evaluation interrupted" ) 453); 454} 455 456# [ cfg ( unix )] 457# [ test ] 458fn ctrl_c_interrupts_pending_top_level_await () { 459let directory = tempfile:: tempdir (). unwrap (); 460let mut child =start_repl ( directory. path (), & [ "repl" , "-0" ]); 461let mut stdin = child. stdin . take (). unwrap (); 462 stdin 463. write_all ( b"await new Promise(resolve => setTimeout(resolve, 10_000))\n" ) 464. unwrap (); 465 thread:: sleep ( Duration :: from_millis ( 500 )); 466assert_eq! ( unsafe { libc:: kill ( child. id () as i32 , libc:: SIGINT ) }, 0 ); 467 thread:: sleep ( Duration :: from_millis ( 50 )); 468 stdin. write_all ( b"3 + 3\n.exit\n" ). unwrap (); 469drop ( stdin); 470let output = common:: wait ( child); 471assert! ( output. status . success ()); 472assert! ( String :: from_utf8 ( output. stdout ). unwrap (). contains ( "6\n" )); 473assert! ( 474String :: from_utf8 ( output. stderr ) 475. unwrap () 476. contains ( "Evaluation interrupted" ) 477); 478} 479 480# [ test ] 481fn background_timers_progress_while_waiting_for_input () { 482let directory = tempfile:: tempdir (). unwrap (); 483let mut child =start_repl ( directory. path (), & [ "repl" , "-0" ]); 484let mut stdin = child. stdin . take (). unwrap (); 485 stdin 486. write_all ( b"let ticks = 0; const timer = setInterval(() => { if (++ticks === 1) console.log('background') }, 5)\n" ) 487. unwrap (); 488 thread:: sleep ( Duration :: from_millis ( 100 )); 489 stdin 490. write_all ( b"clearInterval(timer); ticks\n.exit\n" ) 491. unwrap (); 492drop ( stdin); 493let output = common:: wait ( child); 494assert! ( output. status . success ()); 495let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 496assert! ( stdout. contains ( "background\n" ), "{stdout}" ); 497assert! ( 498 stdout. lines (). last (). unwrap (). parse ::< u32 >(). unwrap () >1 , 499"{stdout}" 500); 501} 502 503# [ test ] 504fn repl_completions_multiline_literals_and_previews_preserve_state () { 505let directory = tempfile:: tempdir (). unwrap (); 506let output =finish_repl ( 507start_repl ( directory. path (), & [ "repl" , "-0" ]), 508"let value = 1\nlet value = 2\nvalue\n{\n \"answer\": 42,\n label: 'ok'\n}\n`brace {\ncontinuation }`\nlet getters = 0\n({ get value() { getters++; return 4 } })\ngetters\nawait Promise.resolve(17)\nthrow 123\n11n\nSymbol.for('__avril.ops') in globalThis\n.exit\n" , 509); 510assert! ( 511 output. status . success (), 512"{}" , 513String :: from_utf8_lossy ( & output. stderr ) 514); 515let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 516assert! ( stdout. starts_with ( "undefined\nundefined\n2\n" ), "{stdout}" ); 517assert! ( stdout. contains ( "{ answer: 42, label: \"ok\" }" ), "{stdout}" ); 518assert! ( stdout. contains ( "\"brace {\\ncontinuation }\"" ), "{stdout}" ); 519assert! ( stdout. ends_with ( "0\n17\n11n\nfalse\n" ), "{stdout}" ); 520assert_eq! ( String :: from_utf8 ( output. stderr ). unwrap (), "123\n" ); 521} 522 523# [ test ] 524fn results_and_thrown_values_are_shown_like_console_log () { 525let directory = tempfile:: tempdir (). unwrap (); 526let output =finish_repl ( 527start_repl ( directory. path (), & [ "repl" , "-0" ]), 528"({ a: { b: { c: [1, 'two'] } } })\nconst point = { x: 1, [Symbol.for('runtime.avril.inspect')]: (inspect, options) => `Point(${inspect(point.x, options)})` }\n[point]\nthrow { code: 'E_BAD' }\n'plain'\n.exit\n" , 529); 530assert! ( 531 output. status . success (), 532"{}" , 533String :: from_utf8_lossy ( & output. stderr ) 534); 535assert_eq! ( 536String :: from_utf8 ( output. stdout ). unwrap (), 537"{ a: { b: { c: [ 1, \"two\" ] } } }\nundefined\n[ Point(1) ]\n\"plain\"\n" 538); 539assert_eq! ( 540String :: from_utf8 ( output. stderr ). unwrap (), 541"{ code: \"E_BAD\" }\n" 542); 543} 544 545# [ test ] 546fn repl_package_aliases_and_typescript_dependencies_use_the_existing_graph () { 547let directory = tempfile:: tempdir (). unwrap (); 548 std:: fs:: create_dir ( directory. path (). join ( "math" )). unwrap (); 549 std:: fs:: write ( directory. path (). join ( "avril.conf" ), "deps: ['./math']" ). unwrap (); 550 std:: fs:: write ( 551 directory. path (). join ( "math/avril.conf" ), 552"name: '@acme/math', exports: {'.': './mod.ts'}" , 553) 554. unwrap (); 555 std:: fs:: write ( 556 directory. path (). join ( "math/mod.ts" ), 557"export const answer: number = 42;" , 558) 559. unwrap (); 560let output =finish_repl ( 561start_repl ( directory. path (), & [ "repl" ]), 562"(await import('@acme/math')).answer\n.exit\n" , 563); 564assert! ( 565 output. status . success (), 566"{}" , 567String :: from_utf8_lossy ( & output. stderr ) 568); 569assert_eq! ( output. stdout , b"42\n" ); 570assert! ( 571 output. stderr . is_empty (), 572"{}" , 573String :: from_utf8_lossy ( & output. stderr ) 574); 575} 576 577# [ test ] 578fn repl_imports_an_esm_module_entry_from_a_commonjs_package () { 579let server = fixture:: Registry :: new (); 580 server. publish_npm ( 581"dual-entry" , 582"1.0.0" , 583& [ 584( 585"package.json" , 586r#"{"name":"dual-entry","version":"1.0.0","module":"entry.es6.js","main":"entry.js"}"# , 587), 588( "entry.es6.js" , "export default value => value.toUpperCase();" ), 589( "entry.js" , "module.exports = value => value.toLowerCase();" ), 590], 591); 592 server. publish_npm ( 593"consumer" , 594"1.0.0" , 595& [ 596( 597"package.json" , 598r#"{"name":"consumer","version":"1.0.0","exports":"./index.mjs","dependencies":{"dual-entry":"1.0.0"}}"# , 599), 600( "index.mjs" , "export { default } from 'dual-entry';" ), 601], 602); 603let directory = tempfile:: tempdir (). unwrap (); 604let registry =format! ( "--npm-registry={}" , server. url ); 605let output =finish_repl ( 606start_repl ( directory. path (), & [ "repl" , & registry]), 607"import convert from 'npm:consumer@1'\nconvert('Hello')\n.exit\n" , 608); 609assert! ( output. status . success ()); 610assert! ( 611 output. stderr . is_empty (), 612"{}" , 613String :: from_utf8_lossy ( & output. stderr ) 614); 615assert_eq! ( output. stdout , b"undefined\n\"HELLO\"\n" ); 616} 617 618# [ test ] 619fn repl_imports_packages_without_creating_or_updating_a_lockfile () { 620let server = fixture:: Registry :: new (); 621 server. package ( 622"@test/pkg" , 623"1.0.0" , 624& [( "mod.ts" , "export const value = 'jsr-one';" )], 625& [( "." , "./mod.ts" )], 626); 627 server. publish_npm ( 628"tool" , 629"1.0.0" , 630& [ 631( 632"package.json" , 633"{\"name\": \"tool\", \"version\": \"1.0.0\", \"type\": \"module\", \"exports\": {\".\": \"./index.js\"}}" , 634), 635( "index.js" , "export default 'npm-one';" ), 636], 637); 638let session = |directory : & std:: path:: Path |{ 639let options = server. options ( directory); 640let registries =[ 641format! ( "--jsr-registry={}" , options. jsr_registry ), 642format! ( "--npm-registry={}" , options. npm_registry ), 643format! ( "--cache-dir={}" , options. cache . display ()), 644]; 645let mut arguments =vec! [ "repl" ]; 646 arguments. extend ( registries. iter (). map ( String :: as_str)); 647finish_repl ( 648start_repl ( directory, & arguments), 649"(await import('jsr:@test/pkg@^1')).value\n(await import('npm:tool@^1')).default\n.exit\n" , 650) 651}; 652 653// A session in a directory with no lockfile resolves and runs packages 654// without creating one. 655let directory = tempfile:: tempdir (). unwrap (); 656let output =session ( directory. path ()); 657assert! ( 658 output. status . success (), 659"{}" , 660String :: from_utf8_lossy ( & output. stderr ) 661); 662assert_eq! ( output. stdout , b"\"jsr-one\"\n\"npm-one\"\n" ); 663assert! ( !directory. path (). join ( "avril.lock" ). exists ()); 664 665// An existing lockfile still pins the session's selections, and is left 666// byte-for-byte alone even once newer versions are available. 667let directory = tempfile:: tempdir (). unwrap (); 668 std:: fs:: write ( 669 directory. path (). join ( "main.ts" ), 670"import {value} from 'jsr:@test/pkg@^1'; import tool from 'npm:tool@^1'; console.log(value, tool);" , 671) 672. unwrap (); 673let options = server. options ( directory. path ()); 674let run =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 675. current_dir ( directory. path ()) 676. args ([ "run" ]) 677. arg ( format! ( "--jsr-registry={}" , options. jsr_registry )) 678. arg ( format! ( "--npm-registry={}" , options. npm_registry )) 679. arg ( format! ( "--cache-dir={}" , options. cache . display ())) 680. arg ( "main.ts" ) 681. stdin ( Stdio :: null ()) 682. stdout ( Stdio :: piped ()) 683. stderr ( Stdio :: piped ()) 684. spawn () 685. unwrap (); 686let output = common:: wait ( run); 687assert! ( 688 output. status . success (), 689"{}" , 690String :: from_utf8_lossy ( & output. stderr ) 691); 692let lock = std:: fs:: read ( directory. path (). join ( "avril.lock" )). unwrap (); 693 server. package ( 694"@test/pkg" , 695"1.1.0" , 696& [( "mod.ts" , "export const value = 'jsr-two';" )], 697& [( "." , "./mod.ts" )], 698); 699 server. publish_npm ( 700"tool" , 701"1.1.0" , 702& [ 703( 704"package.json" , 705"{\"name\": \"tool\", \"version\": \"1.1.0\", \"type\": \"module\", \"exports\": {\".\": \"./index.js\"}}" , 706), 707( "index.js" , "export default 'npm-two';" ), 708], 709); 710let output =session ( directory. path ()); 711assert! ( 712 output. status . success (), 713"{}" , 714String :: from_utf8_lossy ( & output. stderr ) 715); 716assert_eq! ( output. stdout , b"\"jsr-one\"\n\"npm-one\"\n" ); 717assert_eq! ( 718 std:: fs:: read ( directory. path (). join ( "avril.lock" )). unwrap (), 719 lock 720); 721} 722 723# [ test ] 724fn tasks_enqueued_during_a_microtask_checkpoint_wake_pending_await () { 725let directory = tempfile:: tempdir (). unwrap (); 726let output =finish_repl ( 727start_repl ( directory. path (), & [ "repl" , "-0" ]), 728"await new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = () => resolve(42); queueMicrotask(() => port1.postMessage(1)); })\n.exit\n" , 729); 730assert! ( 731 output. status . success (), 732"{}" , 733String :: from_utf8_lossy ( & output. stderr ) 734); 735assert_eq! ( output. stdout , b"42\n" ); 736assert! ( output. stderr . is_empty ()); 737} 738 739# [ test ] 740fn console_output_precedes_results_and_background_errors_do_not_end_the_repl () { 741let directory = tempfile:: tempdir (). unwrap (); 742let output =finish_repl ( 743start_repl ( directory. path (), & [ "repl" , "-0" ]), 744"console.log('first'); 7\nsetTimeout(() => { throw Error('background failure') }, 0)\nawait new Promise(resolve => setTimeout(() => resolve(8), 20))\n2 + 2\n.exit\n" , 745); 746assert! ( output. status . success ()); 747let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 748assert! ( stdout. starts_with ( "first\n7\n" ), "{stdout}" ); 749assert! ( stdout. ends_with ( "8\n4\n" ), "{stdout}" ); 750let stderr =String :: from_utf8 ( output. stderr ). unwrap (); 751assert_eq! ( stderr. matches ( "background failure" ). count (), 1 , "{stderr}" ); 752} 753 754# [ test ] 755fn console_logging_from_async_callbacks_does_not_nest_executors () { 756let directory = tempfile:: tempdir (). unwrap (); 757let output =finish_repl ( 758start_repl ( directory. path (), & [ "repl" , "-0" ]), 759"await new Promise(resolve => setTimeout(() => { console.log('from timer'); resolve(1) }, 10))\nawait null; console.log('continued'); 2\nawait new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = () => { console.log('from message'); resolve(3) }; port1.postMessage(1) })\n.exit\n" , 760); 761assert! ( 762 output. status . success (), 763"{}" , 764String :: from_utf8_lossy ( & output. stderr ) 765); 766assert_eq! ( 767 output. stdout , 768b"from timer\n1\ncontinued\n2\nfrom message\n3\n" 769); 770assert! ( output. stderr . is_empty ()); 771} 772 773# [ test ] 774fn piped_input_recovers_when_await_has_no_runnable_host_work () { 775let directory = tempfile:: tempdir (). unwrap (); 776let output =finish_repl ( 777start_repl ( directory. path (), & [ "repl" , "-0" ]), 778"await new Promise(() => {})\n42\n" , 779); 780assert! ( output. status . success ()); 781assert_eq! ( output. stdout , b"42\n" ); 782assert_eq! ( 783String :: from_utf8 ( output. stderr ). unwrap (), 784"REPL evaluation is still pending with no runnable host work\n" 785); 786} 787 788# [ test ] 789fn native_v8_background_work_completes_without_host_ops () { 790let directory = tempfile:: tempdir (). unwrap (); 791let output =finish_repl ( 792start_repl ( directory. path (), & [ "repl" , "-0" ]), 793"for (let i = 0; i < 50; i++) await WebAssembly.compile(new Uint8Array([0,97,115,109,1,0,0,0])); 42\n.exit\n" , 794); 795assert! ( 796 output. status . success (), 797"{}" , 798String :: from_utf8_lossy ( & output. stderr ) 799); 800assert_eq! ( output. stdout , b"42\n" ); 801assert! ( output. stderr . is_empty ()); 802} 803 804# [ cfg ( unix )] 805# [ test ] 806fn repl_signal_listeners_fire_alongside_ctrl_c () { 807let directory = tempfile:: tempdir (). unwrap (); 808let mut child =start_repl ( directory. path (), & [ "repl" , "-0" ]); 809let mut stdin = child. stdin . take (). unwrap (); 810 stdin 811. write_all ( b"import {addSignalListener} from 'avril:std'; addSignalListener('SIGUSR1', () => console.log('repl-got-usr1')); addSignalListener('SIGINT', () => console.log('repl-got-int'));\n" ) 812. unwrap (); 813 thread:: sleep ( Duration :: from_secs ( 1 )); 814// A signal while idle fires its listener and the session continues. 815assert_eq! ( unsafe { libc:: kill ( child. id () as i32 , libc:: SIGUSR1 ) }, 0 ); 816 thread:: sleep ( Duration :: from_millis ( 500 )); 817// SIGINT still interrupts running evaluation, and its listener observes 818// the interrupt rather than being clobbered by it. 819 stdin. write_all ( b"while (true) {}\n" ). unwrap (); 820 thread:: sleep ( Duration :: from_millis ( 500 )); 821assert_eq! ( unsafe { libc:: kill ( child. id () as i32 , libc:: SIGINT ) }, 0 ); 822 thread:: sleep ( Duration :: from_millis ( 500 )); 823 stdin. write_all ( b"40 + 2\n.exit\n" ). unwrap (); 824drop ( stdin); 825let output = common:: wait ( child); 826assert! ( output. status . success ()); 827let stdout =String :: from_utf8 ( output. stdout ). unwrap (); 828assert! ( stdout. contains ( "repl-got-usr1\n" ), "{stdout}" ); 829assert! ( stdout. contains ( "repl-got-int\n" ), "{stdout}" ); 830assert! ( stdout. contains ( "42\n" ), "{stdout}" ); 831assert! ( 832String :: from_utf8 ( output. stderr ) 833. unwrap () 834. contains ( "Evaluation interrupted" ) 835); 836}