Fork of daniellemaywood.uk/gleam — Wasm codegen work
6.7 kB
244 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Gleam contributors
3
4use std::{io::Read, process::Stdio};
5
6use camino::{Utf8Path, Utf8PathBuf};
7use gleam_core::{
8 build::{Runtime, Target},
9 io::Command,
10 paths::ProjectPaths,
11};
12
13use gleam_cli::{
14 fs,
15 run::{self, Which},
16};
17
18fn run_and_produce_pretty_snapshot(
19 target: Option<Target>,
20 runtime: Option<Runtime>,
21 project_directory: Utf8PathBuf,
22) -> String {
23 let project_root = fs::get_project_root(project_directory).expect("project root");
24
25 // Node has a bug with paths on Windows at the moment, so we have to
26 // edit the path to work around it.
27 // https://github.com/nodejs/node/issues/60435
28 #[cfg(windows)]
29 let project_root = project_root
30 .to_string()
31 .strip_prefix(r"\\?\")
32 .map(Utf8PathBuf::from)
33 .unwrap_or(project_root);
34 let paths = ProjectPaths::new(project_root);
35
36 let output = run_and_capture_output(&paths, "main", target, runtime)
37 // Since the echo output's contains a path we will replace the `\` with a `/`
38 // so that the snapshot doesn't fail on Windows in CI.
39 .replace("src\\", "src/");
40
41 let main_module_content =
42 fs::read(paths.src_directory().join("main.gleam")).expect("read main module");
43
44 format!(
45 "--- main.gleam ----------------------
46{main_module_content}
47
48--- gleam run output ----------------
49{output}
50"
51 )
52}
53
54fn run_and_capture_output(
55 paths: &ProjectPaths,
56 main_module: &str,
57 target: Option<Target>,
58 runtime: Option<Runtime>,
59) -> String {
60 fs::delete_directory(&paths.build_directory()).expect("delete build directory content");
61
62 let Command {
63 program,
64 args,
65 env,
66 cwd: _,
67 stdio: _,
68 } = run::setup(
69 paths,
70 vec![],
71 target,
72 runtime,
73 Some(main_module.into()),
74 Which::Src,
75 true,
76 )
77 .expect("run setup");
78
79 let mut process = std::process::Command::new(&program)
80 .args(args)
81 .stdin(Stdio::null())
82 .stdout(Stdio::null())
83 .stderr(Stdio::piped())
84 .envs(env.iter().map(|pair| (&pair.0, &pair.1)))
85 .current_dir(paths.root())
86 .spawn()
87 .unwrap_or_else(|e| panic!("Failed to spawn process '{}': {}", &program, &e));
88
89 let mut stderr = process.stderr.take().expect("take stderr");
90 let mut output = String::new();
91 let _ = stderr.read_to_string(&mut output).expect("read stderr");
92 let _ = process.wait().expect("run with no errors");
93 output
94}
95
96macro_rules! assert_echo {
97 ($project_name: expr) => {
98 let snapshot_name = snapshot_name(None, None, $project_name);
99 insta::allow_duplicates! {
100 assert_echo!(&snapshot_name, Some(Target::Erlang), None, $project_name);
101 assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::Bun), $project_name);
102 assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::Deno), $project_name);
103 assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::NodeJs), $project_name);
104 }
105 };
106
107 ($target: expr, $project_name: expr) => {
108 let snapshot_name = snapshot_name(Some($target), None, $project_name);
109 match $target {
110 Target::JavaScript => insta::allow_duplicates! {
111 assert_echo!(&snapshot_name, Some($target), Some(Runtime::Bun), $project_name);
112 assert_echo!(&snapshot_name, Some($target), Some(Runtime::Deno), $project_name);
113 assert_echo!(&snapshot_name, Some($target), Some(Runtime::NodeJs), $project_name);
114 },
115 Target::Erlang => {
116 assert_echo!(&snapshot_name, Some($target), None, $project_name);
117 }
118 Target::Cranelift => {
119 assert_echo!(&snapshot_name, Some($target), None, $project_name);
120 }
121 }
122 };
123
124 ($snapshot_name: expr, $target: expr, $runtime: expr, $project_name: expr) => {
125 let path = fs::canonicalise(&Utf8Path::new("../test-output/cases").join($project_name))
126 .expect("canonicalise path");
127 let output = run_and_produce_pretty_snapshot($target, $runtime, path);
128 insta::assert_snapshot!($snapshot_name.to_string(), output);
129 };
130}
131
132fn snapshot_name(target: Option<Target>, runtime: Option<Runtime>, suffix: &str) -> String {
133 let show_target = |target: Target| match target {
134 Target::Erlang => "erlang",
135 Target::JavaScript => "javascript",
136 };
137 let show_runtime = |runtime: Runtime| match runtime {
138 Runtime::NodeJs => "nodejs",
139 Runtime::Deno => "deno",
140 Runtime::Bun => "bun",
141 };
142 let prefix = match (target, runtime) {
143 (None, None) => "".into(),
144 (None, Some(runtime)) => format!("{}-", show_runtime(runtime)),
145 (Some(target), None) => format!("{}-", show_target(target)),
146 (Some(target), Some(runtime)) => {
147 format!("{}-{}-", show_target(target), show_runtime(runtime))
148 }
149 };
150 format!("{prefix}{suffix}")
151}
152
153#[test]
154fn echo_bitarray() {
155 assert_echo!(Target::JavaScript, "echo_bitarray");
156 assert_echo!(Target::Erlang, "echo_bitarray");
157}
158
159#[test]
160fn echo_bool() {
161 assert_echo!("echo_bool");
162}
163
164#[test]
165fn echo_charlist() {
166 assert_echo!("echo_charlist");
167}
168
169#[test]
170fn echo_custom_type() {
171 assert_echo!(Target::Erlang, "echo_custom_type");
172 assert_echo!(Target::JavaScript, "echo_custom_type");
173}
174
175#[test]
176fn echo_dict() {
177 assert_echo!("echo_dict");
178}
179
180#[test]
181fn echo_float() {
182 assert_echo!(Target::Erlang, "echo_float");
183 assert_echo!(Target::JavaScript, "echo_float");
184}
185
186#[test]
187fn echo_function() {
188 assert_echo!("echo_function");
189}
190
191#[test]
192fn echo_importing_module_named_inspect() {
193 assert_echo!("echo_importing_module_named_inspect");
194}
195
196#[test]
197fn echo_int() {
198 assert_echo!("echo_int");
199}
200
201#[test]
202fn echo_list() {
203 assert_echo!("echo_list");
204}
205
206#[test]
207fn echo_nil() {
208 assert_echo!("echo_nil");
209}
210
211#[test]
212fn echo_string() {
213 assert_echo!("echo_string");
214}
215
216#[test]
217fn echo_tuple() {
218 assert_echo!("echo_tuple");
219}
220
221#[test]
222fn echo_non_record_atom_tag() {
223 assert_echo!(Target::Erlang, "echo_non_record_atom_tag");
224}
225
226#[test]
227fn echo_circular_reference() {
228 assert_echo!(Target::JavaScript, "echo_circular_reference");
229}
230
231#[test]
232fn echo_singleton() {
233 assert_echo!("echo_singleton");
234}
235
236#[test]
237fn echo_with_message() {
238 assert_echo!("echo_with_message");
239}
240
241#[test]
242fn linked_process_exit() {
243 assert_echo!(Target::Erlang, "linked_process_exit");
244}