use std::cell::Cell; use std::fs; use std::io; use std::path::Path; #[path = "../../tests/support/mod.rs"] mod support; use super::{Op, RegisteredOps}; fn ops(seed: u32) -> Vec { let counter = Cell::new(seed); vec![ Op::new("next", move |step: u32| { counter.set(counter.get() + step); Ok::<_, io::Error>(counter.get()) }), Op::with_scope("read", move |_, (): ()| Ok::<_, io::Error>(seed)), Op::asynchronous("asyncRead", move |offset: u32| async move { Ok::<_, io::Error>(seed + offset) }), Op::asynchronous_unreferenced("unreferenced", move |offset: u32| async move { Ok::<_, io::Error>(seed + offset) }), Op::try_raw("fail", move |_, _| { Err(io::Error::other(format!("native {seed}"))) }), Op::raw("echo", |_, args| Some(args.get(0))), Op::raw("invoke", |scope, args| { let callback = v8::Local::::try_from(args.get(0)).ok()?; callback.call(scope, v8::undefined(scope).into(), &[]) }), ] } fn evaluate(scope: &mut v8::PinScope, code: &str) -> T { v8::tc_scope!(let scope, scope); let source = v8::String::new(scope, code).unwrap(); let value = v8::Script::compile(scope, source, None).and_then(|script| script.run(scope)); let value = value.unwrap_or_else(|| { let exception = scope.exception().unwrap(); panic!("{}", exception.to_rust_string_lossy(scope)); }); serde_v8::from_v8(scope, value).unwrap() } fn create_snapshot(path: &Path) { let ops = RegisteredOps::new(ops(700)).unwrap(); let mut isolate = v8::Isolate::snapshot_creator(Some(RegisteredOps::external_references().into()), None); isolate.set_slot(ops.clone()); { v8::scope!(let scope, &mut isolate); let context = v8::Context::new(scope, Default::default()); let scope = &mut v8::ContextScope::new(scope, context); let registry = ops.object(scope).unwrap(); let key = v8::String::new(scope, "ops").unwrap(); context .global(scope) .set(scope, key.into(), registry.into()) .unwrap(); evaluate::<()>( scope, r#" globalThis.saved = (() => { const {next, read, asyncRead, unreferenced, fail, echo, invoke} = ops; const built = [read(null), next(1)]; let calls = 0; return { built, read, run() { const marker = {}; if (echo(marker) !== marker) throw Error('Lost identity'); return [++calls, read(null), invoke(() => next(2))]; }, async runAsync() { return [await asyncRead(3), await unreferenced(4)]; }, errors() { const errors = []; try { fail(); } catch (error) { errors.push(error.message); } try { next('invalid'); } catch (error) { errors.push(error instanceof TypeError); } try { new echo(); } catch (error) { errors.push(error instanceof TypeError); } return errors; }, }; })(); delete globalThis.ops; "#, ); assert_eq!(evaluate::>(scope, "saved.run()"), [1, 700, 703]); scope.set_default_context(context); } let snapshot = isolate.create_blob(v8::FunctionCodeHandling::Keep).unwrap(); assert!(!snapshot.is_empty()); fs::write(path, &*snapshot).unwrap(); } fn restore_snapshot(path: &Path, seed: u32) { let mut fresh_ops = ops(seed); fresh_ops.reverse(); let ops = RegisteredOps::new(fresh_ops).unwrap(); let params = v8::Isolate::create_params() .snapshot_blob(fs::read(path).unwrap().into()) .external_references(RegisteredOps::external_references().into()); let mut isolate = v8::Isolate::new(params); isolate.set_microtasks_policy(v8::MicrotasksPolicy::Explicit); super::init(&mut isolate); crate::reporting::init(&mut isolate); crate::microtasks::init(&mut isolate); crate::microtasks::add_listener(&mut isolate); crate::tasks::init(&mut isolate); v8::scope!(let scope, &mut isolate); let context = v8::Context::new(scope, Default::default()); let scope = &mut v8::ContextScope::new(scope, context); // Neither the registry object nor the initialized JS is recreated after loading. assert_eq!(evaluate::(scope, "typeof ops"), "undefined"); assert_eq!(evaluate::>(scope, "saved.built"), [700, 701]); assert_eq!( evaluate::( scope, "try { saved.read(null); } catch (error) { error.message; }" ), "Op is not registered in this isolate", ); scope.set_slot(ops); assert_eq!( evaluate::>(scope, "saved.run()"), [2, seed, seed + 2] ); scope.low_memory_notification(); assert_eq!( evaluate::>(scope, "saved.run()"), [3, seed, seed + 4] ); assert_eq!( evaluate::(scope, "saved.errors()"), serde_json::json!([format!("native {seed}"), true, true]), ); evaluate::<()>( scope, "saved.runAsync().then(value => globalThis.asyncResult = value); undefined", ); assert!(super::run_event_loop(scope, None, true).is_some()); assert_eq!( evaluate::>(scope, "asyncResult"), [seed + 3, seed + 4] ); } #[test] fn snapshot_round_trip_across_processes() { if let Some(mode) = support::role() { crate::initialize_v8(crate::Process::Runtime); let path = support::directory().join("ops.snapshot"); match mode.as_str() { "create" => create_snapshot(&path), "restore" => { restore_snapshot(&path, 10); restore_snapshot(&path, 100); } _ => panic!("Unknown snapshot test mode: {mode}"), } return; } support::roles( "op::tests::snapshot_round_trip_across_processes", &["create", "restore"], ); }