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
6.7 KiB189 linesraw
1use avril_core::{Extension, Runtime, RuntimeOptions, v8};
2
3fn collect_garbage(
4    scope: &mut v8::PinScope,
5    _args: v8::FunctionCallbackArguments,
6    _result: v8::ReturnValue,
7) {
8    scope.low_memory_notification();
9}
10
11fn runtime() -> Runtime {
12    Runtime::new(RuntimeOptions {
13        extensions: vec![Extension {
14            name: "ext:test",
15            dependencies: &[],
16            ops: vec![],
17            modules: &[(
18                "ext:test/main.js",
19                r#"
20                import {setEventHandlers} from 'ext:core/event_hooks';
21                import {enqueueMicrotask} from 'ext:core/microtasks';
22                globalThis.testQueueMicrotask = enqueueMicrotask;
23                globalThis.reported = [];
24                globalThis.cancelErrors = true;
25                globalThis.captureRejections = true;
26                globalThis.cancelRejections = true;
27                globalThis.catchDuringUnhandled = false;
28                setEventHandlers(
29                    (error, filename, line, column) => {
30                        reported.push(['error', error, filename, line, column]);
31                        return cancelErrors;
32                    },
33                    (promise, reason) => {
34                        if (captureRejections) reported.push(['unhandled', promise, reason]);
35                        if (catchDuringUnhandled) {
36                            catchDuringUnhandled = false;
37                            promise.catch(() => {});
38                        }
39                        return cancelRejections;
40                    },
41                    (promise, reason) => reported.push(['handled', promise, reason]),
42                );
43                "#,
44            )],
45            entry_point: Some("ext:test/main.js"),
46        }],
47        ..Default::default()
48    })
49    .unwrap()
50}
51
52#[test]
53fn native_event_hooks_report_exceptions_and_late_rejections() {
54    let mut runtime = runtime();
55    assert!(
56        runtime
57            .execute_script::<()>("direct.js", "throw 'direct exception';")
58            .is_err()
59    );
60    assert!(runtime
61        .execute_script::<bool>(
62            "direct-reported.js",
63            "reported.length === 1 && reported[0][0] === 'error' && reported[0][1] === 'direct exception'",
64        )
65        .unwrap());
66
67    runtime
68        .execute_script::<()>(
69            "same-turn.js",
70            r#"
71            const handledBeforeReport = Promise.reject('same turn');
72            Promise.resolve().then(() => handledBeforeReport.catch(() => {}));
73            testQueueMicrotask(() => {throw 'reported exception';});
74            testQueueMicrotask(() => {throw 'second reported exception';});
75            "#,
76        )
77        .unwrap();
78    assert!(
79        runtime
80            .execute_script::<bool>(
81                "first.js",
82                "reported.length === 3 && reported[1][0] === 'error' && reported[1][1] === 'reported exception' && reported[2][1] === 'second reported exception'",
83            )
84            .unwrap()
85    );
86
87    let error = runtime
88        .execute_script::<()>(
89            "fatal-errors.js",
90            "cancelErrors = false; testQueueMicrotask(() => {throw 'fatal first';}); testQueueMicrotask(() => {throw 'fatal second';});",
91        )
92        .unwrap_err();
93    assert!(error.to_string().contains("fatal first"), "{error}");
94    assert!(runtime
95        .execute_script::<bool>(
96            "fatal-errors-reported.js",
97            "reported.length === 5 && reported[3][1] === 'fatal first' && reported[4][1] === 'fatal second'",
98        )
99        .unwrap());
100    runtime
101        .execute_script::<()>("enable-cancellation.js", "cancelErrors = true;")
102        .unwrap();
103
104    runtime
105        .execute_script::<()>(
106            "caught-in-unhandled-listener.js",
107            "catchDuringUnhandled = true; Promise.reject('caught in listener');",
108        )
109        .unwrap();
110    assert!(runtime
111        .execute_script::<bool>(
112            "caught-in-unhandled-listener-reported.js",
113            "reported.length === 6 && reported[5][0] === 'unhandled' && reported.filter(event => event[0] === 'handled').length === 0",
114        )
115        .unwrap());
116
117    runtime
118        .execute_script::<()>(
119            "late.js",
120            "globalThis.reason = {}; globalThis.promise = Promise.reject(reason);",
121        )
122        .unwrap();
123    assert!(
124        runtime
125            .execute_script::<bool>(
126                "unhandled.js",
127                "reported.length === 7 && reported[6][0] === 'unhandled' && reported[6][1] === promise && reported[6][2] === reason",
128            )
129            .unwrap()
130    );
131
132    runtime
133        .execute_script::<()>("catch.js", "promise.catch(() => {});")
134        .unwrap();
135    assert!(
136        runtime
137            .execute_script::<bool>(
138                "handled.js",
139                "reported.length === 8 && reported[7][0] === 'handled' && reported[7][1] === promise && reported[7][2] === reason",
140            )
141            .unwrap()
142    );
143
144    let error = runtime
145        .execute_script::<()>(
146            "fatal-rejection.js",
147            "cancelRejections = false; Promise.reject('fatal rejection');",
148        )
149        .unwrap_err();
150    assert!(error.to_string().contains("fatal rejection"), "{error}");
151    assert!(runtime
152        .execute_script::<bool>(
153            "fatal-rejection-reported.js",
154            "reported.length === 9 && reported[8][0] === 'unhandled' && reported[8][2] === 'fatal rejection'",
155        )
156        .unwrap());
157    runtime
158        .execute_script::<()>(
159            "enable-rejection-cancellation.js",
160            "cancelRejections = true;",
161        )
162        .unwrap();
163
164    runtime
165        .register_module("test:gc", |scope| {
166            let global = scope.get_current_context().global(scope);
167            let collect = v8::Function::builder(collect_garbage).build(scope)?;
168            let key = v8::String::new(scope, "collectGarbage")?;
169            global.set(scope, key.into(), collect.into());
170            Some(v8::Object::new(scope))
171        })
172        .unwrap();
173    runtime
174        .execute_script::<()>(
175            "unretained-rejections.js",
176            "captureRejections = false; globalThis.promiseRefs = []; globalThis.reasonRefs = []; for (let i = 0; i < 256; i++) { const reason = {}; const promise = Promise.reject(reason); promiseRefs.push(new WeakRef(promise)); reasonRefs.push(new WeakRef(reason)); }",
177        )
178        .unwrap();
179    runtime
180        .execute_script::<()>("collect.js", "collectGarbage();")
181        .unwrap();
182    let collected = runtime
183        .execute_script::<bool>(
184            "collected.js",
185            "promiseRefs.every(reference => reference.deref() === undefined) && reasonRefs.every(reference => reference.deref() === undefined)",
186        )
187        .unwrap();
188    assert!(collected);
189}