| use std::borrow::Cow; |
| |
| /// Entry point for running tests |
| #[derive(Debug, Default)] |
| pub struct TestCases { |
| runner: std::cell::RefCell<crate::RunnerSpec>, |
| bins: std::cell::RefCell<crate::BinRegistry>, |
| substitutions: std::cell::RefCell<crate::elide::Substitutions>, |
| has_run: std::cell::Cell<bool>, |
| } |
| |
| impl TestCases { |
| pub fn new() -> Self { |
| let s = Self::default(); |
| s.runner |
| .borrow_mut() |
| .include(parse_include(std::env::args_os())); |
| s |
| } |
| |
| /// Load tests from `glob` |
| pub fn case(&self, glob: impl AsRef<std::path::Path>) -> &Self { |
| self.runner.borrow_mut().case(glob.as_ref(), None); |
| self |
| } |
| |
| /// Overwrite expected status for a test |
| pub fn pass(&self, glob: impl AsRef<std::path::Path>) -> &Self { |
| self.runner |
| .borrow_mut() |
| .case(glob.as_ref(), Some(crate::schema::CommandStatus::Success)); |
| self |
| } |
| |
| /// Overwrite expected status for a test |
| pub fn fail(&self, glob: impl AsRef<std::path::Path>) -> &Self { |
| self.runner |
| .borrow_mut() |
| .case(glob.as_ref(), Some(crate::schema::CommandStatus::Failed)); |
| self |
| } |
| |
| /// Overwrite expected status for a test |
| pub fn interrupted(&self, glob: impl AsRef<std::path::Path>) -> &Self { |
| self.runner.borrow_mut().case( |
| glob.as_ref(), |
| Some(crate::schema::CommandStatus::Interrupted), |
| ); |
| self |
| } |
| |
| /// Overwrite expected status for a test |
| pub fn skip(&self, glob: impl AsRef<std::path::Path>) -> &Self { |
| self.runner |
| .borrow_mut() |
| .case(glob.as_ref(), Some(crate::schema::CommandStatus::Skipped)); |
| self |
| } |
| |
| /// Set default bin, by path, for commands |
| pub fn default_bin_path(&self, path: impl AsRef<std::path::Path>) -> &Self { |
| let bin = Some(crate::schema::Bin::Path(path.as_ref().into())); |
| self.runner.borrow_mut().default_bin(bin); |
| self |
| } |
| |
| /// Set default bin, by name, for commands |
| pub fn default_bin_name(&self, name: impl AsRef<str>) -> &Self { |
| let bin = Some(crate::schema::Bin::Name(name.as_ref().into())); |
| self.runner.borrow_mut().default_bin(bin); |
| self |
| } |
| |
| /// Set default timeout for commands |
| pub fn timeout(&self, time: std::time::Duration) -> &Self { |
| self.runner.borrow_mut().timeout(Some(time)); |
| self |
| } |
| |
| /// Set default environment variable |
| pub fn env(&self, key: impl Into<String>, value: impl Into<String>) -> &Self { |
| self.runner.borrow_mut().env(key, value); |
| self |
| } |
| |
| /// Add a bin to the "PATH" for cases to use |
| pub fn register_bin( |
| &self, |
| name: impl Into<String>, |
| path: impl Into<crate::schema::Bin>, |
| ) -> &Self { |
| self.bins |
| .borrow_mut() |
| .register_bin(name.into(), path.into()); |
| self |
| } |
| |
| /// Add a series of bins to the "PATH" for cases to use |
| pub fn register_bins<N: Into<String>, B: Into<crate::schema::Bin>>( |
| &self, |
| bins: impl IntoIterator<Item = (N, B)>, |
| ) -> &Self { |
| self.bins |
| .borrow_mut() |
| .register_bins(bins.into_iter().map(|(n, b)| (n.into(), b.into()))); |
| self |
| } |
| |
| /// Add a variable for normalizing output |
| /// |
| /// Variable names must be |
| /// - Surrounded by `[]` |
| /// - Consist of uppercase letters |
| /// |
| /// Variables will be preserved through `TRYCMD=overwrite` / `TRYCMD=dump`. |
| /// |
| /// **NOTE:** We do basic search/replaces so new any new output will blindly be replaced. |
| /// |
| /// Reserved names: |
| /// - `[..]` |
| /// - `[EXE]` |
| /// - `[CWD]` |
| /// - `[ROOT]` |
| /// |
| /// ## Example |
| /// |
| /// ```rust,no_run |
| /// #[test] |
| /// fn cli_tests() { |
| /// trycmd::TestCases::new() |
| /// .case("tests/cmd/*.trycmd") |
| /// .insert_var("[VAR]", "value"); |
| /// } |
| /// ``` |
| pub fn insert_var( |
| &self, |
| var: &'static str, |
| value: impl Into<Cow<'static, str>>, |
| ) -> Result<&Self, crate::Error> { |
| self.substitutions.borrow_mut().insert(var, value)?; |
| Ok(self) |
| } |
| |
| /// Batch add variables for normalizing output |
| /// |
| /// See `insert_var`. |
| pub fn extend_vars( |
| &self, |
| vars: impl IntoIterator<Item = (&'static str, impl Into<Cow<'static, str>>)>, |
| ) -> Result<&Self, crate::Error> { |
| self.substitutions.borrow_mut().extend(vars)?; |
| Ok(self) |
| } |
| |
| /// Run tests |
| /// |
| /// This will happen on `drop` if not done explicitly |
| pub fn run(&self) { |
| self.has_run.set(true); |
| |
| let mode = parse_mode(std::env::var_os("TRYCMD").as_deref()); |
| mode.initialize().unwrap(); |
| |
| let runner = self.runner.borrow_mut().prepare(); |
| runner.run(&mode, &self.bins.borrow(), &self.substitutions.borrow()); |
| } |
| } |
| |
| impl std::panic::RefUnwindSafe for TestCases {} |
| |
| #[doc(hidden)] |
| impl Drop for TestCases { |
| fn drop(&mut self) { |
| if !self.has_run.get() && !std::thread::panicking() { |
| self.run(); |
| } |
| } |
| } |
| |
| // Filter which test cases are run by trybuild. |
| // |
| // $ cargo test -- ui trybuild=tuple_structs.rs |
| // |
| // The first argument after `--` must be the trybuild test name i.e. the name of |
| // the function that has the #[test] attribute and calls trybuild. That's to get |
| // Cargo to run the test at all. The next argument starting with `trybuild=` |
| // provides a filename filter. Only test cases whose filename contains the |
| // filter string will be run. |
| #[allow(clippy::needless_collect)] // false positive https://github.com/rust-lang/rust-clippy/issues/5991 |
| fn parse_include(args: impl IntoIterator<Item = std::ffi::OsString>) -> Option<Vec<String>> { |
| let filters = args |
| .into_iter() |
| .flat_map(std::ffi::OsString::into_string) |
| .filter_map(|arg| { |
| const PREFIX: &str = "trycmd="; |
| if let Some(remainder) = arg.strip_prefix(PREFIX) { |
| if remainder.is_empty() { |
| None |
| } else { |
| Some(remainder.to_owned()) |
| } |
| } else { |
| None |
| } |
| }) |
| .collect::<Vec<String>>(); |
| |
| if filters.is_empty() { |
| None |
| } else { |
| Some(filters) |
| } |
| } |
| |
| fn parse_mode(var: Option<&std::ffi::OsStr>) -> crate::Mode { |
| if var == Some(std::ffi::OsStr::new("overwrite")) { |
| crate::Mode::Overwrite |
| } else if var == Some(std::ffi::OsStr::new("dump")) { |
| crate::Mode::Dump("dump".into()) |
| } else { |
| crate::Mode::Fail |
| } |
| } |