use std::path; use std::process; use crate::error::*; use crate::format; use crate::msg::*; /// The `test` subcommand (emulated). /// /// Created via [`CargoBuild::run_tests`]. /// /// Benefits over spawning `cargo test`: /// - Able to cache binary path, avoiding cargo overhead. /// - Independent of CWD. /// - stdout/stderr are clean of `cargo test` output. /// /// Required feature: `test_unstable` since the format parsed is unstable. /// /// Relevant features /// - `print` for logged output to be printed instead, generally for test writing. /// /// # Example /// /// ```rust /// extern crate escargot; /// extern crate assert_fs; /// /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .test("test") /// .manifest_path("tests/fixtures/test/Cargo.toml") /// .target_dir(temp.path()) /// .run_tests().unwrap() /// .next().unwrap().unwrap(); /// println!("artifact={}", run.path().display()); /// ``` /// /// [`CargoBuild::run_tests`]: crate::CargoBuild::run_tests() pub struct CargoTest { bin_path: path::PathBuf, kind: String, name: String, } impl CargoTest { pub(crate) fn with_messages( msgs: CommandMessages, ) -> impl Iterator> { extract_binary_paths(msgs) } /// The `name` of test /// /// Used to offer filtering or displays. /// /// # Example /// /// ```rust /// extern crate escargot; /// extern crate assert_fs; /// /// let temp = assert_fs::TempDir::new().unwrap(); /// let run: Result, _> = escargot::CargoBuild::new() /// .tests() /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/test/Cargo.toml") /// .target_dir(temp.path()) /// .run_tests() /// .unwrap() /// .collect(); /// let run = run.unwrap(); /// let mut names: Vec<_> = run.iter().map(|r| r.name()).collect(); /// names.sort_unstable(); /// assert_eq!(names, ["test", "test_fixture", "test_fixture"]); /// ``` pub fn name(&self) -> &str { self.name.as_str() } /// The `kind` of test /// /// Used to distinguish between integration tests (`test`) and unit tests (`bin`, `lib`). /// /// # Example /// /// ```rust /// extern crate escargot; /// extern crate assert_fs; /// /// let temp = assert_fs::TempDir::new().unwrap(); /// let run: Result, _> = escargot::CargoBuild::new() /// .tests() /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/test/Cargo.toml") /// .target_dir(temp.path()) /// .run_tests() /// .unwrap() /// .collect(); /// let run = run.unwrap(); /// let mut kinds: Vec<_> = run.iter().map(|r| r.kind()).collect(); /// kinds.sort_unstable(); /// assert_eq!(kinds, ["bin", "lib", "test"]); /// ``` pub fn kind(&self) -> &str { self.kind.as_str() } /// Path to the specified binary. /// /// This is to support alternative ways of launching the binary besides [`Command`]. /// /// # Example /// /// ```rust /// extern crate escargot; /// extern crate assert_fs; /// /// let temp = assert_fs::TempDir::new().unwrap(); /// let run: Vec<_> = escargot::CargoBuild::new() /// .tests() /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/test/Cargo.toml") /// .target_dir(temp.path()) /// .run_tests() /// .unwrap() /// .collect(); /// assert_eq!(run.len(), 3); /// ``` /// /// [`Command`]: std::process::Command pub fn path(&self) -> &path::Path { &self.bin_path } /// Run the build artifact. pub fn command(&self) -> process::Command { let mut cmd = process::Command::new(self.path()); cmd.arg("-Z").arg("unstable-options").arg("--format=json"); cmd } /// Run the configured test, returning test events. pub fn exec(&self) -> CargoResult { CommandMessages::with_command(self.command()) } } fn extract_bin(msg: &format::Message<'_>) -> Option { match msg { format::Message::CompilerArtifact(art) => { if art.profile.test { let bin_path = art .filenames .get(0) .expect("files must exist") .to_path_buf(); let kind = art .target .kind .get(0) .expect("kind must exist") .as_ref() .to_owned(); let name = art.target.name.as_ref().to_owned(); Some(CargoTest { bin_path, kind, name, }) } else { None } } _ => None, } } fn transpose(r: Result, E>) -> Option> { match r { Ok(Some(x)) => Some(Ok(x)), Ok(None) => None, Err(e) => Some(Err(e)), } } fn extract_binary_paths( msgs: CommandMessages, ) -> impl Iterator> { msgs.filter_map(move |m| { let m = m.and_then(|m| { let m = m.decode()?; format::log_message(&m); let p = extract_bin(&m); Ok(p) }); transpose(m) }) }