char/avril

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

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

Charlotte Somcore: add dependencies list to Extension81eaeb3

main
5.3 KiB161 linesraw
1use std::cell::Cell;
2use std::convert::Infallible;
3use std::rc::Rc;
4
5use avril_core::{Extension, Op, Runtime, RuntimeOptions, v8};
6
7fn runtime() -> Runtime {
8    Runtime::new(RuntimeOptions {
9        extensions: vec![Extension {
10            name: "ext:test",
11            dependencies: &[],
12            ops: vec![],
13            modules: &[(
14                "ext:test/main.js",
15                "import {markPromiseHandled} from 'ext:core/promises'; globalThis.markPromiseHandled = markPromiseHandled;",
16            )],
17            entry_point: Some("ext:test/main.js"),
18        }],
19        ..Default::default()
20    })
21    .unwrap()
22}
23
24#[test]
25fn marking_rejected_promises_clears_tracking_without_changing_their_results() {
26    let mut runtime = runtime();
27    runtime
28        .execute_script::<()>(
29            "rejected",
30            r#"
31            globalThis.reason = {failure: true};
32            globalThis.promise = Promise.reject(reason);
33            if (markPromiseHandled(promise) !== undefined) throw Error('Unexpected result');
34            markPromiseHandled(promise);
35            "#,
36        )
37        .unwrap();
38    runtime
39        .execute_script::<()>(
40            "observe",
41            "globalThis.observed = false; promise.catch(error => observed = error === reason);",
42        )
43        .unwrap();
44    assert!(
45        runtime
46            .execute_script::<bool>("result", "observed")
47            .unwrap()
48    );
49    runtime.run_event_loop().unwrap();
50}
51
52#[test]
53fn marking_pending_promises_suppresses_later_rejections_without_property_access() {
54    let mut runtime = runtime();
55    runtime
56        .execute_script::<()>(
57            "pending",
58            r#"
59            let reject;
60            const promise = new Promise((resolve, failure) => reject = failure);
61            const poison = () => {throw Error('Inspected promise property');};
62            Object.defineProperties(promise, {
63                constructor: {get: poison},
64                then: {get: poison}
65            });
66            markPromiseHandled(promise);
67            Promise.resolve().then(() => reject('later failure'));
68            "#,
69        )
70        .unwrap();
71    runtime.run_event_loop().unwrap();
72}
73
74#[test]
75fn marking_one_promise_does_not_suppress_unrelated_rejections() {
76    let mut runtime = runtime();
77    let error = runtime
78        .execute_script::<()>(
79            "unrelated",
80            "Promise.reject('unrelated failure'); markPromiseHandled(Promise.reject('suppressed failure'));",
81        )
82        .unwrap_err()
83        .to_string();
84    assert!(error.contains("unrelated failure"), "{error}");
85    runtime.run_event_loop().unwrap();
86}
87
88#[test]
89fn marking_promises_does_not_allocate_derived_promises() {
90    extern "C" fn created(
91        event: v8::PromiseHookType,
92        promise: v8::Local<v8::Promise>,
93        _parent: v8::Local<v8::Value>,
94    ) {
95        if event == v8::PromiseHookType::Init {
96            // SAFETY: V8 supplies a valid promise belonging to the active isolate.
97            v8::callback_scope!(unsafe scope, promise);
98            let count = scope.get_slot::<Rc<Cell<u32>>>().unwrap();
99            count.set(count.get() + 1);
100        }
101    }
102
103    let count = Rc::new(Cell::new(0_u32));
104    let counter = count.clone();
105    let mut runtime = Runtime::new(RuntimeOptions {
106        extensions: vec![Extension {
107            name: "ext:test",
108            dependencies: &[],
109            ops: vec![Op::with_scope("count_promises", move |scope, (): ()| {
110                scope.set_slot(counter.clone());
111                scope.set_promise_hook(created);
112                Ok::<_, Infallible>(())
113            })],
114            modules: &[(
115                "ext:test/main.js",
116                "import {markPromiseHandled} from 'ext:core/promises'; import {count_promises} from 'ext:core/ops'; globalThis.markPromiseHandled = markPromiseHandled; globalThis.countPromises = count_promises;",
117            )],
118            entry_point: Some("ext:test/main.js"),
119        }],
120        ..Default::default()
121    })
122    .unwrap();
123    runtime
124        .execute_script::<()>(
125            "allocations",
126            r#"
127            countPromises(null);
128            const promises = [new Promise(() => {}), Promise.resolve(), Promise.reject('handled')];
129            for (const promise of promises) {
130                markPromiseHandled(promise);
131                markPromiseHandled(promise);
132            }
133            "#,
134        )
135        .unwrap();
136    assert_eq!(count.get(), 3);
137}
138
139#[test]
140fn native_promise_handling_accepts_subclasses_but_rejects_impostors() {
141    let mut runtime = runtime();
142    runtime
143        .execute_script::<()>(
144            "brands",
145            r#"
146            class Derived extends Promise {}
147            markPromiseHandled(Derived.resolve());
148            markPromiseHandled(Derived.reject('subclass failure'));
149            for (const value of [
150                undefined, null, 42, {}, {then() {}},
151                Object.create(Promise.prototype), new Proxy(Promise.resolve(), {})
152            ]) {
153                try {markPromiseHandled(value); throw Error('Accepted impostor');}
154                catch (error) {if (!(error instanceof TypeError)) throw error;}
155            }
156            try {new markPromiseHandled(Promise.resolve()); throw Error('Constructible helper');}
157            catch (error) {if (!(error instanceof TypeError)) throw error;}
158            "#,
159        )
160        .unwrap();
161}