char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
d71572b
main
1mod support; 2 3use std:: cell::{ Cell , RefCell }; 4use std:: io; 5use std:: rc:: Rc ; 6 7use avril_core::{ 8Error , Extension , ModuleLoader , ModuleSource , Op , Runtime , RuntimeOptions , Snapshot , 9SnapshotError , WasmStreamingRegistry , v8, 10}; 11 12struct Loader ( u32 ); 13 14impl ModuleLoader for Loader { 15fn resolve ( & self , specifier : & str , _: & str ) ->Result < String , Error > { 16Ok ( if specifier =="private-alias" { 17"ext:test/state.js" 18} else { 19 specifier 20} 21. into ()) 22} 23 24fn load ( & self , name : & str ) ->Result < ModuleSource , Error > { 25match name{ 26"guest:main" =>Ok ( include_str! ( "snapshot/guest.js" ). into ()), 27"guest:dep" =>Ok ( format! ( 28"export const seed = {}; export const url = import.meta.url;" , 29self . 0 30) 31. into ()), 32 _ =>Err ( Error :: Module ( format! ( "Missing {name}" ))), 33} 34} 35 36fn module_url ( & self , name : & str ) ->Option < String > { 37Some ( format! ( "file:///snapshot-{}/{name}.js" , self . 0 )) 38} 39 40fn map_source_location ( 41& self , 42name : & str , 43line : u32 , 44column : u32 , 45) ->Option <( String , u32 , u32 )> { 46( name =="guest:stack" ). then ( ||( format! ( "fresh-{}.ts" , self . 0 ), line +10 , column +2 )) 47} 48} 49 50fn options ( seed : u32 , allowed : Rc < Cell < bool >>) ->RuntimeOptions { 51let mut ops =vec! [ 52Op :: new ( "staticOnly" , move |(): () |{ 53assert_eq! ( seed, 0 , "Static entry reexecuted during restoration" ); 54Ok ::< _ , io:: Error >(()) 55}), 56Op :: new ( "nativeSeed" , move |(): () |{ 57assert_ne! ( seed, 0 , "Live initialization ran during snapshot creation" ); 58if !allowed. get () { 59return Err ( io:: Error :: other ( "native access denied" )); 60} 61Ok ::< _ , io:: Error >( seed) 62}), 63Op :: with_scope ( "collect" , |scope, (): () |{ 64 scope. low_memory_notification (); 65Ok ::< _ , io:: Error >(()) 66}), 67Op :: asynchronous ( "asyncSeed" , move |(): () |async move { 68Ok ::< _ , io:: Error >( seed) 69}), 70Op :: try_raw ( "registerWasm" , |scope, args|{ 71let callback = 72 v8:: Local ::< v8:: Function >:: try_from ( args. get ( 0 )). map_err ( io:: Error :: other) ?; 73 scope 74. get_slot ::< Rc < RefCell < WasmStreamingRegistry >>>() 75. unwrap () 76. borrow_mut () 77. register ( v8:: Global :: new ( scope, callback)) ?; 78Ok ::< _ , io:: Error >( v8:: undefined ( scope). into ()) 79}), 80Op :: with_scope ( "finishWasm" , |scope, id : u64 |{ 81let registry = scope 82. get_slot ::< Rc < RefCell < WasmStreamingRegistry >>>() 83. unwrap (); 84let mut registry = registry. borrow_mut (); 85 registry 86. stream_mut ( id) ? 87. on_bytes_received ( b"\0asm\x01\0\0\0" ); 88 registry. finish ( id) 89}), 90]; 91if seed !=0 { 92 ops. reverse (); 93} 94RuntimeOptions { 95extensions : vec! [ Extension { 96name : "ext:test" , 97dependencies : & [], 98 ops, 99modules : & [ 100( "ext:test/main.js" , include_str! ( "snapshot/main.js" )), 101( 102"ext:test/state.js" , 103"export const state = {seed: undefined};" , 104), 105( "ext:test/lazy.js" , include_str! ( "snapshot/lazy.js" )), 106], 107entry_point : Some ( "ext:test/main.js" ), 108}], 109module_loader : Some ( Rc :: new ( Loader ( seed))), 110} 111} 112 113# [ test ] 114fn core_snapshot_cross_process () { 115let path = || support:: directory (). join ( "core.snapshot" ); 116match support:: role (). as_deref () { 117Some ( "create" ) =>{ 118let snapshot = 119Runtime :: create_snapshot ( vec! [ options ( 0 , Rc :: new ( Cell :: new ( true )))]). unwrap (); 120 std:: fs:: write ( path (), snapshot. as_bytes ()). unwrap (); 121} 122Some ( "restore" ) =>{ 123let bytes = std:: fs:: read ( path ()). unwrap (); 124// SAFETY: The sibling create role produced this artifact with this executable. 125let snapshot =unsafe { Snapshot :: from_bytes ( bytes) }. unwrap (); 126for seedin [ 10 , 100 ] { 127let allowed =Rc :: new ( Cell :: new ( true )); 128let mut runtime = 129Runtime :: from_snapshot ( options ( seed, allowed. clone ()), & snapshot). unwrap (); 130 runtime 131. register_extension_module ( "test:main" , "ext:test/main.js" ) 132. unwrap (); 133 runtime 134. register_extension_module ( "test:lazy" , "ext:test/lazy.js" ) 135. unwrap (); 136 runtime. execute_module ( "guest:main" ). unwrap (); 137 runtime 138. execute_script ::<()>( "report events" , "afterTasks()" ) 139. unwrap (); 140assert! ( 141 runtime 142. execute_script ::< bool >( "complete" , "globalThis.complete === true" ) 143. unwrap () 144); 145assert_eq! ( 146 runtime 147. execute_script ::< u32 >( "seed" , "snapshotState.seed" ) 148. unwrap (), 149 seed 150); 151assert_eq! ( 152 runtime 153. execute_script ::< Vec < String >>( "events" , "events" ) 154. unwrap (), 155[ 156"static" , 157"live" , 158"lazy" , 159"task" , 160"finish" , 161"microtask" , 162"rejection" 163] 164); 165let stack: String = runtime 166. execute_script ( "guest:stack" , "new Error('restored stack').stack" ) 167. unwrap (); 168assert! ( stack. contains ( & format! ( "fresh-{seed}.ts:11:" )), "{stack}" ); 169 runtime 170. execute_script ::<()>( "handled" , "rejected.catch(() => {});" ) 171. unwrap (); 172assert! ( 173 runtime 174. execute_script ::< bool >( "handled event" , "events.at(-1) === 'handled'" ) 175. unwrap () 176); 177 allowed. set ( false ); 178assert! ( 179matches! ( runtime. execute_script ::< u32 >( "denied" , "readNative()" ), Err ( Error :: JavaScript ( message)) if message. contains ( "native access denied" )) 180); 181} 182} 183 _ => support:: roles ( "core_snapshot_cross_process" , & [ "create" , "restore" ]), 184} 185} 186 187# [ test ] 188fn snapshot_layout_mismatches_are_rejected_before_live_initialization () { 189if support:: role (). is_none () { 190return support:: roles ( 191"snapshot_layout_mismatches_are_rejected_before_live_initialization" , 192& [ "run" ], 193); 194} 195let snapshot =Runtime :: create_snapshot ( vec! [ options ( 0 , Rc :: new ( Cell :: new ( true )))]). unwrap (); 196for changein 0 ..3 { 197let mut options =options ( 10 , Rc :: new ( Cell :: new ( false ))); 198match change{ 1990 => options. extensions [ 0 ]. ops [ 0 ]. name ="renamed" , 2001 =>{ 201 options. extensions [ 0 ]. modules = 202& [( "ext:test/main.js" , "throw Error('changed source');" )] 203} 2042 => options. extensions [ 0 ]. entry_point =None , 205 _ =>unreachable! (), 206} 207assert! ( matches! ( 208Runtime :: from_snapshot ( options, & snapshot), 209Err ( Error :: Snapshot ( SnapshotError :: IncompatibleLayout )) 210)); 211} 212} 213 214# [ test ] 215fn restored_live_failure_does_not_run_later_initializers () { 216if support:: role (). is_none () { 217return support:: roles ( 218"restored_live_failure_does_not_run_later_initializers" , 219& [ "run" ], 220); 221} 222let called =Rc :: new ( Cell :: new ( false )); 223let options = ||RuntimeOptions { 224extensions : vec! [ Extension { 225name : "ext:test" , 226dependencies : & [], 227ops : vec! [ 228Op :: new ( "fail" , |(): () |{ 229Err ::<(), _ >( io:: Error :: other ( "failed live setup" )) 230}), 231Op :: new ( "later" , { 232let called = called. clone (); 233move |(): () |{ 234 called. set ( true ); 235Ok ::< _ , io:: Error >(()) 236} 237}), 238], 239modules : & [( 240"ext:test/main.js" , 241r#" 242import {onRuntimeStart} from 'ext:core/bootstrap'; 243import {fail, later} from 'ext:core/ops'; 244onRuntimeStart(() => { fail(); }); 245onRuntimeStart(() => { later(); }); 246"# , 247)], 248entry_point : Some ( "ext:test/main.js" ), 249}], 250 ..Default :: default () 251}; 252let snapshot =Runtime :: create_snapshot ( vec! [ options ()]). unwrap (); 253assert! ( matches! ( 254Runtime :: from_snapshot ( options (), & snapshot), 255Err ( Error :: JavaScript ( message)) if message. contains ( "failed live setup" ) 256)); 257assert! ( !called. get ()); 258} 259 260struct DropProbe ( Rc < Cell < bool >>); 261 262impl Drop for DropProbe { 263fn drop ( & mut self ) { 264self . 0 . set ( true ); 265} 266} 267 268# [ test ] 269fn abandoned_snapshot_builders_release_native_state_and_return_errors () { 270if support:: role (). is_none () { 271return support:: roles ( 272"abandoned_snapshot_builders_release_native_state_and_return_errors" , 273& [ "run" ], 274); 275} 276let cases: & [ & [( & str , & str )]] =& [ 277& [( "ext:test/main.js" , "throw Error('failed static setup');" )], 278& [( 279"ext:test/main.js" , 280"import {pending} from 'ext:core/ops'; pending();" , 281)], 282& [( 283"ext:test/main.js" , 284"import {setEventHandlers} from 'ext:core/event_hooks'; setEventHandlers(() => true, () => true, () => {});" , 285)], 286]; 287for ( index, modules) in cases. iter (). enumerate () { 288let dropped =Rc :: new ( Cell :: new ( false )); 289let probe =DropProbe ( dropped. clone ()); 290let result =Runtime :: create_snapshot ( vec! [ RuntimeOptions { 291extensions : vec! [ Extension { 292name : "ext:test" , 293dependencies : & [], 294ops : vec! [ 295Op :: new ( "retain" , move |(): () |{ 296let _retain =& probe; 297Ok ::< _ , io:: Error >(()) 298}), 299Op :: asynchronous_unreferenced ( "pending" , |(): () |{ 300 futures:: future:: pending ::< Result <(), io:: Error >>() 301}), 302], 303 modules, 304entry_point : Some ( "ext:test/main.js" ), 305}], 306 ..Default :: default () 307}]); 308if index ==0 { 309assert! ( 310matches! ( result, Err ( Error :: JavaScript ( message)) if message. contains ( "failed static setup" )) 311); 312} else { 313assert! ( matches! ( 314 result, 315Err ( Error :: Snapshot ( SnapshotError :: ActiveState ( _))) 316)); 317} 318assert! ( dropped. get (), "Native op state leaked" ); 319} 320}