char/avril

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

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

Charlotte Somsnapshot: prepare typed ops and static/live bootstrap711e4d5

main
6.7 KiB193 linesraw
1use std::cell::Cell;
2use std::io;
3use std::rc::Rc;
4
5use avril_core::{Error, Extension, Op, Runtime, RuntimeOptions};
6
7#[test]
8fn live_initializers_run_after_all_static_entries_and_keep_export_identity() {
9    let native = Rc::new(Cell::new(10));
10    let read = native.clone();
11    let mut runtime = Runtime::new(RuntimeOptions {
12        extensions: vec![
13            Extension {
14                name: "ext:consumer",
15                dependencies: &["ext:producer"],
16                ops: vec![Op::new("setNative", move |value: u32| {
17                    native.set(value);
18                    Ok::<_, io::Error>(())
19                })],
20                modules: &[(
21                    "ext:consumer/main.js",
22                    r#"
23                    import {onRuntimeStart, startRuntime} from 'ext:core/bootstrap';
24                    import {setNative} from 'ext:core/ops';
25                    import {value} from 'ext:producer/main.js';
26                    if (value.native !== undefined) throw Error('Early initialization');
27                    await Promise.resolve();
28                    events.push('static consumer');
29                    setNative(42);
30                    globalThis.captured = value;
31                    globalThis.startAgain = startRuntime;
32                    globalThis.registerLate = () => onRuntimeStart(() => {});
33                    onRuntimeStart(() => {
34                        if (value.native !== 42) throw Error('Stale native state');
35                        events.push('live consumer');
36                    });
37                "#,
38                )],
39                entry_point: Some("ext:consumer/main.js"),
40            },
41            Extension {
42                name: "ext:producer",
43                dependencies: &[],
44                ops: vec![Op::new("readNative", move |(): ()| {
45                    Ok::<_, io::Error>(read.get())
46                })],
47                modules: &[(
48                    "ext:producer/main.js",
49                    r#"
50                    import {onRuntimeStart} from 'ext:core/bootstrap';
51                    import {readNative} from 'ext:core/ops';
52                    globalThis.events = ['static producer'];
53                    export const value = {native: undefined};
54                    onRuntimeStart(() => {
55                        value.native = readNative();
56                        Object.freeze(value);
57                        events.push('live producer');
58                    });
59                "#,
60                )],
61                entry_point: Some("ext:producer/main.js"),
62            },
63        ],
64        ..Default::default()
65    })
66    .unwrap();
67    assert_eq!(
68        runtime
69            .execute_script::<Vec<String>>("events.js", "events")
70            .unwrap(),
71        [
72            "static producer",
73            "static consumer",
74            "live producer",
75            "live consumer"
76        ],
77    );
78    runtime
79        .register_extension_module("test:producer", "ext:producer/main.js")
80        .unwrap();
81    runtime
82        .execute_script::<()>(
83            "identity.js",
84            r#"
85        import('test:producer').then(({value}) => {
86            if (value !== captured || value.native !== 42 || !Object.isFrozen(value)) {
87                throw Error('Export identity or descriptors changed');
88            }
89        });
90        for (const action of [startAgain, registerLate]) {
91            try { action(); throw Error('Expected rejection'); }
92            catch (error) { if (error.message !== 'Runtime already started') throw error; }
93        }
94    "#,
95        )
96        .unwrap();
97    runtime.run_event_loop().unwrap();
98}
99
100#[test]
101fn failing_live_initializers_abort_construction_without_running_later_initializers() {
102    let called = Rc::new(Cell::new(false));
103    let observed = called.clone();
104    let result = Runtime::new(RuntimeOptions {
105        extensions: vec![Extension {
106            name: "ext:test",
107            dependencies: &[],
108            ops: vec![Op::new("later", move |(): ()| {
109                called.set(true);
110                Ok::<_, io::Error>(())
111            })],
112            modules: &[(
113                "ext:test/main.js",
114                r#"
115                import {onRuntimeStart} from 'ext:core/bootstrap';
116                import {later} from 'ext:core/ops';
117                onRuntimeStart(() => { throw Error('failed live initialization'); });
118                onRuntimeStart(() => { later(); });
119            "#,
120            )],
121            entry_point: Some("ext:test/main.js"),
122        }],
123        ..Default::default()
124    });
125    assert!(
126        matches!(result, Err(Error::JavaScript(message)) if message.contains("failed live initialization"))
127    );
128    assert!(!observed.get());
129}
130
131#[test]
132fn live_initializers_cannot_escape_the_startup_boundary_with_promises() {
133    let result = Runtime::new(RuntimeOptions {
134        extensions: vec![Extension {
135            name: "ext:test",
136            dependencies: &[],
137            ops: vec![],
138            modules: &[(
139                "ext:test/main.js",
140                r#"
141                import {onRuntimeStart} from 'ext:core/bootstrap';
142                onRuntimeStart(async () => {});
143            "#,
144            )],
145            entry_point: Some("ext:test/main.js"),
146        }],
147        ..Default::default()
148    });
149    assert!(
150        matches!(result, Err(Error::JavaScript(message)) if message.contains("Runtime initializers must return undefined"))
151    );
152}
153
154#[test]
155fn live_initializers_ignore_poisoned_array_protocols() {
156    let mut runtime = Runtime::new(RuntimeOptions {
157        extensions: vec![Extension {
158            name: "ext:test",
159            dependencies: &[],
160            ops: vec![],
161            modules: &[(
162                "ext:test/main.js",
163                r#"
164                import {onRuntimeStart} from 'ext:core/bootstrap';
165                const push = Array.prototype.push;
166                const iterator = Array.prototype[Symbol.iterator];
167                let initialized = 0;
168                onRuntimeStart(function () {
169                    if (this !== undefined) throw Error('Leaked initializer receiver');
170                    initialized++;
171                });
172                const poison = () => { throw Error('Mutable array protocol'); };
173                Array.prototype.push = poison;
174                Array.prototype[Symbol.iterator] = poison;
175                onRuntimeStart(() => {
176                    Array.prototype.push = push;
177                    Array.prototype[Symbol.iterator] = iterator;
178                    globalThis.initialized = initialized;
179                });
180            "#,
181            )],
182            entry_point: Some("ext:test/main.js"),
183        }],
184        ..Default::default()
185    })
186    .unwrap();
187    assert_eq!(
188        runtime
189            .execute_script::<u32>("initialized.js", "initialized")
190            .unwrap(),
191        1
192    );
193}