char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1use std:: fs; 2use std:: path:: Path ; 3use std:: process::{ Command , Output }; 4 5fn files ( root : & Path , files : & [( & str , & str )]) { 6for ( name, source) in files{ 7let path = root. join ( name); 8 fs:: create_dir_all ( path. parent (). unwrap ()). unwrap (); 9 fs:: write ( path, source). unwrap (); 10} 11} 12 13fn run ( root : & Path , entry : & str , args : & [ & str ]) ->Output { 14Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 15. current_dir ( root) 16// Options alone run the entry; a leading command selects another. 17. args ( 18 args. first () 19. is_none_or ( |arg| arg. starts_with ( '-' )) 20. then_some ( "run" ), 21) 22. args ( args) 23. arg ( entry) 24. output () 25. unwrap () 26} 27 28fn success ( output : & Output ) ->& str { 29assert! ( 30 output. status . success (), 31"{}" , 32String :: from_utf8_lossy ( & output. stderr ) 33); 34 std:: str:: from_utf8 ( & output. stdout ). unwrap () 35} 36 37# [ test ] 38fn renamed_scoped_and_self_exports_share_identity_with_relative_imports () { 39let dir = tempfile:: tempdir (). unwrap (); 40files ( 41 dir. path (), 42& [ 43( 44"app/avril.conf" , 45"name: '@app/demo', deps: [{from: '../math', name: 'math'}, {from: '../math', name: 'math-copy'}, {from: '../math', name: '@app/math'}]" , 46), 47( 48"app/main.ts" , 49"import * as a from 'math'; import * as b from 'math-copy'; import * as c from '../math/mod.ts'; import * as d from '@app/math'; import * as v from 'math/vector'; const name = 'math'; console.log(a === b, b === c, c === d, v === await import(name + '/vector'), a.self === v, a.count, v.count); try {await import('@original/math');} catch(e) {console.log(e.message.includes('Unsupported module specifier'));}" , 50), 51( 52"math/avril.conf" , 53"name: '@original/math', version: '0.1.0', description: 'Shared math utilities', exports: {'.': './mod.ts', './vector': './src/vector.ts'}" , 54), 55( 56"math/mod.ts" , 57"import * as self from '@original/math/vector'; export {self}; export const count = globalThis.roots = (globalThis.roots ?? 0) + 1;" , 58), 59( 60"math/src/vector.ts" , 61"export const count = globalThis.vectors = (globalThis.vectors ?? 0) + 1;" , 62), 63], 64); 65assert_eq! ( 66success ( & run ( dir. path (), "app/main.ts" , & [])), 67"true true true true true 1 1\ntrue\n" 68); 69assert_eq! ( success ( & run ( & dir. path (). join ( "math" ), "mod.ts" , & [])), "" ); 70} 71 72# [ test ] 73fn identical_declared_names_in_different_directories_keep_distinct_self_bindings () { 74let dir = tempfile:: tempdir (). unwrap (); 75files ( 76 dir. path (), 77& [ 78( 79"app/avril.conf" , 80"deps: [{from: '../v1', name: 'math'}, {from: '../v2', name: 'math-next'}, {from: '../format', name: '@app/format'}]" , 81), 82( 83"app/main.ts" , 84"import {value as first} from 'math'; import {value as second} from 'math-next'; import {format} from '@app/format'; console.log(first, second, format);" , 85), 86( 87"v1/avril.conf" , 88"name: '@acme/math', version: '1.0.0', exports: {'.': './mod.ts', './inner': './inner.ts'}" , 89), 90( 91"v2/avril.conf" , 92"name: '@acme/math', version: '2.0.0', exports: {'.': './mod.ts', './inner': './inner.ts'}" , 93), 94( "v1/mod.ts" , "export {value} from '@acme/math/inner';" ), 95( "v2/mod.ts" , "export {value} from '@acme/math/inner';" ), 96( "v1/inner.ts" , "export const value = 1;" ), 97( "v2/inner.ts" , "export const value = 2;" ), 98( "format/avril.conf" , "exports: './mod.ts'" ), 99( "format/mod.ts" , "export const format = 'unnamed';" ), 100], 101); 102assert_eq! ( 103success ( & run ( dir. path (), "app/main.ts" , & [])), 104"1 2 unnamed\n" 105); 106} 107 108# [ test ] 109fn dependencies_and_explicit_maps_are_private_to_the_owning_package () { 110let dir = tempfile:: tempdir (). unwrap (); 111files ( 112 dir. path (), 113& [ 114( 115"app/avril.conf" , 116"deps: [{from: '../math', name: 'math'}], imports: {numeric: './app-number.ts', consumerOnly: './app-number.ts'}" , 117), 118( 119"app/main.ts" , 120"import {value, isolated} from 'math'; import {value as appValue} from 'numeric'; console.log(value, appValue, await isolated());" , 121), 122( "app/app-number.ts" , "export const value = 'app';" ), 123( 124"math/avril.conf" , 125"exports: './mod.ts', deps: ['../numeric'], imports: {helper: './helper.ts'}" , 126), 127( 128"math/mod.ts" , 129"import {value as n} from 'number-implementation'; import {suffix} from 'helper'; export const value = n + suffix; export async function isolated() {try {await import('consumerOnly'); return false;} catch(e) {return e.message.includes('Unsupported module specifier');}}" , 130), 131( "math/helper.ts" , "export const suffix = '-math';" ), 132( 133"numeric/avril.conf" , 134"name: 'number-implementation', exports: './mod.ts'" , 135), 136( "numeric/mod.ts" , "export const value = 'numeric';" ), 137], 138); 139assert_eq! ( 140success ( & run ( dir. path (), "app/main.ts" , & [])), 141"numeric-math app true\n" 142); 143files ( 144 dir. path (), 145& [( "app/main.ts" , "import 'number-implementation';" )], 146); 147let output =run ( dir. path (), "app/main.ts" , & []); 148assert! ( !output. status . success ()); 149assert! ( 150String :: from_utf8_lossy ( & output. stderr ) 151. contains ( "Unsupported module specifier: number-implementation" ) 152); 153} 154 155# [ test ] 156fn cyclic_package_headers_and_source_graphs_do_not_scan_unused_dependencies () { 157let dir = tempfile:: tempdir (). unwrap (); 158files ( 159 dir. path (), 160& [ 161( 162"app/avril.conf" , 163"deps: ['../a', '../b', {from: '../missing', name: 'unused'}]" , 164), 165( 166"app/main.ts" , 167"import {cycle} from '@cycle/a'; import {leafB} from '@cycle/b/leaf'; console.log(cycle(), leafB());" , 168), 169( 170"a/avril.conf" , 171"name: '@cycle/a', exports: './mod.ts', deps: ['../b']" , 172), 173( 174"a/mod.ts" , 175"import {leafB} from '@cycle/b/leaf'; export const a = 'a'; export function cycle() {return leafB();}" , 176), 177( 178"b/avril.conf" , 179"name: '@cycle/b', exports: {'./leaf': './leaf.ts'}, deps: ['../a']" , 180), 181( 182"b/leaf.ts" , 183"import {a} from '@cycle/a'; export function leafB() {return a + 'b';}" , 184), 185], 186); 187assert_eq! ( success ( & run ( dir. path (), "app/main.ts" , & [])), "ab ab\n" ); 188} 189 190# [ test ] 191fn package_imports_have_no_private_file_fallback_but_relative_files_remain_available () { 192let dir = tempfile:: tempdir (). unwrap (); 193files ( 194 dir. path (), 195& [ 196( "app/avril.conf" , "deps: [{from: '../math', name: 'math'}]" ), 197( 198"app/main.js" , 199"import {privateSelf} from 'math'; console.log(await privateSelf());" , 200), 201( "math/avril.conf" , "name: '@acme/math', exports: './mod.js'" ), 202( 203"math/mod.js" , 204"export async function privateSelf() {try {await import('@acme/math/private.js'); return false;} catch(e) {return e.message.includes('no export');}}" , 205), 206( "math/private.js" , "console.log('private relative file');" ), 207], 208); 209assert_eq! ( success ( & run ( dir. path (), "app/main.js" , & [])), "true\n" ); 210for specifierin [ 211"math/private.js" , 212"math/../private.js" , 213"math/%2e%2e/private.js" , 214"math/" , 215] { 216files ( 217 dir. path (), 218& [( "app/main.js" , & format! ( "import '{specifier}';" ))], 219); 220let output =run ( dir. path (), "app/main.js" , & []); 221assert! ( !output. status . success (), "{specifier}" ); 222assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "no export" )); 223assert! ( output. stdout . is_empty ()); 224} 225files ( 226 dir. path (), 227& [( "app/main.js" , "import '../math/private.js';" )], 228); 229assert_eq! ( 230success ( & run ( dir. path (), "app/main.js" , & [])), 231"private relative file\n" 232); 233} 234 235# [ test ] 236fn declared_dependency_requires_its_own_config_and_nonempty_exports () { 237let dir = tempfile:: tempdir (). unwrap (); 238files ( 239 dir. path (), 240& [ 241( 242"avril.conf" , 243"exports: './main.js', deps: [{from: './lib', name: 'lib'}]" , 244), 245( "main.js" , "import 'lib';" ), 246( "lib/mod.js" , "throw Error('not exported');" ), 247], 248); 249let output =run ( dir. path (), "main.js" , & []); 250assert! ( !output. status . success ()); 251assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "must contain avril.conf" )); 252files ( dir. path (), & [( "lib/avril.conf" , "name: 'original'" )]); 253let output =run ( dir. path (), "main.js" , & []); 254assert! ( !output. status . success ()); 255assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "has no exports" )); 256files ( 257 dir. path (), 258& [( "lib/avril.conf" , "exports: {'./sub': './mod.js'}" )], 259); 260let output =run ( dir. path (), "main.js" , & []); 261assert! ( !output. status . success ()); 262assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "no export \".\"" )); 263} 264 265# [ test ] 266fn export_files_and_cached_dependency_headers_obey_live_execution_caps () { 267let dir = tempfile:: tempdir (). unwrap (); 268files ( 269 dir. path (), 270& [ 271( "app/avril.conf" , "deps: ['../lib']" ), 272( "lib/avril.conf" , "name: 'lib', exports: './mod.js'" ), 273( "lib/mod.js" , "export const value = 1;" ), 274], 275); 276for deniedin [ "lib/avril.conf" , "lib/mod.js" , "lib" ] { 277files ( 278 dir. path (), 279& [( 280"app/main.js" , 281& format! ( 282"import {{pledge}} from 'avril:std'; await import('lib'); pledge({{jsExec: {{deny: ['{denied}']}}}}); try {{await import('lib'); console.log('allowed');}} catch(e) {{console.log(e.message.includes('PermissionDenied'));}}" 283), 284)], 285); 286assert_eq! ( 287success ( & run ( dir. path (), "app/main.js" , & [])), 288"true\n" , 289"{denied}" 290); 291} 292} 293 294# [ test ] 295fn denied_dependencies_do_not_disclose_directory_or_config_existence () { 296let dir = tempfile:: tempdir (). unwrap (); 297files ( 298 dir. path (), 299& [ 300( "app/avril.conf" , "deps: [{from: '../secret', name: 'lib'}]" ), 301( "app/main.js" , "import 'lib';" ), 302], 303); 304let args =[ "-0" , "--allow-js-exec=app" ]; 305let missing =run ( dir. path (), "app/main.js" , & args); 306files ( dir. path (), & [( "secret/avril.conf" , "not: valid: syntax" )]); 307let present =run ( dir. path (), "app/main.js" , & args); 308assert! ( !missing. status . success ()); 309assert_eq! ( missing. stderr , present. stderr ); 310assert! ( String :: from_utf8_lossy ( & missing. stderr ). contains ( "PermissionDenied: js-exec lib" )); 311let args =[ 312"-0" , 313"--allow-js-exec=app" , 314"--allow-js-exec=secret" , 315"--deny-js-exec=secret/avril.conf" , 316]; 317 fs:: remove_file ( dir. path (). join ( "secret/avril.conf" )). unwrap (); 318let missing =run ( dir. path (), "app/main.js" , & args); 319files ( dir. path (), & [( "secret/avril.conf" , "not: valid: syntax" )]); 320let present =run ( dir. path (), "app/main.js" , & args); 321assert_eq! ( missing. stderr , present. stderr ); 322assert! ( String :: from_utf8_lossy ( & missing. stderr ). contains ( "PermissionDenied: js-exec lib" )); 323} 324 325# [ cfg ( unix )] 326# [ test ] 327fn exported_symlinks_are_confined_and_directory_aliases_preserve_identity () { 328use std:: os:: unix:: fs:: symlink; 329let dir = tempfile:: tempdir (). unwrap (); 330files ( 331 dir. path (), 332& [ 333( 334"app/avril.conf" , 335"deps: [{from: '../lib', name: 'lib'}, {from: '../link', name: 'renamed'}]" , 336), 337( 338"app/main.js" , 339"import * as a from 'lib'; import * as b from 'renamed'; console.log(a === b);" , 340), 341( "lib/avril.conf" , "exports: './public.js'" ), 342( "lib/inside.js" , "export const value = 1;" ), 343( "outside.js" , "console.log('escaped');" ), 344], 345); 346symlink ( "lib" , dir. path (). join ( "link" )). unwrap (); 347symlink ( "inside.js" , dir. path (). join ( "lib/public.js" )). unwrap (); 348assert_eq! ( success ( & run ( dir. path (), "app/main.js" , & [])), "true\n" ); 349 fs:: remove_file ( dir. path (). join ( "lib/public.js" )). unwrap (); 350symlink ( "../outside.js" , dir. path (). join ( "lib/public.js" )). unwrap (); 351let output =run ( dir. path (), "app/main.js" , & []); 352assert! ( !output. status . success ()); 353assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "Export target escapes its package" )); 354assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "avril.conf:1:" )); 355assert! ( output. stdout . is_empty ()); 356} 357 358# [ cfg ( unix )] 359# [ test ] 360fn dependency_config_symlinks_and_world_writable_headers_are_rejected () { 361use std:: os:: unix:: fs::{ PermissionsExt , symlink}; 362// Path-only entries read the dependency's name at startup, outside guest grants. 363for depsin [ "[{from: '../lib', name: 'lib'}]" , "['../lib']" ] { 364let dir = tempfile:: tempdir (). unwrap (); 365files ( 366 dir. path (), 367& [ 368( "app/avril.conf" , & format! ( "deps: {deps}" )), 369( "app/main.js" , "import 'lib';" ), 370( "lib/other.conf" , "name: 'lib', exports: './mod.js'" ), 371( "lib/mod.js" , "console.log('untrusted');" ), 372], 373); 374let config = dir. path (). join ( "lib/avril.conf" ); 375symlink ( "other.conf" , & config). unwrap (); 376let present =run ( dir. path (), "app/main.js" , & []); 377 fs:: remove_file ( & config). unwrap (); 378symlink ( "missing" , & config). unwrap (); 379let missing =run ( dir. path (), "app/main.js" , & []); 380assert! ( !present. status . success (), "{deps}" ); 381assert_eq! ( present. stderr , missing. stderr , "{deps}" ); 382assert! ( String :: from_utf8_lossy ( & present. stderr ). contains ( "non-symlink" )); 383 fs:: remove_file ( & config). unwrap (); 384 fs:: write ( & config, "name: 'lib', exports: './mod.js'" ). unwrap (); 385 fs:: set_permissions ( & config, fs:: Permissions :: from_mode ( 0o666 )). unwrap (); 386let output =run ( dir. path (), "app/main.js" , & []); 387assert! ( !output. status . success (), "{deps}" ); 388assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "Untrusted config" )); 389assert! ( output. stdout . is_empty ()); 390 fs:: set_permissions ( & config, fs:: Permissions :: from_mode ( 0o644 )). unwrap (); 391let lib = dir. path (). join ( "lib" ); 392 fs:: set_permissions ( & lib, fs:: Permissions :: from_mode ( 0o777 )). unwrap (); 393let output =run ( dir. path (), "app/main.js" , & []); 394assert! ( !output. status . success (), "{deps}" ); 395assert! ( String :: from_utf8_lossy ( & output. stderr ). contains ( "Untrusted dependency directory" )); 396assert! ( output. stdout . is_empty ()); 397 fs:: set_permissions ( & lib, fs:: Permissions :: from_mode ( 0o755 )). unwrap (); 398assert_eq! ( success ( & run ( dir. path (), "app/main.js" , & [])), "untrusted\n" ); 399} 400} 401 402# [ test ] 403fn info_reports_metadata_and_per_referrer_export_bindings () { 404let dir = tempfile:: tempdir (). unwrap (); 405files ( 406 dir. path (), 407& [ 408( 409"app/avril.conf" , 410"name: '@app/demo', deps: ['../math', {from: '../math', name: 'math'}], imports: {helper: './helper.ts'}" , 411), 412( 413"app/main.ts" , 414"import {value} from 'math'; import {typed} from '@original/math'; export const result = value + typed; throw Error('must not execute');" , 415), 416( "app/helper.ts" , "export const value = 'app';" ), 417( 418"math/avril.conf" , 419"name: '@original/math', version: '0.1.0', description: 'Shared utilities', exports: {'.': './mod.ts', './types': './types.d.ts'}, imports: {helper: './helper.ts'}" , 420), 421( 422"math/mod.ts" , 423"import {value} from 'helper'; import type {Value} from '@original/math/types'; export const typed: Value = value; export {value};" , 424), 425( "math/helper.ts" , "export const value = 1;" ), 426( "math/types.d.ts" , "export type Value = number;" ), 427], 428); 429let output =run ( dir. path (), "app/main.ts" , & [ "info" ]); 430let graph: serde_json:: Value = serde_json:: from_str ( success ( & output)). unwrap (); 431let main =& graph[ "modules" ][ dir. path (). join ( "app/main.ts" ). to_str (). unwrap ()]; 432let library =& graph[ "modules" ][ dir. path (). join ( "math/mod.ts" ). to_str (). unwrap ()]; 433assert_eq! ( 434 main[ "imports" ][ "math" ], 435 dir. path (). join ( "math/mod.ts" ). to_str (). unwrap () 436); 437assert_eq! ( main[ "package" ][ "name" ], "@app/demo" ); 438for bindingin [ "math" , "@original/math" ] { 439assert_eq! ( 440 main[ "imports" ][ binding], 441 dir. path (). join ( "math/mod.ts" ). to_str (). unwrap () 442); 443assert_eq! ( 444 main[ "package" ][ "dependencies" ][ binding], 445 dir. path (). join ( "math" ). to_str (). unwrap () 446); 447} 448assert_eq! ( library[ "package" ][ "name" ], "@original/math" ); 449assert_eq! ( library[ "package" ][ "version" ], "0.1.0" ); 450assert_eq! ( library[ "package" ][ "description" ], "Shared utilities" ); 451assert_eq! ( 452 library[ "imports" ][ "helper" ], 453 dir. path (). join ( "math/helper.ts" ). to_str (). unwrap () 454); 455} 456 457# [ test ] 458fn failed_dependency_headers_can_be_repaired_without_poisoning_later_imports () { 459let dir = tempfile:: tempdir (). unwrap (); 460files ( 461 dir. path (), 462& [ 463( "avril.conf" , "deps: [{from: './lib', name: 'lib'}]" ), 464( 465"main.js" , 466"import {writeTextFile} from 'avril:fs'; try {await import('lib');} catch(e) {console.log(e.message.includes('avril.conf'));} await writeTextFile('./lib/avril.conf', \"exports: './mod.js'\"); console.log((await import('lib')).value);" , 467), 468( "lib/mod.js" , "export const value = 42;" ), 469], 470); 471assert_eq! ( success ( & run ( dir. path (), "main.js" , & [])), "true\n42\n" ); 472files ( dir. path (), & [( "lib/avril.conf" , "exports: false" )]); 473assert_eq! ( success ( & run ( dir. path (), "main.js" , & [])), "true\n42\n" ); 474} 475 476# [ test ] 477fn exported_sources_keep_their_nearest_nested_package_ownership () { 478let dir = tempfile:: tempdir (). unwrap (); 479files ( 480 dir. path (), 481& [ 482( 483"app/avril.conf" , 484"deps: [{from: '../outer', name: 'outer'}]" , 485), 486( 487"app/main.ts" , 488"import {value, isolated} from 'outer'; console.log(value, await isolated());" , 489), 490( 491"outer/avril.conf" , 492"name: '@original/outer', exports: './inner/mod.ts', imports: {helper: './outer-helper.ts'}" , 493), 494( "outer/outer-helper.ts" , "export const value = 'outer';" ), 495( 496"outer/inner/avril.conf" , 497"name: '@original/inner', exports: {'./value': './value.ts'}" , 498), 499( 500"outer/inner/mod.ts" , 501"export {value} from '@original/inner/value'; export async function isolated() {try {await import('helper'); return false;} catch(e) {return e.message.includes('Unsupported module specifier');}}" , 502), 503( "outer/inner/value.ts" , "export const value = 'inner';" ), 504], 505); 506assert_eq! ( 507success ( & run ( dir. path (), "app/main.ts" , & [])), 508"inner true\n" 509); 510} 511 512# [ test ] 513fn ordinary_absence_snapshots_require_a_fresh_loader_to_establish_a_new_package () { 514let dir = tempfile:: tempdir (). unwrap (); 515files ( 516 dir. path (), 517& [ 518( "avril.conf" , "deps: [{from: './lib', name: 'lib'}]" ), 519( 520"main.js" , 521"import * as relative from './lib/mod.js'; import {writeTextFile} from 'avril:fs'; await writeTextFile('./lib/avril.conf', \"exports: './mod.js'\"); try {console.log('same', (await import('lib')) === relative);} catch(e) {console.log('snapshot', e.message.includes('must contain avril.conf'));}" , 522), 523( "lib/mod.js" , "export {value} from './helper.js';" ), 524( "lib/helper.js" , "export const value = 1;" ), 525], 526); 527assert_eq! ( success ( & run ( dir. path (), "main.js" , & [])), "snapshot true\n" ); 528assert_eq! ( success ( & run ( dir. path (), "main.js" , & [])), "same true\n" ); 529} 530 531# [ test ] 532fn path_only_dependencies_bind_their_declared_names_and_overrides_do_not () { 533let dir = tempfile:: tempdir (). unwrap (); 534files ( 535 dir. path (), 536& [ 537( 538"app/avril.conf" , 539"deps: ['../math', {from: '../math', name: 'math'}]" , 540), 541( 542"app/main.ts" , 543"import * as a from '@acme/math'; import * as b from 'math'; import * as v from '@acme/math/vector'; import * as w from 'math/vector'; console.log(a === b, v === w, a.self === v, a.value);" , 544), 545( 546"math/avril.conf" , 547"name: '@acme/math', exports: {'.': './mod.ts', './vector': './vector.ts'}" , 548), 549( 550"math/mod.ts" , 551"import * as self from '@acme/math/vector'; export {self}; export const value = 'math';" , 552), 553( "math/vector.ts" , "export const vector = true;" ), 554], 555); 556assert_eq! ( 557success ( & run ( dir. path (), "app/main.ts" , & [])), 558"true true true math\n" 559); 560files ( 561 dir. path (), 562& [( "app/avril.conf" , "deps: [{from: '../math', name: 'math'}]" )], 563); 564let output =run ( dir. path (), "app/main.ts" , & []); 565assert! ( !output. status . success ()); 566assert! ( 567String :: from_utf8_lossy ( & output. stderr ) 568. contains ( "Unsupported module specifier: @acme/math" ) 569); 570} 571 572# [ test ] 573fn path_only_dependency_names_must_be_declared_and_unique () { 574let dir = tempfile:: tempdir (). unwrap (); 575files ( 576 dir. path (), 577& [ 578( "app/main.js" , "console.log('ran');" ), 579( "v1/avril.conf" , "name: '@acme/math', exports: './mod.js'" ), 580( "v2/avril.conf" , "name: '@acme/math', exports: './mod.js'" ), 581( "anonymous/avril.conf" , "exports: './mod.js'" ), 582( "broken/avril.conf" , "name: false" ), 583], 584); 585for ( deps, message) in [ 586( "['../anonymous']" , "declares no name" ), 587( "['../missing']" , "Cannot read the name of dependency" ), 588( "['../v2/../broken']" , "Expected a string" ), 589( 590"['../v1', '../v2']" , 591"Duplicate dependency binding \"@acme/math\"" , 592), 593] { 594files ( dir. path (), & [( "app/avril.conf" , & format! ( "deps: {deps}" ))]); 595let output =run ( dir. path (), "app/main.js" , & []); 596let stderr =String :: from_utf8_lossy ( & output. stderr ); 597assert! ( !output. status . success (), "{deps}" ); 598assert! ( output. stdout . is_empty (), "{deps}" ); 599assert! ( stderr. contains ( message), "{deps}: {stderr}" ); 600let consumer =format! ( "app{}avril.conf:1:" , std:: path:: MAIN_SEPARATOR ); 601assert! ( stderr. contains ( & consumer), "{deps}: {stderr}" ); 602} 603} 604 605# [ test ] 606fn root_bindings_are_read_at_startup_and_nested_bindings_when_their_config_loads () { 607let dir = tempfile:: tempdir (). unwrap (); 608files ( 609 dir. path (), 610& [ 611( 612"app/avril.conf" , 613"deps: ['../lib', {from: '../mid', name: 'mid'}]" , 614), 615( 616"app/main.js" , 617"import {writeTextFile} from 'avril:fs'; await writeTextFile('../lib/avril.conf', \"name: 'renamed', exports: './mod.js'\"); await writeTextFile('../leaf/avril.conf', \"name: 'leaf', exports: './mod.js'\"); console.log((await import('original')).value, (await import('mid')).value); try {await import('renamed');} catch(e) {console.log(e.message.includes('Unsupported module specifier'));}" , 618), 619( "lib/avril.conf" , "name: 'original', exports: './mod.js'" ), 620( "lib/mod.js" , "export const value = 'lib';" ), 621( "mid/avril.conf" , "exports: './mod.js', deps: ['../leaf']" ), 622( 623"mid/mod.js" , 624"import {value as leaf} from 'leaf'; export const value = 'mid-' + leaf;" , 625), 626( "leaf/mod.js" , "export const value = 'leaf';" ), 627], 628); 629let output =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 630. arg ( "run" ) 631. current_dir ( dir. path (). join ( "app" )) 632. arg ( "main.js" ) 633. output () 634. unwrap (); 635assert_eq! ( success ( & output), "lib mid-leaf\ntrue\n" ); 636} 637 638# [ test ] 639fn name_discovery_ignores_execution_grants_but_loading_does_not () { 640let dir = tempfile:: tempdir (). unwrap (); 641files ( 642 dir. path (), 643& [ 644( "app/avril.conf" , "deps: ['../lib']" ), 645( 646"app/main.js" , 647"for (const name of ['@acme/lib', 'undeclared']) {try {console.log((await import(name)).value);} catch(e) {console.log(e.message);}}" , 648), 649( "lib/avril.conf" , "name: '@acme/lib', exports: './mod.js'" ), 650( "lib/mod.js" , "export const value = 'lib';" ), 651], 652); 653assert_eq! ( 654success ( & run ( 655 dir. path (), 656"app/main.js" , 657& [ "-0" , "--allow-js-exec=app" ] 658)), 659"PermissionDenied: js-exec @acme/lib\nUnsupported module specifier: undeclared\n" 660); 661assert_eq! ( 662success ( & run ( 663 dir. path (), 664"app/main.js" , 665& [ "-0" , "--allow-js-exec=app" , "--allow-js-exec=lib" ] 666)), 667"lib\nUnsupported module specifier: undeclared\n" 668); 669assert_eq! ( 670success ( & run ( 671 dir. path (), 672"app/main.js" , 673& [ 674"-0" , 675"--allow-js-exec=app" , 676"--allow-js-exec=lib" , 677"--deny-js-exec=lib/avril.conf" 678] 679)), 680"PermissionDenied: js-exec @acme/lib\nUnsupported module specifier: undeclared\n" 681); 682files ( 683 dir. path (), 684& [( 685"app/main.js" , 686"import {pledge} from 'avril:std'; pledge({jsExec: {deny: ['lib/avril.conf']}}); try {await import('@acme/lib'); console.log('allowed');} catch(e) {console.log(e.message);}" , 687)], 688); 689assert_eq! ( 690success ( & run ( dir. path (), "app/main.js" , & [])), 691"PermissionDenied: js-exec @acme/lib\n" 692); 693}