char/avril

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

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

Charlotte Somsnapshot: embed shared main and worker startup in the CLI688e96e

main
7.5 KiB238 linesraw
1mod common;
2
3use common::{run, spawn, wait};
4
5#[test]
6fn binary_piping_and_byob_preserve_bytes() {
7    let input: Vec<u8> = (0..160_003).map(|index| (index % 256) as u8).collect();
8    for source in [
9        r#"
10        import { stdin, stdout } from "avril:std";
11        await stdin.pipeThrough(new TransformStream()).pipeTo(stdout);
12        "#,
13        r#"
14        import { stdin, stdout } from "avril:std";
15        const reader = stdin.getReader({ mode: "byob" });
16        const writer = stdout.getWriter();
17        while (true) {
18            const view = new Uint8Array(new ArrayBuffer(1033), 7, 1024);
19            const { value, done } = await reader.read(view);
20            if (done) break;
21            await writer.write(value);
22        }
23        await writer.close();
24        "#,
25    ] {
26        assert_eq!(run(source, &input).stdout, input);
27    }
28}
29
30#[test]
31fn stdio_is_not_exposed_on_the_global_and_output_is_ordered() {
32    let output = run(
33        r#"
34        const opsSymbol = Symbol.for("__avril.ops");
35        if (opsSymbol in globalThis || globalThis[opsSymbol] !== undefined
36            || Reflect.ownKeys(globalThis).includes(opsSymbol)) {
37            throw new Error("Native op registry exposed");
38        }
39        if (Symbol.for("__avril.stdio") in globalThis) {
40            throw new Error("Stdio registry exposed");
41        }
42        import { stdout, stderr } from "avril:std";
43        const Bytes = Uint8Array;
44        const writer = stdout.getWriter();
45        const errors = stderr.getWriter();
46        globalThis.Uint8Array = () => { throw new Error("Poisoned constructor"); };
47        Promise.resolve = () => { throw new Error("Poisoned resolve"); };
48        Array.prototype.map = () => { throw new Error("Poisoned map"); };
49        writer.write(new Bytes([1, 2]));
50        writer.write(new Bytes([3, 4]));
51        await writer.close();
52        errors.write(new Bytes([5, 6]));
53        "#,
54        &[],
55    );
56    assert_eq!(output.stdout, [1, 2, 3, 4]);
57    assert_eq!(output.stderr, [5, 6]);
58}
59
60#[test]
61fn canceling_a_pending_stdin_read_does_not_hold_the_process_open() {
62    let (_dir, mut child) = spawn(
63        r#"
64        import { stdin } from "avril:std";
65        const reader = stdin.getReader();
66        const pending = reader.read();
67        await Promise.resolve();
68        await reader.cancel();
69        await pending;
70    "#,
71    );
72    let stdin = child.stdin.take().unwrap();
73    let output = wait(child);
74    drop(stdin);
75    assert!(
76        output.status.success(),
77        "{}",
78        String::from_utf8_lossy(&output.stderr)
79    );
80}
81
82#[test]
83fn output_failures_reject_writes_and_fail_the_cli() {
84    let (_dir, mut child) = spawn(
85        r#"import { stdout } from "avril:std"; await stdout
86        .getWriter().write(new Uint8Array([1]));"#,
87    );
88    drop(child.stdout.take());
89    let output = wait(child);
90    assert!(!output.status.success());
91    assert!(!output.stderr.is_empty());
92}
93
94#[test]
95fn default_stdin_reads_do_not_retain_oversized_buffers() {
96    let output = run(
97        r#"
98        import { stdin, stdout } from "avril:std";
99        const reader = stdin.getReader();
100        const writer = stdout.getWriter();
101        while (true) {
102            const { value, done } = await reader.read();
103            if (done) break;
104            if (value.buffer.byteLength !== value.byteLength) {
105                throw new Error("Input chunk retained an oversized buffer");
106            }
107            await writer.write(value);
108        }
109        await writer.close();
110    "#,
111        b"hi\n",
112    );
113    assert_eq!(output.stdout, b"hi\n");
114}
115
116#[test]
117fn concurrent_stdin_reads_cancel_cleanly() {
118    let (_dir, mut child) = spawn(
119        r#"
120        import { stdin } from "avril:std";
121        const reader = stdin.getReader();
122        const reads = [reader.read(), reader.read()];
123        await reader.cancel();
124        const results = await Promise.all(reads);
125        if (results.some(({done}) => !done)) throw new Error("Cancellation did not close input");
126    "#,
127    );
128    let stdin = child.stdin.take().unwrap();
129    let output = wait(child);
130    drop(stdin);
131    assert!(
132        output.status.success(),
133        "{}",
134        String::from_utf8_lossy(&output.stderr)
135    );
136}
137
138#[test]
139fn queued_stream_output_does_not_block_console_or_the_event_loop() {
140    let output = run(
141        r#"
142        import { stdout } from "avril:std";
143        const writer = stdout.getWriter();
144        const writes = [];
145        for (let value = 65; value < 73; value++) {
146            writes.push(writer.write(new Uint8Array(128 * 1024).fill(value)));
147        }
148        writes[0].then(() => console.log("done"));
149        await Promise.all(writes);
150        await writer.close();
151    "#,
152        &[],
153    );
154    let done = output
155        .stdout
156        .windows(b"done\n".len())
157        .position(|window| window == b"done\n")
158        .expect("console output should progress while stream writes are queued");
159    let mut stream_output = output.stdout;
160    stream_output.drain(done..done + b"done\n".len());
161    let expected: Vec<_> = (65..73)
162        .flat_map(|byte| std::iter::repeat_n(byte, 128 * 1024))
163        .collect();
164    assert_eq!(stream_output, expected);
165}
166
167#[test]
168fn output_accepts_shared_and_resizable_byte_views() {
169    let output = run(
170        r#"
171        import { stdout } from "avril:std";
172        const writer = stdout.getWriter();
173        const shared = new Uint8Array(new SharedArrayBuffer(4));
174        shared.set([0, 65, 66, 0]);
175        const sharedView = new Uint8Array(shared.buffer, 1, 2);
176        const resizable = new ArrayBuffer(4, { maxByteLength: 8 });
177        const fixedView = new Uint8Array(resizable, 1, 2);
178        fixedView.set([67, 68]);
179        const tracking = new Uint8Array(resizable, 3);
180        resizable.resize(7);
181        tracking.set([69, 70, 71, 72]);
182        const growable = new SharedArrayBuffer(1, { maxByteLength: 4 });
183        const growingView = new Uint8Array(growable);
184        growable.grow(2);
185        growingView.set([73, 74]);
186        globalThis.Uint8Array = () => { throw new Error("Poisoned constructor"); };
187        Object.defineProperty(ArrayBuffer.prototype, "resizable", {
188            get() { throw new Error("Poisoned getter"); },
189        });
190        await writer.write(sharedView);
191        await writer.write(fixedView);
192        await writer.write(tracking);
193        await writer.write(growingView);
194        await writer.close();
195    "#,
196        &[],
197    );
198    assert_eq!(output.stdout, b"ABCDEFGHIJ");
199}
200
201#[test]
202fn accepted_stream_writes_precede_console_output() {
203    let output = run(
204        r#"
205        import { stdout } from "avril:std";
206        const writer = stdout.getWriter();
207        await writer.ready;
208        writer.write(new Uint8Array([65, 10]));
209        console.log("B");
210        await writer.close();
211    "#,
212        &[],
213    );
214    assert_eq!(output.stdout, b"A\nB\n");
215}
216
217#[test]
218fn accepted_output_is_flushed_even_when_the_module_fails() {
219    let (_dir, child) = spawn(
220        r#"
221        import { stdout } from "avril:std";
222        const writer = stdout.getWriter();
223        const bytes = new Uint8Array(8 * 128 * 1024);
224        for (let value = 65; value < 73; value++) {
225            bytes.fill(value, (value - 65) * 128 * 1024, (value - 64) * 128 * 1024);
226        }
227        writer.write(bytes);
228        throw new Error("after accepting output");
229    "#,
230    );
231    let output = wait(child);
232    let expected: Vec<_> = (65..73)
233        .flat_map(|byte| std::iter::repeat_n(byte, 128 * 1024))
234        .collect();
235    assert!(!output.status.success());
236    assert_eq!(output.stdout, expected);
237    assert!(String::from_utf8_lossy(&output.stderr).contains("after accepting output"));
238}