Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer glob failures #284

Merged
merged 4 commits into from Sep 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/glob.rs
@@ -1,11 +1,23 @@
use std::env;
use std::path::Path;
use std::sync::Mutex;

use globset::{GlobBuilder, GlobMatcher};
use once_cell::sync::Lazy;
use walkdir::WalkDir;

use crate::settings::Settings;
use crate::utils::style;

pub(crate) struct GlobCollector {
pub(crate) fail_fast: bool,
pub(crate) failed: usize,
pub(crate) show_insta_hint: bool,
}

// the glob stack holds failure count + an indication if cargo insta review
// should be run.
pub(crate) static GLOB_STACK: Lazy<Mutex<Vec<GlobCollector>>> = Lazy::new(Mutex::default);

static GLOB_FILTER: Lazy<Vec<GlobMatcher>> = Lazy::new(|| {
env::var("INSTA_GLOB_FILTER")
Expand Down Expand Up @@ -34,6 +46,12 @@ pub fn glob_exec<F: FnMut(&Path)>(base: &Path, pattern: &str, mut f: F) {
let mut glob_found_matches = false;
let mut settings = Settings::clone_current();

GLOB_STACK.lock().unwrap().push(GlobCollector {
failed: 0,
show_insta_hint: false,
fail_fast: std::env::var("INSTA_GLOB_FAIL_FAST").as_deref() == Ok("1"),
});

for file in walker {
let file = file.unwrap();
let path = file.path();
Expand All @@ -58,7 +76,28 @@ pub fn glob_exec<F: FnMut(&Path)>(base: &Path, pattern: &str, mut f: F) {
});
}

let top = GLOB_STACK.lock().unwrap().pop().unwrap();
if !glob_found_matches && !settings.allow_empty_glob() {
panic!("the glob! macro did not match any files.");
}

if top.failed > 0 {
if top.show_insta_hint {
println!(
"{hint}",
hint = style("To update snapshots run `cargo insta review`").dim(),
);
}
if top.failed > 1 {
println!(
"{hint}",
hint = style("To enable fast failing for glob! export INSTA_GLOB_FAIL_FAST=1 as environment variable.").dim()
);
}
panic!(
"glob! resulted in {} snapshot assertion failure{}s",
top.failed,
if top.failed == 1 { "" } else { "s" },
);
}
}
5 changes: 5 additions & 0 deletions src/macros.rs
Expand Up @@ -488,6 +488,11 @@ macro_rules! with_settings {
/// `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.
///
/// Another effect of the globbing system is that snapshot failures within the glob macro
/// are deferred until the end of of it. In other words this means that each snapshot
/// assertion within the `glob!` block are reported. It can be disabled by setting
/// `INSTA_GLOB_FAIL_FAST` environment variable to `1`.
#[cfg(feature = "glob")]
#[cfg_attr(docsrs, doc(cfg(feature = "glob")))]
#[macro_export]
Expand Down
59 changes: 57 additions & 2 deletions src/runtime.rs
Expand Up @@ -436,9 +436,39 @@ fn print_snapshot_info(ctx: &SnapshotAssertionContext, new_snapshot: &Snapshot)
}
}

#[cfg(feature = "glob")]
macro_rules! print_or_panic {
($fail_fast:expr, $($tokens:tt)*) => {{
if (!$fail_fast) {
eprintln!($($tokens)*);
eprintln!();
} else {
panic!($($tokens)*);
}
}}
}

/// Finalizes the assertion based on the update result.
fn finalize_assertion(ctx: &SnapshotAssertionContext, update_result: SnapshotUpdate) {
if update_result == SnapshotUpdate::NewFile
// if we are in glob mode, we want to adjust the finalization
// so that we do not show the hints immediately.
let fail_fast = {
#[cfg(feature = "glob")]
{
if let Some(top) = crate::glob::GLOB_STACK.lock().unwrap().last() {
top.fail_fast
} else {
true
}
}
#[cfg(not(feature = "glob"))]
{
true
}
};

if fail_fast
&& update_result == SnapshotUpdate::NewFile
&& get_output_behavior() != OutputBehavior::Nothing
&& !ctx.is_doctest
{
Expand All @@ -449,7 +479,7 @@ fn finalize_assertion(ctx: &SnapshotAssertionContext, update_result: SnapshotUpd
}

if update_result != SnapshotUpdate::InPlace && !force_pass() {
if get_output_behavior() != OutputBehavior::Nothing {
if fail_fast && get_output_behavior() != OutputBehavior::Nothing {
println!(
"{hint}",
hint = style(
Expand All @@ -458,6 +488,31 @@ fn finalize_assertion(ctx: &SnapshotAssertionContext, update_result: SnapshotUpd
.dim(),
);
}

// if we are in glob mode, count the failures and print the
// errors instead of panicking. The glob will then panic at
// the end.
#[cfg(feature = "glob")]
{
let mut stack = crate::glob::GLOB_STACK.lock().unwrap();
if let Some(glob_collector) = stack.last_mut() {
glob_collector.failed += 1;
if update_result == SnapshotUpdate::NewFile
&& get_output_behavior() != OutputBehavior::Nothing
{
glob_collector.show_insta_hint = true;
}

print_or_panic!(
fail_fast,
"snapshot assertion from glob for '{}' failed in line {}",
ctx.snapshot_name.as_deref().unwrap_or("unnamed snapshot"),
ctx.assertion_line
);
return;
}
}

panic!(
"snapshot assertion for '{}' failed in line {}",
ctx.snapshot_name.as_deref().unwrap_or("unnamed snapshot"),
Expand Down