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 pub path_and_extra_options: Vec<String>,
83}
84
85pub struct Test {
87 crate_path: PathBuf,
88 crate_data: manifest::CrateData,
89 cache: Cache,
90 node: bool,
91 mode: InstallMode,
92 firefox: bool,
93 geckodriver: Option<PathBuf>,
94 chrome: bool,
95 chromedriver: Option<PathBuf>,
96 safari: bool,
97 safaridriver: Option<PathBuf>,
98 headless: bool,
99 release: bool,
100 test_runner_path: Option<PathBuf>,
101 extra_options: Vec<String>,
102}
103
104type TestStep = fn(&mut Test) -> Result<()>;
105
106impl Test {
107 pub fn try_from_opts(test_opts: TestOptions) -> Result<Self> {
109 let TestOptions {
110 node,
111 mode,
112 headless,
113 release,
114 chrome,
115 chromedriver,
116 firefox,
117 geckodriver,
118 safari,
119 safaridriver,
120 mut path_and_extra_options,
121 } = test_opts;
122
123 let first_arg_is_path = path_and_extra_options
124 .get(0)
125 .map(|first_arg| !first_arg.starts_with("-"))
126 .unwrap_or(false);
127
128 let (path, extra_options) = if first_arg_is_path {
129 let path = PathBuf::from_str(&path_and_extra_options.remove(0))?;
130 let extra_options = path_and_extra_options;
131
132 (Some(path), extra_options)
133 } else {
134 (None, path_and_extra_options)
135 };
136
137 let crate_path = get_crate_path(path)?;
138 let crate_data = manifest::CrateData::new(&crate_path, None)?;
139 let any_browser = chrome || firefox || safari;
140
141 if !node && !any_browser {
142 bail!("Must specify at least one of `--node`, `--chrome`, `--firefox`, or `--safari`")
143 }
144
145 if headless && !any_browser {
146 bail!(
147 "The `--headless` flag only applies to browser tests. Node does not provide a UI, \
148 so it doesn't make sense to talk about a headless version of Node tests."
149 )
150 }
151
152 Ok(Test {
153 cache: cache::get_wasm_pack_cache()?,
154 crate_path,
155 crate_data,
156 node,
157 mode,
158 chrome,
159 chromedriver,
160 firefox,
161 geckodriver,
162 safari,
163 safaridriver,
164 headless,
165 release,
166 test_runner_path: None,
167 extra_options,
168 })
169 }
170
171 pub fn set_cache(&mut self, cache: Cache) {
173 self.cache = cache;
174 }
175
176 pub fn run(mut self) -> Result<()> {
178 let process_steps = self.get_process_steps();
179
180 let started = Instant::now();
181 for (_, process_step) in process_steps {
182 process_step(&mut self)?;
183 }
184 let duration = crate::command::utils::elapsed(started.elapsed());
185 info!("Done in {}.", &duration);
186
187 Ok(())
188 }
189
190 fn get_process_steps(&self) -> Vec<(&'static str, TestStep)> {
191 macro_rules! steps {
192 ($($name:ident $(if $e:expr)* ),+) => {
193 {
194 let mut steps: Vec<(&'static str, TestStep)> = Vec::new();
195 $(
196 $(if $e)* {
197 steps.push((stringify!($name), Test::$name));
198 }
199 )*
200 steps
201 }
202 };
203 ($($name:ident $(if $e:expr)* ,)*) => (steps![$($name $(if $e)* ),*])
204 }
205 match self.mode {
206 InstallMode::Normal => steps![
207 step_check_rustc_version,
208 step_check_for_wasm_target,
209 step_build_tests,
210 step_install_wasm_bindgen,
211 step_test_node if self.node,
212 step_get_chromedriver if self.chrome && self.chromedriver.is_none(),
213 step_test_chrome if self.chrome,
214 step_get_geckodriver if self.firefox && self.geckodriver.is_none(),
215 step_test_firefox if self.firefox,
216 step_get_safaridriver if self.safari && self.safaridriver.is_none(),
217 step_test_safari if self.safari,
218 ],
219 InstallMode::Force => steps![
220 step_check_for_wasm_target,
221 step_build_tests,
222 step_install_wasm_bindgen,
223 step_test_node if self.node,
224 step_get_chromedriver if self.chrome && self.chromedriver.is_none(),
225 step_test_chrome if self.chrome,
226 step_get_geckodriver if self.firefox && self.geckodriver.is_none(),
227 step_test_firefox if self.firefox,
228 step_get_safaridriver if self.safari && self.safaridriver.is_none(),
229 step_test_safari if self.safari,
230 ],
231 InstallMode::Noinstall => steps![
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 }
243 }
244
245 fn step_check_rustc_version(&mut self) -> Result<()> {
246 info!("Checking rustc version...");
247 let _ = build::check_rustc_version()?;
248 info!("Rustc version is correct.");
249 Ok(())
250 }
251
252 fn step_check_for_wasm_target(&mut self) -> Result<()> {
253 info!("Adding wasm-target...");
254 build::wasm_target::check_for_wasm32_target()?;
255 info!("Adding wasm-target was successful.");
256 Ok(())
257 }
258
259 fn step_build_tests(&mut self) -> Result<()> {
260 info!("Compiling tests to wasm...");
261
262 let extra_options =
265 if let Some(index) = self.extra_options.iter().position(|arg| arg == "--") {
266 &self.extra_options[..index]
267 } else {
268 &self.extra_options
269 };
270 build::cargo_build_wasm_tests(&self.crate_path, !self.release, extra_options)?;
271
272 info!("Finished compiling tests to wasm.");
273 Ok(())
274 }
275
276 fn step_install_wasm_bindgen(&mut self) -> Result<()> {
277 info!("Identifying wasm-bindgen dependency...");
278 let lockfile = Lockfile::new(&self.crate_data)?;
279 let bindgen_version = lockfile.require_wasm_bindgen()?;
280
281 if lockfile.wasm_bindgen_test_version().is_none() {
287 bail!(
288 "Ensure that you have \"{}\" as a dependency in your Cargo.toml file:\n\
289 [dev-dependencies]\n\
290 wasm-bindgen-test = \"0.2\"",
291 style("wasm-bindgen-test").bold().dim(),
292 )
293 }
294
295 let status = install::download_prebuilt_or_cargo_install(
296 Tool::WasmBindgen,
297 &self.cache,
298 &bindgen_version,
299 self.mode.install_permitted(),
300 )?;
301
302 self.test_runner_path = match status {
303 install::Status::Found(dl) => Some(dl.binary("wasm-bindgen-test-runner")?),
304 _ => bail!("Could not find 'wasm-bindgen-test-runner'."),
305 };
306
307 info!("Getting wasm-bindgen-cli was successful.");
308 Ok(())
309 }
310
311 fn step_test_node(&mut self) -> Result<()> {
312 assert!(self.node);
313 info!("Running tests in node...");
314 test::cargo_test_wasm(
315 &self.crate_path,
316 self.release,
317 vec![
318 (
319 "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER",
320 &**self.test_runner_path.as_ref().unwrap(),
321 ),
322 ("WASM_BINDGEN_TEST_ONLY_NODE", "1".as_ref()),
323 ],
324 &self.extra_options,
325 )?;
326 info!("Finished running tests in node.");
327 Ok(())
328 }
329
330 fn step_get_chromedriver(&mut self) -> Result<()> {
331 assert!(self.chrome && self.chromedriver.is_none());
332
333 self.chromedriver = Some(webdriver::get_or_install_chromedriver(
334 &self.cache,
335 self.mode,
336 )?);
337 Ok(())
338 }
339
340 fn step_test_chrome(&mut self) -> Result<()> {
341 let chromedriver = self.chromedriver.as_ref().unwrap().display().to_string();
342 let chromedriver = chromedriver.as_str();
343 info!(
344 "Running tests in Chrome with chromedriver at {}",
345 chromedriver
346 );
347
348 let mut envs = self.webdriver_env();
349 envs.push(("CHROMEDRIVER", chromedriver));
350
351 test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
352 Ok(())
353 }
354
355 fn step_get_geckodriver(&mut self) -> Result<()> {
356 assert!(self.firefox && self.geckodriver.is_none());
357
358 self.geckodriver = Some(webdriver::get_or_install_geckodriver(
359 &self.cache,
360 self.mode,
361 )?);
362 Ok(())
363 }
364
365 fn step_test_firefox(&mut self) -> Result<()> {
366 let geckodriver = self.geckodriver.as_ref().unwrap().display().to_string();
367 let geckodriver = geckodriver.as_str();
368 info!(
369 "Running tests in Firefox with geckodriver at {}",
370 geckodriver
371 );
372
373 let mut envs = self.webdriver_env();
374 envs.push(("GECKODRIVER", geckodriver));
375
376 test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
377 Ok(())
378 }
379
380 fn step_get_safaridriver(&mut self) -> Result<()> {
381 assert!(self.safari && self.safaridriver.is_none());
382
383 self.safaridriver = Some(webdriver::get_safaridriver()?);
384 Ok(())
385 }
386
387 fn step_test_safari(&mut self) -> Result<()> {
388 let safaridriver = self.safaridriver.as_ref().unwrap().display().to_string();
389 let safaridriver = safaridriver.as_str();
390 info!(
391 "Running tests in Safari with safaridriver at {}",
392 safaridriver
393 );
394
395 let mut envs = self.webdriver_env();
396 envs.push(("SAFARIDRIVER", safaridriver));
397
398 test::cargo_test_wasm(&self.crate_path, self.release, envs, &self.extra_options)?;
399 Ok(())
400 }
401
402 fn webdriver_env(&self) -> Vec<(&'static str, &str)> {
403 let test_runner = self.test_runner_path.as_ref().unwrap().to_str().unwrap();
404 info!("Using wasm-bindgen test runner at {}", test_runner);
405 let mut envs = vec![
406 ("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER", test_runner),
407 ("WASM_BINDGEN_TEST_ONLY_WEB", "1"),
408 ];
409 if !self.headless {
410 envs.push(("NO_HEADLESS", "1"));
411 }
412 envs
413 }
414}