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# [ path = "common/http.rs" ] 3mod http_fixture; 4# [ path = "common/registry.rs" ] 5mod registry_fixture; 6 7use std:: fs; 8use std:: process::{ Command , Stdio }; 9 10fn run ( directory : & std:: path:: Path , entry : & str , args : & [ String ]) -> std:: process:: Output { 11let output = common:: wait ( 12Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 13. arg ( "run" ) 14. current_dir ( directory) 15. args ( args) 16. arg ( entry) 17. stdout ( Stdio :: piped ()) 18. stderr ( Stdio :: piped ()) 19. spawn () 20. unwrap (), 21); 22assert! ( 23 output. status . success (), 24"{}" , 25String :: from_utf8_lossy ( & output. stderr ) 26); 27 output 28} 29 30# [ test ] 31fn file_metadata_resolves_without_loading_and_binds_to_the_module () { 32let dir = tempfile:: tempdir (). unwrap (); 33 fs:: create_dir ( dir. path (). join ( "nested" )). unwrap (); 34 fs:: write ( dir. path (). join ( "avril.conf" ), "imports: { alias: './not%20present%20%23%F0%9F%98%80.ts', blocked: null }, scopes: {'./nested/': {alias: './nested/missing.ts'}}" ). unwrap (); 35 fs:: write ( 36 dir. path (). join ( "nested/dep.ts" ), 37"export const resolve = import.meta.resolve; export const meta = import.meta;" , 38) 39. unwrap (); 40 fs:: write ( 41 dir. path (). join ( "not-evaluated.js" ), 42"throw Error('Resolution evaluated a module');" , 43) 44. unwrap (); 45 fs:: write ( dir. path (). join ( "main #😀.ts" ), r#" 46import {resolve as other, meta} from './nested/dep.ts'; 47const ownURL = import.meta.url; 48if (import.meta.filename !== decodeURIComponent(new URL(ownURL).pathname) || 49import.meta.dirname !== decodeURIComponent(new URL('.',ownURL).pathname).slice(0,-1)) throw Error('File paths'); 50if (Object.getPrototypeOf(import.meta) !== null || !import.meta.main || meta.main) throw Error('Metadata'); 51const resolve = import.meta.resolve; 52if (resolve.length !== 1 || resolve.name !== 'resolve') throw Error('Function surface'); 53import.meta.url = 'http://fake.invalid/'; 54if (resolve('./not-evaluated.js') !== new URL('./not-evaluated.js',ownURL).href || 55resolve('alias') !== new URL('./not%20present%20%23%F0%9F%98%80.ts',ownURL).href || 56other.call(import.meta,'alias') !== new URL('./nested/missing.ts',ownURL).href || 57resolve('avril:std') !== 'avril:std') throw Error('Resolver identity or import map'); 58const marker = {}; 59try {resolve({toString() {throw marker;}}); throw Error('Swallowed exception');} catch (error) {if (error !== marker) throw error;} 60for (const operation of [() => resolve(Symbol()), () => resolve('blocked'), () => resolve('unknown'), () => resolve('ext:core/ops'), () => new resolve('./x')]) { 61try {operation(); throw Error('Expected TypeError');} catch (error) {if (!(error instanceof TypeError)) throw error;} 62} 63if (resolve(new URL('./missing.ts',ownURL)) !== new URL('./missing.ts',ownURL).href) throw Error('URL coercion'); 64console.log('metadata'); 65"# ). unwrap (); 66assert_eq! ( run ( dir. path (), "main #😀.ts" , & []). stdout , b"metadata\n" ); 67} 68 69# [ test ] 70fn package_exports_and_live_permissions_apply_to_resolution () { 71let dir = tempfile:: tempdir (). unwrap (); 72 fs:: create_dir ( dir. path (). join ( "library" )). unwrap (); 73 fs:: write ( 74 dir. path (). join ( "avril.conf" ), 75"name: 'app', exports: {'.': './main.js'}, deps: ['./library']" , 76) 77. unwrap (); 78 fs:: write ( 79 dir. path (). join ( "library/avril.conf" ), 80"name: 'lib', exports: {'.': './missing.ts'}" , 81) 82. unwrap (); 83 fs:: write ( dir. path (). join ( "main.js" ), r#" 84import {pledge} from 'avril:std'; 85if (import.meta.resolve('app') !== import.meta.url || import.meta.resolve('lib') !== new URL('./library/missing.ts',import.meta.url).href) throw Error('Exports'); 86const blocked = new URL('./blocked.js', import.meta.url); 87pledge({jsExec:{deny:[blocked]}}); 88try {import.meta.resolve('./blocked.js'); throw Error('Permission accepted');} 89catch (error) {if (!(error instanceof TypeError) || !error.message.includes('PermissionDenied')) throw error;} 90console.log('exports'); 91"# ). unwrap (); 92assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"exports\n" ); 93} 94 95# [ test ] 96fn http_metadata_resolves_from_the_redirected_base_without_fetching () { 97let http = http_fixture:: Http :: new (); 98 http. redirect ( "start" , & http. name ( "nested/module.js" )); 99 http. source ( "nested/module.js" , r#" 100if ('dirname' in import.meta || 'filename' in import.meta) throw Error('HTTP filesystem metadata'); 101console.log(import.meta.resolve('./missing.js?query#fragment')); 102"# ); 103let dir = tempfile:: tempdir (). unwrap (); 104let output =run ( 105 dir. path (), 106& http. name ( "start" ), 107& [ "--cache-dir=cache" . into ()], 108); 109assert_eq! ( 110 output. stdout , 111format! ( "{}\n" , http. name ( "nested/missing.js?query#fragment" )). as_bytes () 112); 113assert! ( 114 !http 115. requests 116. lock () 117. unwrap () 118. iter () 119. any ( |path| path. contains ( "missing" )) 120); 121} 122 123# [ test ] 124fn prepared_jsr_metadata_resolves_other_exports_without_acquisition () { 125let registry = registry_fixture:: Registry :: new (); 126 registry. package ( 127"@test/meta" , 128"1.0.0" , 129& [ 130( "mod.ts" , "export {};" ), 131( "other.ts" , "throw Error('Loaded other export');" ), 132], 133& [( "." , "./mod.ts" ), ( "./other" , "./other.ts" )], 134); 135let dir = tempfile:: tempdir (). unwrap (); 136 fs:: write ( 137 dir. path (). join ( "avril.conf" ), 138"imports: {pkg: 'jsr:@test/meta@1', 'pkg/': 'jsr:@test/meta@1/'}" , 139) 140. unwrap (); 141 fs:: write ( 142 dir. path (). join ( "main.ts" ), 143"import 'pkg'; console.log(import.meta.resolve('pkg/other'));" , 144) 145. unwrap (); 146let options = registry. options ( dir. path ()); 147let output =run ( 148 dir. path (), 149"main.ts" , 150& [ 151format! ( "--jsr-registry={}" , options. jsr_registry ), 152format! ( "--cache-dir={}" , options. cache . display ()), 153], 154); 155assert_eq! ( 156 output. stdout , 157format! ( "{}@test/meta/1.0.0/other.ts\n" , registry. url ). as_bytes () 158); 159assert! ( 160 !registry 161. requests 162. lock () 163. unwrap () 164. iter () 165. any ( |path| path. ends_with ( "other.ts" )) 166); 167} 168 169# [ test ] 170fn resolve_matches_loaded_module_for_relative_files () { 171let dir = tempfile:: tempdir (). unwrap (); 172 fs:: create_dir ( dir. path (). join ( "sub" )). unwrap (); 173 fs:: write ( 174 dir. path (). join ( "dep.js" ), 175"export const metaUrl = import.meta.url;\n" , 176) 177. unwrap (); 178 fs:: write ( 179 dir. path (). join ( "sub/nested.js" ), 180"export const metaUrl = import.meta.url;\n" , 181) 182. unwrap (); 183 fs:: write ( dir. path (). join ( "main.js" ), r#" 184async function check(spec) { 185const loaded = await import(spec); 186const viaMeta = import.meta.resolve(spec); 187if (viaMeta !== loaded.metaUrl) throw new Error(`parity ${spec}: ${viaMeta} !== ${loaded.metaUrl}`); 188} 189await check('./dep.js'); 190await check('./sub/nested.js'); 191console.log('relative-parity'); 192"# ). unwrap (); 193assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"relative-parity\n" ); 194} 195 196# [ test ] 197fn resolve_matches_loaded_module_for_local_binding_exports () { 198let dir = tempfile:: tempdir (). unwrap (); 199 fs:: create_dir ( dir. path (). join ( "lib" )). unwrap (); 200 fs:: write ( 201 dir. path (). join ( "avril.conf" ), 202"name: 'app', deps: ['./lib']" , 203) 204. unwrap (); 205 fs:: write ( 206 dir. path (). join ( "lib/avril.conf" ), 207"name: 'mylib', exports: {'.': './index.js', './feature': './feature.js'}" , 208) 209. unwrap (); 210 fs:: write ( 211 dir. path (). join ( "lib/index.js" ), 212"export const metaUrl = import.meta.url;\n" , 213) 214. unwrap (); 215 fs:: write ( 216 dir. path (). join ( "lib/feature.js" ), 217"export const metaUrl = import.meta.url;\n" , 218) 219. unwrap (); 220 fs:: write ( dir. path (). join ( "main.js" ), r#" 221async function check(spec) { 222const loaded = await import(spec); 223const viaMeta = import.meta.resolve(spec); 224if (viaMeta !== loaded.metaUrl) throw new Error(`parity ${spec}: ${viaMeta} !== ${loaded.metaUrl}`); 225} 226await check('mylib'); 227await check('mylib/feature'); 228console.log('binding-parity'); 229"# ). unwrap (); 230assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"binding-parity\n" ); 231} 232 233# [ test ] 234fn resolve_matches_loaded_module_for_http_modules () { 235let http = http_fixture:: Http :: new (); 236 http. redirect ( "start" , & http. name ( "nested/module.js" )); 237 http. source ( 238"nested/module.js" , 239r#" 240const viaMeta = import.meta.resolve('./sibling.js'); 241const loaded = await import('./sibling.js'); 242if (viaMeta !== loaded.metaUrl) throw new Error(`parity ./sibling.js: ${viaMeta} !== ${loaded.metaUrl}`); 243console.log('http-parity'); 244"# , 245); 246 http. source ( 247"nested/sibling.js" , 248"export const metaUrl = import.meta.url;\n" , 249); 250let dir = tempfile:: tempdir (). unwrap (); 251let output =run ( 252 dir. path (), 253& http. name ( "start" ), 254& [ "--cache-dir=cache" . into ()], 255); 256assert_eq! ( output. stdout , b"http-parity\n" ); 257} 258 259# [ test ] 260fn resolve_matches_loaded_module_for_jsr_direct_and_binding () { 261let registry = registry_fixture:: Registry :: new (); 262 registry. package ( 263"@test/parity" , 264"1.0.0" , 265& [ 266( "mod.ts" , "export const metaUrl = import.meta.url;" ), 267( "sub.ts" , "export const metaUrl = import.meta.url;" ), 268], 269& [( "." , "./mod.ts" ), ( "./sub" , "./sub.ts" )], 270); 271let dir = tempfile:: tempdir (). unwrap (); 272 fs:: write ( 273 dir. path (). join ( "avril.conf" ), 274"deps: ['jsr:@test/parity@^1']" , 275) 276. unwrap (); 277 fs:: write ( dir. path (). join ( "main.ts" ), r#" 278async function check(spec) { 279const loaded = await import(spec); 280const viaMeta = import.meta.resolve(spec); 281if (viaMeta !== loaded.metaUrl) throw new Error(`parity ${spec}: ${viaMeta} !== ${loaded.metaUrl}`); 282} 283await check('jsr:@test/parity@^1'); 284await check('jsr:@test/parity@^1/sub'); 285await check('@test/parity'); 286await check('@test/parity/sub'); 287console.log('jsr-parity'); 288"# ). unwrap (); 289let options = registry. options ( dir. path ()); 290let output =run ( 291 dir. path (), 292"main.ts" , 293& [ 294format! ( "--jsr-registry={}" , options. jsr_registry ), 295format! ( "--cache-dir={}" , options. cache . display ()), 296], 297); 298assert_eq! ( output. stdout , b"jsr-parity\n" ); 299} 300 301# [ test ] 302fn resolve_matches_loaded_module_for_npm_direct_binding_and_owned_referrer () { 303let registry = registry_fixture:: Registry :: new (); 304 registry. publish_npm ( 305"metadep" , 306"1.0.0" , 307& [ 308( 309"package.json" , 310r#"{"name": "metadep", "version": "1.0.0", "type": "module", "exports": {".": "./index.js"}}"# , 311), 312( "index.js" , "export const metaUrl = import.meta.url;\n" ), 313], 314); 315 registry. publish_npm ( 316"metaparity" , 317"1.0.0" , 318& [ 319( 320"package.json" , 321r#"{"name": "metaparity", "version": "1.0.0", "type": "module", "exports": {".": "./index.js", "./sibling": "./sibling.js"}, "dependencies": {"metadep": "^1.0.0"}}"# , 322), 323( 324"index.js" , 325r#"export const metaUrl = import.meta.url; 326export async function checkOwned() { 327const viaSibling = import.meta.resolve('./sibling.js'); 328const sibling = await import('./sibling.js'); 329if (viaSibling !== sibling.metaUrl) throw new Error(`owned sibling parity: ${viaSibling} !== ${sibling.metaUrl}`); 330const viaDep = import.meta.resolve('metadep'); 331const dep = await import('metadep'); 332if (viaDep !== dep.metaUrl) throw new Error(`owned dep parity: ${viaDep} !== ${dep.metaUrl}`); 333} 334"# , 335), 336( "sibling.js" , "export const metaUrl = import.meta.url;\n" ), 337], 338); 339let dir = tempfile:: tempdir (). unwrap (); 340 fs:: write ( dir. path (). join ( "avril.conf" ), "deps: ['npm:metaparity@^1']" ). unwrap (); 341 fs:: write ( dir. path (). join ( "main.js" ), r#" 342async function check(spec) { 343const loaded = await import(spec); 344const viaMeta = import.meta.resolve(spec); 345if (!viaMeta.startsWith('file://')) throw new Error(`npm identity is not a file URL: ${spec} -> ${viaMeta}`); 346if (viaMeta !== loaded.metaUrl) throw new Error(`parity ${spec}: ${viaMeta} !== ${loaded.metaUrl}`); 347} 348await check('npm:metaparity@^1'); 349await check('npm:metaparity@^1/sibling'); 350await check('metaparity'); 351await check('metaparity/sibling'); 352await (await import('npm:metaparity@^1')).checkOwned(); 353console.log('npm-parity'); 354"# ). unwrap (); 355let options = registry. options ( dir. path ()); 356let output =run ( 357 dir. path (), 358"main.js" , 359& [ 360format! ( "--npm-registry={}" , options. npm_registry ), 361format! ( "--cache-dir={}" , options. cache . display ()), 362], 363); 364assert_eq! ( output. stdout , b"npm-parity\n" ); 365} 366 367# [ test ] 368fn resolve_matches_loaded_avril_builtin () { 369let dir = tempfile:: tempdir (). unwrap (); 370 fs:: write ( 371 dir. path (). join ( "main.js" ), 372r#" 373if (import.meta.resolve('avril:std') !== 'avril:std') throw new Error('builtin identity'); 374const std = await import('avril:std'); 375if (typeof std.pledge !== 'function') throw new Error('builtin loads'); 376console.log('builtin-parity'); 377"# , 378) 379. unwrap (); 380assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"builtin-parity\n" ); 381} 382 383# [ test ] 384fn resolve_and_import_agree_on_ext_and_node_rejections () { 385let dir = tempfile:: tempdir (). unwrap (); 386 fs:: write ( dir. path (). join ( "main.js" ), r#" 387for (const spec of ['node:fs', 'ext:core/ops']) { 388let metaError = null; 389try { import.meta.resolve(spec); } catch (error) { metaError = error; } 390if (!(metaError instanceof TypeError)) throw new Error(`resolve should reject ${spec}`); 391let importError = null; 392try { await import(spec); } catch (error) { importError = error; } 393if (!importError) throw new Error(`import should reject ${spec}`); 394const want = spec.startsWith('node:') ? 'not supported' : 'private'; 395if (!String(metaError.message).includes(want) || !String(importError.message ?? importError).includes(want)) { 396throw new Error(`message class for ${spec}: ${metaError.message} / ${importError.message ?? importError}`); 397} 398} 399console.log('rejections'); 400"# ). unwrap (); 401assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"rejections\n" ); 402} 403 404# [ test ] 405fn workers_get_their_own_file_metadata_and_bound_resolver () { 406let dir = tempfile:: tempdir (). unwrap (); 407 fs:: write ( dir. path (). join ( "worker.js" ), r#" 408postMessage([import.meta.filename, import.meta.dirname, import.meta.resolve('./missing.js'), import.meta.main]); close(); 409"# ). unwrap (); 410 fs:: write ( dir. path (). join ( "main.js" ), r#" 411const worker = new Worker(new URL('./worker.js',import.meta.url),{type:'module'}); 412const data = await new Promise((resolve,reject) => {worker.onmessage = e => resolve(e.data); worker.onerror = reject;}); 413if (data[0] !== import.meta.dirname + '/worker.js' || data[1] !== import.meta.dirname || data[2] !== import.meta.resolve('./missing.js') || data[3] !== true) throw Error('Worker metadata'); 414console.log('worker'); 415"# ). unwrap (); 416assert_eq! ( run ( dir. path (), "main.js" , & []). stdout , b"worker\n" ); 417}