sweet_cli/commands/
test_wasm.rs

1use beet::prelude::*;
2use clap::Parser;
3use std::fs;
4use std::path::PathBuf;
5use std::process::Child;
6use std::process::Command;
7use std::str::FromStr;
8
9/// The wasm test runner
10///
11/// To use add the following:
12///
13/// ```toml
14///
15/// # .cargo/config.toml
16///
17/// [target.wasm32-unknown-unknown]
18///
19/// runner = 'sweet test-wasm'
20///
21/// ```
22///
23#[derive(Debug, Parser)]
24pub struct TestWasm {
25	/// the file passed in by cargo test.
26	///
27	/// It will look something like $CARGO_TARGET_DIR/wasm32-unknown-unknown/debug/deps/hello_test-c3298911e67ad05b.wasm
28	test_binary: String,
29	/// arguments passed to wasm-bindgen
30	#[arg(long)]
31	wasm_bindgen_args: Option<String>,
32
33	// we wont actuallly use this struct because the args will
34	// be passed to deno, but it provides --help messages
35	#[command(flatten)]
36	runner_args: sweet::prelude::TestRunnerConfig,
37}
38
39
40impl TestWasm {
41	pub fn run(self) -> Result {
42		self.run_wasm_bindgen()?;
43		self.init_deno()?;
44		self.run_deno()?;
45		Ok(())
46	}
47	fn run_wasm_bindgen(&self) -> Result {
48		let output = Command::new("wasm-bindgen")
49			.arg("--out-dir")
50			.arg(sweet_target_dir())
51			.arg("--out-name")
52			.arg("bindgen")
53			.arg("--target")
54			.arg("web")
55			.arg("--no-typescript")
56			.arg(&self.test_binary)
57			.args(
58				self.wasm_bindgen_args
59					.as_deref()
60					.unwrap_or_default()
61					.split_whitespace(),
62			)
63			.spawn()?;
64
65		handle_process("wasm-bindgen", output)?;
66		Ok(())
67	}
68
69
70	/// Move the deno file to the correct directory,
71	/// if this is the first time this will also ensure deno is installed
72	/// by running `deno --version`
73	fn init_deno(&self) -> Result {
74		let deno_runner_path = deno_runner_path();
75		let deno_str = include_str!("./deno.ts");
76
77		// return if the deno file already exists
78		if fs_ext::exists(&deno_runner_path)? {
79			let runner_hash = fs_ext::hash_file(&deno_runner_path)?;
80			let deno_hash = fs_ext::hash_string(deno_str);
81			if runner_hash == deno_hash {
82				return Ok(());
83			}
84		};
85
86		let deno_installed =
87			match Command::new("deno").arg("--version").status() {
88				Ok(val) => val.success(),
89				_ => false,
90			};
91		if !deno_installed {
92			bevybail!("{}", INSTALL_DENO);
93		}
94		println!("copying deno file to {}", deno_runner_path.display());
95
96		// wasm-bindgen will ensure parent dir exists
97		fs::write(deno_runner_path, deno_str)?;
98		Ok(())
99	}
100
101	fn run_deno(&self) -> Result {
102		// args will look like this so skip 3
103		// sweet test-wasm binary-path *actual-args
104		// why doesnt it work with three?
105		let args = std::env::args().skip(2).collect::<Vec<_>>();
106		let child = Command::new("deno")
107			.arg("--allow-read")
108			.arg("--allow-net")
109			.arg("--allow-env")
110			.arg(deno_runner_path())
111			.args(args)
112			.spawn()?;
113		handle_process("deno", child)?;
114		Ok(())
115	}
116}
117
118
119fn handle_process(_stderr_prefix: &str, child: Child) -> Result {
120	let output = child.wait_with_output()?;
121
122	let stdout = String::from_utf8_lossy(&output.stdout);
123	if !stdout.is_empty() {
124		println!("{}", stdout);
125	}
126
127	if !output.status.success() {
128		let stderr = String::from_utf8_lossy(&output.stderr);
129		bevybail!("{stderr}");
130	}
131	Ok(())
132}
133
134fn workspace_root() -> PathBuf {
135	let root_str = std::env::var("SWEET_ROOT").unwrap_or_default();
136	PathBuf::from_str(&root_str).unwrap()
137}
138
139fn sweet_target_dir() -> PathBuf {
140	let mut path = workspace_root();
141	path.push("target/sweet");
142	path
143}
144
145fn deno_runner_path() -> PathBuf {
146	let mut path = sweet_target_dir();
147	path.push("deno.ts");
148	path
149}
150
151const INSTALL_DENO: &str = "
152🦖 Sweet uses Deno for wasm tests 🦖
153
154Install Deno via:
155shell: 				curl -fsSL https://deno.land/install.sh | sh
156powershell: 	irm https://deno.land/install.ps1 | iex
157website: 			https://docs.deno.com/runtime/getting_started/installation/
158
159";
160
161
162#[cfg(test)]
163mod test {
164	use super::*;
165	use sweet::prelude::*;
166
167	#[test]
168	fn works() {
169		deno_runner_path()
170			.to_string_lossy()
171			.replace("\\", "/")
172			.xpect_ends_with("target/sweet/deno.ts");
173	}
174}