diff --git a/cargo-insta/integration-tests/Cargo.toml b/cargo-insta/integration-tests/Cargo.toml index 70fd1867..1163e476 100644 --- a/cargo-insta/integration-tests/Cargo.toml +++ b/cargo-insta/integration-tests/Cargo.toml @@ -8,6 +8,6 @@ edition = "2018" [dependencies] dircpy = "0.3.4" -insta = { version = "1.1.0", path = "../..", features = ["json", "yaml", "redactions", "glob"] } +insta = { path = "../..", features = ["json", "yaml", "redactions", "glob"] } walkdir = "2.3.1" serde = { version = "1.0.117", features = ["derive"] } diff --git a/cargo-insta/src/cli.rs b/cargo-insta/src/cli.rs index 4b32668f..54412945 100644 --- a/cargo-insta/src/cli.rs +++ b/cargo-insta/src/cli.rs @@ -140,6 +140,9 @@ pub struct TestCommand { /// Delete unreferenced snapshots after the test run. #[structopt(long)] pub delete_unreferenced_snapshots: bool, + /// Filters to apply to the insta glob feature. + #[structopt(long)] + pub glob_filter: Vec, /// Do not pass the quiet flag (`-q`) to tests. #[structopt(short = "Q", long)] pub no_quiet: bool, @@ -558,6 +561,22 @@ fn test_run(mut cmd: TestCommand, color: &str) -> Result<(), Box> { if cmd.force_update_snapshots { proc.env("INSTA_FORCE_UPDATE_SNAPSHOTS", "1"); } + + let glob_filter = + cmd.glob_filter + .iter() + .map(|x| x.as_str()) + .fold(String::new(), |mut s, item| { + if !s.is_empty() { + s.push(';'); + } + s.push_str(item); + s + }); + if !glob_filter.is_empty() { + proc.env("INSTA_GLOB_FILTER", glob_filter); + } + if cmd.release { proc.arg("--release"); } diff --git a/src/glob.rs b/src/glob.rs index 257a243b..6c247e5e 100644 --- a/src/glob.rs +++ b/src/glob.rs @@ -1,10 +1,27 @@ +use std::env; use std::path::Path; -use globset::GlobBuilder; +use globset::{GlobBuilder, GlobMatcher}; +use once_cell::sync::Lazy; use walkdir::WalkDir; use crate::settings::Settings; +static GLOB_FILTER: Lazy> = Lazy::new(|| { + env::var("INSTA_GLOB_FILTER") + .unwrap_or_default() + .split(';') + .filter(|x| !x.is_empty()) + .filter_map(|filter| { + GlobBuilder::new(filter) + .case_insensitive(true) + .build() + .ok() + .map(|x| x.compile_matcher()) + }) + .collect() +}); + pub fn glob_exec(base: &Path, pattern: &str, mut f: F) { let glob = GlobBuilder::new(pattern) .case_insensitive(true) @@ -25,10 +42,17 @@ pub fn glob_exec(base: &Path, pattern: &str, mut f: F) { continue; } + glob_found_matches = true; + + // if there is a glob filter, skip if it does not match this path + if !GLOB_FILTER.is_empty() && !GLOB_FILTER.iter().any(|x| x.is_match(stripped_path)) { + eprintln!("Skipping {} due to glob filter", stripped_path.display()); + continue; + } + settings.set_input_file(&path); settings.set_snapshot_suffix(path.file_name().unwrap().to_str().unwrap()); - glob_found_matches = true; settings.bind(|| { f(path); }); diff --git a/src/macros.rs b/src/macros.rs index 3aab6ca5..d420e98c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -467,7 +467,27 @@ macro_rules! with_settings { /// Executes a closure for all input files matching a glob. /// -/// The closure is passed the path to the file. +/// The closure is passed the path to the file. You can use [`std::fs::read_to_string`] +/// or similar functions to load the file and process it. +/// +/// ``` +/// # use insta::{assert_snapshot, glob, Settings}; +/// # let mut settings = Settings::clone_current(); +/// # settings.set_allow_empty_glob(true); +/// # let _dropguard = settings.bind_to_scope(); +/// use std::fs; +/// +/// glob!("inputs/*.txt", |path| { +/// let input = fs::read_to_string(path).unwrap(); +/// assert_snapshot!(input.to_uppercase()); +/// }); +/// ``` +/// +/// The `INSTA_GLOB_FILTER` environment variable can be set to only execute certain files. +/// The format of the filter is a semicolon separated filter. For instance by setting +/// `INSTA_GLOB_FILTER` to `foo-*txt;bar-*.txt` only files starting with `foo-` or `bar-` +/// end ending in `.txt` will be executed. When using `cargo-insta` the `--glob-filter` +/// option can be used instead. #[cfg(feature = "glob")] #[cfg_attr(docsrs, doc(cfg(feature = "glob")))] #[macro_export]