char/avril

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

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

Charlotte Sommass: run cargo fmt05d50bd

main
4.7 KiB141 linesraw
1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::rc::Rc;
4use std::task::Poll;
5
6use avril_core::{Error, ModuleLoader, ModuleSource, Runtime, RuntimeOptions};
7use futures::future::LocalBoxFuture;
8
9struct AcquiringLoader {
10    sources: HashMap<String, ModuleSource>,
11    ready: Rc<RefCell<HashSet<String>>>,
12}
13
14impl ModuleLoader for AcquiringLoader {
15    fn resolve(&self, specifier: &str, _: &str) -> Result<String, Error> {
16        Ok(specifier.trim_start_matches("./").to_owned())
17    }
18
19    fn load(&self, name: &str) -> Result<ModuleSource, Error> {
20        if !self.ready.borrow().contains(name) {
21            return Err(Error::Module(
22                "V8 linked before acquisition completed".into(),
23            ));
24        }
25        self.sources
26            .get(name)
27            .cloned()
28            .ok_or_else(|| Error::Module("Missing source".into()))
29    }
30
31    fn prepare(
32        &self,
33        specifier: String,
34        referrer: String,
35    ) -> LocalBoxFuture<'static, Result<String, Error>> {
36        let name = if referrer.is_empty() {
37            specifier
38        } else {
39            self.resolve(&specifier, &referrer).unwrap()
40        };
41        let ready = self.ready.clone();
42        let mut yielded = false;
43        Box::pin(futures::future::poll_fn(move |cx| {
44            if !yielded {
45                yielded = true;
46                cx.waker().wake_by_ref();
47                return Poll::Pending;
48            }
49            if name == "missing.js" {
50                return Poll::Ready(Err(Error::Module("Acquisition failed".into())));
51            }
52            ready.borrow_mut().insert(name.clone());
53            if name == "dep.js" {
54                ready.borrow_mut().insert("static.js".into());
55            }
56            Poll::Ready(Ok(name.clone()))
57        }))
58    }
59}
60
61#[test]
62fn asynchronous_preparation_precedes_linking_for_entries_and_computed_imports() {
63    let sources = [
64        (
65            "main.js",
66            r#"
67            const name = './' + 'dep.js';
68            const [a, b] = await Promise.all([import(name), import(name)]);
69            globalThis.result = [a === b, a.value, a.count, a.main];
70            try { await import('./missing.js'); } catch(e) { globalThis.failure = e.message; }
71            globalThis.again = (await import(name)) === a;
72        "#,
73        ),
74        (
75            "dep.js",
76            r#"
77            import {value} from './static.js';
78            export {value};
79            export const count = globalThis.count = (globalThis.count ?? 0) + 1;
80            export const main = import.meta.main;
81        "#,
82        ),
83        ("static.js", "export const value = 42;"),
84    ]
85    .into_iter()
86    .map(|(name, source)| (name.into(), source.into()))
87    .collect();
88    let mut runtime = Runtime::new(RuntimeOptions {
89        module_loader: Some(Rc::new(AcquiringLoader { sources, ready: Rc::default() })),
90        ..Default::default()
91    })
92    .unwrap();
93    runtime.execute_module("main.js").unwrap();
94    let result: (bool, u32, u32, bool) = runtime.execute_script("result", "result").unwrap();
95    assert_eq!(result, (true, 42, 1, false));
96    assert_eq!(
97        runtime
98            .execute_script::<String>("failure", "failure")
99            .unwrap(),
100        "Acquisition failed"
101    );
102    assert!(runtime.execute_script::<bool>("again", "again").unwrap());
103}
104
105#[test]
106fn started_modules_allow_host_progress_during_top_level_await() {
107    for reject in [false, true] {
108        let source = format!(
109            "await new Promise(resolve => globalThis.resume = resolve); {}",
110            if reject {
111                "throw Error('entry rejection');"
112            } else {
113                "globalThis.result = 42;"
114            }
115        );
116        let mut runtime = Runtime::new(RuntimeOptions {
117            module_loader: Some(Rc::new(AcquiringLoader {
118                sources: HashMap::from([("main.js".into(), source.into())]),
119                ready: Rc::default(),
120            })),
121            ..Default::default()
122        })
123        .unwrap();
124        runtime.start_module("main.js").unwrap();
125        assert!(runtime.has_pending_work());
126        assert!(runtime.start_module("main.js").is_err());
127        runtime.execute_script::<()>("resume", "resume()").unwrap();
128        let mut context = std::task::Context::from_waker(futures::task::noop_waker_ref());
129        let polled = runtime.poll_event_loop(&mut context);
130        if reject {
131            assert!(polled.unwrap_err().to_string().contains("entry rejection"));
132        } else {
133            polled.unwrap();
134            assert_eq!(
135                runtime.execute_script::<u32>("result", "result").unwrap(),
136                42
137            );
138        }
139        assert!(!runtime.has_pending_work());
140    }
141}