sweet_cli/test_runners/
test_wasm.rs

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