char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsnapshot: capture and restore extension layouts in one artifactd71572b

main
6.2 KiB172 linesraw
1use std::cell::Cell;
2use std::fs;
3use std::io;
4use std::path::Path;
5
6#[path = "../../tests/support/mod.rs"]
7mod support;
8
9use super::{Op, RegisteredOps};
10
11fn ops(seed: u32) -> Vec<Op> {
12    let counter = Cell::new(seed);
13    vec![
14        Op::new("next", move |step: u32| {
15            counter.set(counter.get() + step);
16            Ok::<_, io::Error>(counter.get())
17        }),
18        Op::with_scope("read", move |_, (): ()| Ok::<_, io::Error>(seed)),
19        Op::asynchronous("asyncRead", move |offset: u32| async move {
20            Ok::<_, io::Error>(seed + offset)
21        }),
22        Op::asynchronous_unreferenced("unreferenced", move |offset: u32| async move {
23            Ok::<_, io::Error>(seed + offset)
24        }),
25        Op::try_raw("fail", move |_, _| {
26            Err(io::Error::other(format!("native {seed}")))
27        }),
28        Op::raw("echo", |_, args| Some(args.get(0))),
29        Op::raw("invoke", |scope, args| {
30            let callback = v8::Local::<v8::Function>::try_from(args.get(0)).ok()?;
31            callback.call(scope, v8::undefined(scope).into(), &[])
32        }),
33    ]
34}
35
36fn evaluate<T: serde::de::DeserializeOwned>(scope: &mut v8::PinScope, code: &str) -> T {
37    v8::tc_scope!(let scope, scope);
38    let source = v8::String::new(scope, code).unwrap();
39    let value = v8::Script::compile(scope, source, None).and_then(|script| script.run(scope));
40    let value = value.unwrap_or_else(|| {
41        let exception = scope.exception().unwrap();
42        panic!("{}", exception.to_rust_string_lossy(scope));
43    });
44    serde_v8::from_v8(scope, value).unwrap()
45}
46
47fn create_snapshot(path: &Path) {
48    let ops = RegisteredOps::new(ops(700)).unwrap();
49    let mut isolate =
50        v8::Isolate::snapshot_creator(Some(RegisteredOps::external_references().into()), None);
51    isolate.set_slot(ops.clone());
52    {
53        v8::scope!(let scope, &mut isolate);
54        let context = v8::Context::new(scope, Default::default());
55        let scope = &mut v8::ContextScope::new(scope, context);
56        let registry = ops.object(scope).unwrap();
57        let key = v8::String::new(scope, "ops").unwrap();
58        context
59            .global(scope)
60            .set(scope, key.into(), registry.into())
61            .unwrap();
62        evaluate::<()>(
63            scope,
64            r#"
65            globalThis.saved = (() => {
66                const {next, read, asyncRead, unreferenced, fail, echo, invoke} = ops;
67                const built = [read(null), next(1)];
68                let calls = 0;
69                return {
70                    built, read,
71                    run() {
72                        const marker = {};
73                        if (echo(marker) !== marker) throw Error('Lost identity');
74                        return [++calls, read(null), invoke(() => next(2))];
75                    },
76                    async runAsync() {
77                        return [await asyncRead(3), await unreferenced(4)];
78                    },
79                    errors() {
80                        const errors = [];
81                        try { fail(); } catch (error) { errors.push(error.message); }
82                        try { next('invalid'); } catch (error) { errors.push(error instanceof TypeError); }
83                        try { new echo(); } catch (error) { errors.push(error instanceof TypeError); }
84                        return errors;
85                    },
86                };
87            })();
88            delete globalThis.ops;
89        "#,
90        );
91        assert_eq!(evaluate::<Vec<u32>>(scope, "saved.run()"), [1, 700, 703]);
92        scope.set_default_context(context);
93    }
94    let snapshot = isolate.create_blob(v8::FunctionCodeHandling::Keep).unwrap();
95    assert!(!snapshot.is_empty());
96    fs::write(path, &*snapshot).unwrap();
97}
98
99fn restore_snapshot(path: &Path, seed: u32) {
100    let mut fresh_ops = ops(seed);
101    fresh_ops.reverse();
102    let ops = RegisteredOps::new(fresh_ops).unwrap();
103    let params = v8::Isolate::create_params()
104        .snapshot_blob(fs::read(path).unwrap().into())
105        .external_references(RegisteredOps::external_references().into());
106    let mut isolate = v8::Isolate::new(params);
107    isolate.set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
108    super::init(&mut isolate);
109    crate::reporting::init(&mut isolate);
110    crate::microtasks::init(&mut isolate);
111    crate::microtasks::add_listener(&mut isolate);
112    crate::tasks::init(&mut isolate);
113    v8::scope!(let scope, &mut isolate);
114    let context = v8::Context::new(scope, Default::default());
115    let scope = &mut v8::ContextScope::new(scope, context);
116
117    // Neither the registry object nor the initialized JS is recreated after loading.
118    assert_eq!(evaluate::<String>(scope, "typeof ops"), "undefined");
119    assert_eq!(evaluate::<Vec<u32>>(scope, "saved.built"), [700, 701]);
120    assert_eq!(
121        evaluate::<String>(
122            scope,
123            "try { saved.read(null); } catch (error) { error.message; }"
124        ),
125        "Op is not registered in this isolate",
126    );
127    scope.set_slot(ops);
128    assert_eq!(
129        evaluate::<Vec<u32>>(scope, "saved.run()"),
130        [2, seed, seed + 2]
131    );
132    scope.low_memory_notification();
133    assert_eq!(
134        evaluate::<Vec<u32>>(scope, "saved.run()"),
135        [3, seed, seed + 4]
136    );
137    assert_eq!(
138        evaluate::<serde_json::Value>(scope, "saved.errors()"),
139        serde_json::json!([format!("native {seed}"), true, true]),
140    );
141    evaluate::<()>(
142        scope,
143        "saved.runAsync().then(value => globalThis.asyncResult = value); undefined",
144    );
145    assert!(super::run_event_loop(scope, None, true).is_some());
146    assert_eq!(
147        evaluate::<Vec<u32>>(scope, "asyncResult"),
148        [seed + 3, seed + 4]
149    );
150}
151
152#[test]
153fn snapshot_round_trip_across_processes() {
154    if let Some(mode) = support::role() {
155        crate::initialize_v8(crate::Process::Runtime);
156        let path = support::directory().join("ops.snapshot");
157        match mode.as_str() {
158            "create" => create_snapshot(&path),
159            "restore" => {
160                restore_snapshot(&path, 10);
161                restore_snapshot(&path, 100);
162            }
163            _ => panic!("Unknown snapshot test mode: {mode}"),
164        }
165        return;
166    }
167
168    support::roles(
169        "op::tests::snapshot_round_trip_across_processes",
170        &["create", "restore"],
171    );
172}