1use crate::build;
4use crate::cache;
5use crate::command::utils::get_crate_path;
6use crate::install::{self, InstallMode, Tool};
7use crate::lockfile::Lockfile;
8use crate::manifest;
9use crate::test::{self, webdriver};
10use anyhow::{bail, Result};
11use binary_install::Cache;
12use clap::Args;
13use console::style;
14use log::info;
15use std::path::PathBuf;
16use std::str::FromStr;
17use std::time::Instant;
18
19#[derive(Debug, Default, Args)]
20#[command(allow_hyphen_values = true, trailing_var_arg = true)]
21pub struct TestOptions {
23 #[clap(long = "node")]
24 pub node: bool,
26
27 #[clap(long = "firefox")]
28 pub firefox: bool,
33
34 #[clap(long = "geckodriver")]
35 pub geckodriver: Option<PathBuf>,
38
39 #[clap(long = "chrome")]
40 pub chrome: bool,
45
46 #[clap(long = "chromedriver")]
47 pub chromedriver: Option<PathBuf>,
50
51 #[clap(long = "safari")]
52 pub safari: bool,
57
58 #[clap(long = "safaridriver")]
59 pub safaridriver: Option<PathBuf>,
62
63 #[clap(long = "headless")]
64 pub headless: bool,
67
68 #[clap(long = "mode", short = 'm', default_value = "normal")]
69 pub mode: InstallMode,
71
72 #[clap(long = "release", short = 'r')]
73 pub release: bool,
75
76 #[clap(long = "panic-unwind")]
77 pub panic_unwind: bool,
82
83 pub path_and_extra_options: Vec<String>,
90}
91
92pub struct Test {
94 crate_path: PathBuf,
95 crate_data: manifest::CrateData,
96 cache: Cache,
97 node: bool,
98 mode: InstallMode,
99 firefox: bool,
100 geckodriver: Option<PathBuf>,
101 chrome: bool,
102 chromedriver: Option<PathBuf>,
103 safari: bool,
104 safaridriver: Option<PathBuf>,
105 headless: bool,
106 release: bool,
107 panic_unwind: bool,
108 test_runner_path: Option<PathBuf>,
109 extra_options: Vec<String>,
110 target_triple: String,
111}
112
113type TestStep = fn(&mut Test) -> Result<()>;
114
115impl Test {
116 pub fn try_from_opts(test_opts: TestOptions) -> Result<Self> {
118 let TestOptions {
119 node,
120 mode,
121 headless,
122 release,
123 panic_unwind,
124 chrome,
125 chromedriver,
126 firefox,
127 geckodriver,
128 safari,
129 safaridriver,
130 mut path_and_extra_options,
131 } = test_opts;
132
133 let first_arg_is_path = path_and_extra_options
134 .get(0)
135 .map(|first_arg| !first_arg.starts_with("-"))
136 .unwrap_or(false);
137
138 let (path, extra_options) = if first_arg_is_path {
139 let path = PathBuf::from_str(&path_and_extra_options.remove(0))?;
140 let extra_options = path_and_extra_options;
141
142 (Some(path), extra_options)
143 } else {
144 (None, path_and_extra_options)
145 };
146
147 let crate_path = get_crate_path(path)?;
148 let crate_data = manifest::CrateData::new(&crate_path, None)?;
149 let any_browser = chrome || firefox || safari;
150
151 let target_triple = {
152 let mut iter = extra_options.iter();
153 if iter.by_ref().any(|option| option == "--target") {
154 iter.next().map(|s| s.as_str())
155 } else {
156 None
157 }
158 .unwrap_or("wasm32-unknown-unknown")
159 .to_owned()
160 };
161
162 if !node && !any_browser {
163 bail!("Must specify at least one of `--node`, `--chrome`, `--firefox`, or `--safari`")
164 }
165
166 if headless && !any_browser {
167 bail!(
168 "The `--headless` flag only applies to browser tests. Node does not provide a UI, \
169 so it doesn't make sense to talk about a headless version of Node tests."
170 )
171 }
172
173 Ok(Test {
174 cache: cache::get_wasm_pack_cache()?,
175 crate_path,
176 crate_data,
177 node,
178 mode,
179 chrome,
180 chromedriver,
181 firefox,
182 geckodriver,
183 safari,
184 safaridriver,
185 headless,
186 release,
187 panic_unwind,
188 test_runner_path: None,
189 target_triple,
190 extra_options,
191 })
192 }
193
194 pub fn set_cache(&mut self, cache: Cache) {
196 self.cache = cache;
197 }
198
199 pub fn run(mut self) -> Result<()> {
201 let process_steps = self.get_process_steps();
202
203 let started = Instant::now();
204 for (_, process_step) in process_steps {
205 process_step(&mut self)?;
206 }
207 let duration = crate::command::utils::elapsed(started.elapsed());
208 info!("Done in {}.", &duration);
209
210 Ok(())
211 }
212
213 fn get_process_steps(&self) -> Vec<(&'static str, TestStep)> {
214 macro_rules! steps {
215 ($($name:ident $(if $e:expr)* ),+) => {
216 {
217 let mut steps: Vec<(&'static str, TestStep)> = Vec::new();
218 $(
219 $(if $e)* {
220 steps.push((stringify!($name), Test::$name));
221 }
222 )*
223 steps
224 }
225 };
226 ($($name:ident $(if $e:expr)* ,)*) => (steps![$($name $(if $e)* ),*])
227 }
228 match self.mode {
229 InstallMode::Normal => steps![
230 step_check_rustc_version,
231 step_check_for_wasm_target,
232 step_build_tests,
233 step_install_wasm_bindgen,
234 step_test_node if self.node,
235 step_get_chromedriver if self.chrome && self.chromedriver.is_none(),
236 step_test_chrome if self.chrome,
237 step_get_geckodriver if self.firefox && self.geckodriver.is_none(),
238 step_test_firefox if self.firefox,
239 step_get_safaridriver if self.safari && self.safaridriver.is_none(),
240 step_test_safari if self.safari,
241 ],
242 InstallMode::Force => steps![
243 step_check_for_wasm_target,
244 step_build_tests,
245 step_install_wasm_bindgen,
246 step_test_node if self.node,
247 step_get_chromedriver if self.chrome && self.chromedriver.is_none(),
248 step_test_chrome if self.chrome,
249 step_get_geckodriver if self.firefox && self.geckodriver.is_none(),
250 step_test_firefox if self.firefox,
251 step_get_safaridriver if self.safari && self.safaridriver.is_none(),
252 step_test_safari if self.safari,
253 ],
254 InstallMode::Noinstall => steps![
255 step_build_tests,
256 step_install_wasm_bindgen,
257 step_test_node if self.node,
258 step_get_chromedriver if self.chrome && self.chromedriver.is_none(),
259 step_test_chrome if self.chrome,
260 step_get_geckodriver if self.firefox && self.geckodriver.is_none(),
261 step_test_firefox if self.firefox,
262 step_get_safaridriver if self.safari && self.safaridriver.is_none(),
263 step_test_safari if self.safari,
264 ],
265 }
266 }
267
268 fn step_check_rustc_version(&mut self) -> Result<()> {
269 if self.panic_unwind {
271 info!("Skipping rustc version check (using nightly via --panic-unwind).");
272 return Ok(());
273 }
274 info!("Checking rustc version...");
275 let _ = build::check_rustc_version()?;
276 info!("Rustc version is correct.");
277 Ok(())
278 }
279
280 fn step_check_for_wasm_target(&mut self) -> Result<()> {
281 if self.panic_unwind {
282 info!("Checking nightly toolchain prerequisites for panic=unwind...");
283 build::wasm_target::check_nightly_prerequisites()?;
284 info!("Nightly prerequisites check was successful.");
285 return Ok(());
286 }
287 info!("Adding wasm-target...");
288 build::wasm_target::check_for_wasm_target(&self.target_triple)?;
289 info!("Adding wasm-target was successful.");
290 Ok(())
291 }
292
293 fn step_build_tests(&mut self) -> Result<()> {
294 info!("Compiling tests to wasm...");
295
296 let extra_options =
299 if let Some(index) = self.extra_options.iter().position(|arg| arg == "--") {
300 &self.extra_options[..index]
301 } else {
302 &self.extra_options
303 };
304 build::cargo_build_wasm_tests(
305 &self.crate_path,
306 !self.release,
307 extra_options,
308 &self.target_triple,
309 self.panic_unwind,
310 )?;
311
312 info!("Finished compiling tests to wasm.");
313 Ok(())
314 }
315
316 fn step_install_wasm_bindgen(&mut self) -> Result<()> {
317 info!("Identifying wasm-bindgen dependency...");
318 let lockfile = Lockfile::new(&self.crate_data)?;
319 let bindgen_version = lockfile.require_wasm_bindgen()?;
320
321 if lockfile.wasm_bindgen_test_version().is_none() {
327 bail!(
328 "Ensure that you have \"{}\" as a dependency in your Cargo.toml file:\n\
329 [dev-dependencies]\n\
330 wasm-bindgen-test = \"0.2\"",
331 style("wasm-bindgen-test").bold().dim(),
332 )
333 }
334
335 let status = install::download_prebuilt_or_cargo_install(
336 Tool::WasmBindgen,
337 &self.cache,
338 &bindgen_version,
339 self.mode.install_permitted(),
340 )?;
341
342 self.test_runner_path = match status {
343 install::Status::Found(dl) => Some(dl.binary("wasm-bindgen-test-runner")?),
344 _ => bail!("Could not find 'wasm-bindgen-test-runner'."),
345 };
346
347 info!("Getting wasm-bindgen-cli was successful.");
348 Ok(())
349 }
350
351 fn step_test_node(&mut self) -> Result<()> {
352 assert!(self.node);
353 info!("Running tests in node...");
354 let runner_env = format!(
355 "CARGO_TARGET_{}_RUNNER",
356 self.target_triple.replace('-', "_").to_uppercase()
357 );
358 let runner_path = self
359 .test_runner_path
360 .as_ref()
361 .unwrap()
362 .to_str()
363 .unwrap()
364 .to_string();
365 test::cargo_test_wasm(
366 &self.crate_path,
367 self.release,
368 vec![
369 (runner_env, runner_path),
370 ("WASM_BINDGEN_TEST_ONLY_NODE".to_string(), "1".to_string()),
371 ],
372 &self.extra_options,
373 &self.target_triple,
374 )?;
375 info!("Finished running tests in node.");
376 Ok(())
377 }
378
379 fn step_get_chromedriver(&mut self) -> Result<()> {
380 assert!(self.chrome && self.chromedriver.is_none());
381
382 self.chromedriver = Some(webdriver::get_or_install_chromedriver(
383 &self.cache,
384 self.mode,
385 )?);
386 Ok(())
387 }
388
389 fn step_test_chrome(&mut self) -> Result<()> {
390 let chromedriver = self.chromedriver.as_ref().unwrap().display().to_string();
391 info!(
392 "Running tests in Chrome with chromedriver at {}",
393 chromedriver
394 );
395
396 let mut envs = self.webdriver_env();
397 envs.push(("CHROMEDRIVER".to_string(), chromedriver));
398
399 test::cargo_test_wasm(
400 &self.crate_path,
401 self.release,
402 envs,
403 &self.extra_options,
404 &self.target_triple,
405 )?;
406 Ok(())
407 }
408
409 fn step_get_geckodriver(&mut self) -> Result<()> {
410 assert!(self.firefox && self.geckodriver.is_none());
411
412 self.geckodriver = Some(webdriver::get_or_install_geckodriver(
413 &self.cache,
414 self.mode,
415 )?);
416 Ok(())
417 }
418
419 fn step_test_firefox(&mut self) -> Result<()> {
420 let geckodriver = self.geckodriver.as_ref().unwrap().display().to_string();
421 info!(
422 "Running tests in Firefox with geckodriver at {}",
423 geckodriver
424 );
425
426 let mut envs = self.webdriver_env();
427 envs.push(("GECKODRIVER".to_string(), geckodriver));
428
429 test::cargo_test_wasm(
430 &self.crate_path,
431 self.release,
432 envs,
433 &self.extra_options,
434 &self.target_triple,
435 )?;
436 Ok(())
437 }
438
439 fn step_get_safaridriver(&mut self) -> Result<()> {
440 assert!(self.safari && self.safaridriver.is_none());
441
442 self.safaridriver = Some(webdriver::get_safaridriver()?);
443 Ok(())
444 }
445
446 fn step_test_safari(&mut self) -> Result<()> {
447 let safaridriver = self.safaridriver.as_ref().unwrap().display().to_string();
448 info!(
449 "Running tests in Safari with safaridriver at {}",
450 safaridriver
451 );
452
453 let mut envs = self.webdriver_env();
454 envs.push(("SAFARIDRIVER".to_string(), safaridriver));
455
456 test::cargo_test_wasm(
457 &self.crate_path,
458 self.release,
459 envs,
460 &self.extra_options,
461 &self.target_triple,
462 )?;
463 Ok(())
464 }
465
466 fn webdriver_env(&self) -> Vec<(String, String)> {
467 let test_runner = self
468 .test_runner_path
469 .as_ref()
470 .unwrap()
471 .to_str()
472 .unwrap()
473 .to_string();
474 info!("Using wasm-bindgen test runner at {}", test_runner);
475 let runner_env = format!(
476 "CARGO_TARGET_{}_RUNNER",
477 self.target_triple.replace('-', "_").to_uppercase()
478 );
479 let mut envs = vec![
480 (runner_env, test_runner),
481 ("WASM_BINDGEN_TEST_ONLY_WEB".to_string(), "1".to_string()),
482 ];
483 if !self.headless {
484 envs.push(("NO_HEADLESS".to_string(), "1".to_string()));
485 }
486 envs
487 }
488}