diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7c49926..43c0f866 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,7 @@ jobs: strategy: fail-fast: false matrix: - msrv: ['1.56.0'] + msrv: ['1.57.0'] crate: - cucumber-codegen - cucumber diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f332bd9..9bb3c0c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th - Moved `World` type parameter of `WriterExt` trait to methods. ([#160]) - Renamed `Normalized` and `Summarized` `Writer`s to `Normalize` and `Summarize`. ([#162]) - Removed `writer::Basic` `Default` impl and change `writer::Basic::new()` return type to `writer::Normalize`. ([#162]) +- Bump up [MSRV] to 1.57 for better error reporting in `const` assertions. ([rev]) ### Added @@ -49,6 +50,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th [#165]: /../../pull/165 [#166]: /../../pull/166 [#168]: /../../pull/168 +[rev]: /../../commit/rev-full [0110-1]: https://llg.cubic.org/docs/junit [0110-2]: https://github.com/cucumber/cucumber-json-schema @@ -273,4 +275,5 @@ All user visible changes to `cucumber` crate will be documented in this file. Th [`gherkin_rust`]: https://docs.rs/gherkin_rust [Cucumber Expressions]: https://cucumber.github.io/cucumber-expressions +[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field [Semantic Versioning 2.0.0]: https://semver.org diff --git a/Cargo.toml b/Cargo.toml index f82104b8..d88d2233 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "cucumber" version = "0.11.0-dev" edition = "2021" -rust-version = "1.56" +rust-version = "1.57" description = """\ Cucumber testing framework for Rust, with async support. \ Fully native, no external test runners or dependencies.\ diff --git a/README.md b/README.md index 9b25477d..c98c092b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Cucumber testing framework for Rust [![Documentation](https://docs.rs/cucumber/badge.svg)](https://docs.rs/cucumber) [![CI](https://github.com/cucumber-rs/cucumber/workflows/CI/badge.svg?branch=main "CI")](https://github.com/cucumber-rs/cucumber/actions?query=workflow%3ACI+branch%3Amain) -[![Rust 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg "Rust 1.56+")](https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html) +[![Rust 1.57+](https://img.shields.io/badge/rustc-1.57+-lightgray.svg "Rust 1.57+")](https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html) [![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance) An implementation of the [Cucumber] testing framework for Rust. Fully native, no external test runners or dependencies. diff --git a/codegen/CHANGELOG.md b/codegen/CHANGELOG.md index 1ebdd470..a2f303e8 100644 --- a/codegen/CHANGELOG.md +++ b/codegen/CHANGELOG.md @@ -11,6 +11,10 @@ All user visible changes to `cucumber-codegen` crate will be documented in this [Milestone](/../../milestone/3) +### BC Breaks + +- Bump up [MSRV] to 1.57 for better error reporting in `const` assertions. ([rev]) + ### Added - Unwrapping `Result`s returned by step functions. ([#151]) @@ -20,6 +24,7 @@ All user visible changes to `cucumber-codegen` crate will be documented in this [#151]: /../../pull/151 [#157]: /../../pull/157 [#168]: /../../pull/168 +[rev]: /../../commit/rev-full @@ -81,4 +86,5 @@ See `cucumber` crate [changelog](https://github.com/cucumber-rs/cucumber/blob/v0 [Cucumber Expressions]: https://cucumber.github.io/cucumber-expressions +[MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field [Semantic Versioning 2.0.0]: https://semver.org diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index e2a5e75a..d2c8fe9b 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "cucumber-codegen" version = "0.11.0-dev" # should be the same as main crate version edition = "2021" -rust-version = "1.56" +rust-version = "1.57" description = "Code generation for `cucumber` crate." license = "MIT OR Apache-2.0" authors = [ diff --git a/codegen/README.md b/codegen/README.md index 86c81db5..5913934f 100644 --- a/codegen/README.md +++ b/codegen/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/cucumber-codegen/badge.svg)](https://docs.rs/cucumber-codegen) [![CI](https://github.com/cucumber-rs/cucumber/workflows/CI/badge.svg?branch=main "CI")](https://github.com/cucumber-rs/cucumber/actions?query=workflow%3ACI+branch%3Amain) -[![Rust 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg "Rust 1.56+")](https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html) +[![Rust 1.57+](https://img.shields.io/badge/rustc-1.57+-lightgray.svg "Rust 1.57+")](https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html) [![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance) - [Changelog](https://github.com/cucumber-rs/cucumber/blob/main/codegen/CHANGELOG.md) diff --git a/codegen/src/attribute.rs b/codegen/src/attribute.rs index 92b364fb..5f1e3d7e 100644 --- a/codegen/src/attribute.rs +++ b/codegen/src/attribute.rs @@ -234,6 +234,7 @@ impl Step { Ok((func_args, addon_parsing)) } else { + // false positive: impl of `FnOnce` is not general enough #[allow(clippy::redundant_closure_for_method_calls)] let (idents, parsings): (Vec<_>, Vec<_>) = itertools::process_results( @@ -483,13 +484,14 @@ impl<'p> Parameters<'p> { | SingleExpression::Whitespaces(_) => None, }) .zip(param_tys.into_iter().map(Some).chain(iter::repeat(None))) - .filter_map(|(ast, ty)| { + .filter_map(|(ast, param_ty)| { if DEFAULT_PARAMETERS.iter().any(|s| s == &**ast) { - // If parameter is default, it's ok if there is no type + // If parameter is default, it's OK if there is no type // corresponding to it, as we know its regex. - ty.cloned() + param_ty + .cloned() .map(|ty| Ok(ParameterProvider { param: ast, ty })) - } else if let Some(ty) = ty.cloned() { + } else if let Some(ty) = param_ty.cloned() { Some(Ok(ParameterProvider { param: ast, ty })) } else { Some(Err(syn::Error::new( @@ -534,14 +536,16 @@ impl<'p> Parameters<'p> { // existing one from `const_assertions` crate, for the // purpose of better errors reporting when the assertion // fails. + let trait_with_hint = format_ident!( "UseParameterNameInsteadOf{}", to_pascal_case(name), ); + quote! { // In case we encounter default parameter, we should - // assert that corresponding type __doesn't__ implement - // a `Parameter` trait. + // assert that corresponding argument's type __doesn't__ + // implement a `Parameter` trait. #[automatically_derived] const _: fn() = || { // Generic trait with a blanket impl over `()` for @@ -572,6 +576,15 @@ impl<'p> Parameters<'p> { }; } } else { + // Here we use double escaping to properly render `{name}` + // in the assertion message of the generated code. + let assert_msg = format!( + "Type `{}` doesn't implement a custom parameter \ + `{{{{{}}}}}`", + quote! { #ty }, + name, + ); + quote! { // In case we encounter a custom parameter, we should // assert that the corresponding type implements @@ -579,18 +592,13 @@ impl<'p> Parameters<'p> { // TODO: Panic here, once `const_panic` is stabilized. // https://github.com/rust-lang/rust/pull/89508 #[automatically_derived] - #[allow(unknown_lints, eq_op)] - const _: [ - (); - 0 - !{ - const ASSERT: bool = - ::cucumber::codegen::str_eq( - <#ty as ::cucumber::Parameter>::NAME, - #name, - ); - ASSERT - } as usize - ] = []; + const _: () = ::std::assert!( + ::cucumber::codegen::str_eq( + <#ty as ::cucumber::Parameter>::NAME, + #name, + ), + #assert_msg, + ); } } }) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 49696a1e..585d1478 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -32,6 +32,7 @@ clippy::decimal_literal_representation, clippy::else_if_without_else, clippy::empty_line_after_outer_attr, + clippy::equatable_if_let, clippy::exit, clippy::expect_used, clippy::fallible_impl_from, @@ -57,6 +58,7 @@ clippy::rc_buffer, clippy::rc_mutex, clippy::rest_pat_in_fully_bound_structs, + clippy::same_name_method, clippy::shadow_unrelated, clippy::str_to_string, clippy::string_add, diff --git a/codegen/src/world_init.rs b/codegen/src/world_init.rs index ba991467..6656b92d 100644 --- a/codegen/src/world_init.rs +++ b/codegen/src/world_init.rs @@ -15,7 +15,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; /// Generates code of `#[derive(WorldInit)]` macro expansion. -#[allow(clippy::similar_names)] +#[allow(clippy::similar_names)] // because of `when_ty` vs `then_ty` pub(crate) fn derive( input: TokenStream, steps: &[&str], diff --git a/src/cli.rs b/src/cli.rs index 3f75e85a..64b17b0a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -158,6 +158,7 @@ where pub struct Empty { /// This field exists only because [`StructOpt`] derive macro doesn't /// support unit structs. + #[allow(dead_code)] #[structopt(skip)] skipped: (), } diff --git a/src/cucumber.rs b/src/cucumber.rs index 7105291f..01765117 100644 --- a/src/cucumber.rs +++ b/src/cucumber.rs @@ -813,29 +813,29 @@ where .. } = self.cli.unwrap_or_else(cli::Opts::<_, _, _, _>::from_args); - let filter = move |f: &gherkin::Feature, - r: Option<&gherkin::Rule>, - s: &gherkin::Scenario| { + let filter = move |feat: &gherkin::Feature, + rule: Option<&gherkin::Rule>, + scenario: &gherkin::Scenario| { re_filter.as_ref().map_or_else( || { tags_filter.as_ref().map_or_else( - || filter(f, r, s), + || filter(feat, rule, scenario), |tags| { // The order `Feature` -> `Rule` -> `Scenario` // matters here. tags.eval( - f.tags + feat.tags .iter() .chain( - r.into_iter() + rule.into_iter() .flat_map(|r| r.tags.iter()), ) - .chain(s.tags.iter()), + .chain(scenario.tags.iter()), ) }, ) }, - |re| re.is_match(&s.name), + |re| re.is_match(&scenario.name), ) }; @@ -850,16 +850,16 @@ where let filtered = features.map(move |feature| { let mut feature = feature?; - let scenarios = mem::take(&mut feature.scenarios); - feature.scenarios = scenarios + let feat_scenarios = mem::take(&mut feature.scenarios); + feature.scenarios = feat_scenarios .into_iter() .filter(|s| filter(&feature, None, s)) .collect(); let mut rules = mem::take(&mut feature.rules); for r in &mut rules { - let scenarios = mem::take(&mut r.scenarios); - r.scenarios = scenarios + let rule_scenarios = mem::take(&mut r.scenarios); + r.scenarios = rule_scenarios .into_iter() .filter(|s| filter(&feature, Some(r), s)) .collect(); diff --git a/src/event.rs b/src/event.rs index ea2ffa87..0925ebdf 100644 --- a/src/event.rs +++ b/src/event.rs @@ -191,7 +191,6 @@ impl Cucumber { ) -> Self { Self::Feature( feat, - #[allow(clippy::option_if_let_else)] // use of moved value: `event` if let Some(r) = rule { Feature::Rule(r, Rule::Scenario(scenario, event)) } else { diff --git a/src/lib.rs b/src/lib.rs index 75a36786..45b7ea8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ clippy::decimal_literal_representation, clippy::else_if_without_else, clippy::empty_line_after_outer_attr, + clippy::equatable_if_let, clippy::exit, clippy::expect_used, clippy::fallible_impl_from, @@ -59,6 +60,7 @@ clippy::rc_buffer, clippy::rc_mutex, clippy::rest_pat_in_fully_bound_structs, + clippy::same_name_method, clippy::shadow_unrelated, clippy::str_to_string, clippy::string_add, diff --git a/src/parser/basic.rs b/src/parser/basic.rs index 8d30a7d8..a64539ee 100644 --- a/src/parser/basic.rs +++ b/src/parser/basic.rs @@ -84,7 +84,7 @@ impl> Parser for Basic { .collect::>() }; - let get_path = || { + let get_features_path = || { let path = path.as_ref(); path.canonicalize() .or_else(|_| { @@ -106,26 +106,26 @@ impl> Parser for Basic { let features = if let Some(walker) = cli.features { walk(walker.0) } else { - let path = match get_path() { - Ok(path) => path, + let feats_path = match get_features_path() { + Ok(p) => p, Err(e) => return vec![Err(e.into())], }; - if path.is_file() { + if feats_path.is_file() { let env = self .language .as_ref() .and_then(|l| GherkinEnv::new(l).ok()) .unwrap_or_default(); - vec![gherkin::Feature::parse_path(path, env)] + vec![gherkin::Feature::parse_path(feats_path, env)] } else { - let walker = GlobWalkerBuilder::new(path, "*.feature") + let w = GlobWalkerBuilder::new(feats_path, "*.feature") .case_insensitive(true) .build() .unwrap_or_else(|e| { unreachable!("GlobWalkerBuilder panicked: {}", e) }); - walk(walker) + walk(w) } }; diff --git a/src/runner/basic.rs b/src/runner/basic.rs index bc9f4352..3c3ef998 100644 --- a/src/runner/basic.rs +++ b/src/runner/basic.rs @@ -708,8 +708,8 @@ where let rule_background = rule .as_ref() - .map(|rule| { - rule.background + .map(|r| { + r.background .as_ref() .map(|b| b.steps.iter().map(|s| Arc::new(s.clone()))) .into_iter() @@ -861,6 +861,7 @@ where } }; + #[allow(clippy::shadow_unrelated)] match fut.await { Ok(world) => { self.send(event::Cucumber::scenario( @@ -956,6 +957,7 @@ where } }; + #[allow(clippy::shadow_unrelated)] match run.await { Ok((Some(captures), Some(world))) => { self.send(passed(step, captures)); @@ -1047,16 +1049,18 @@ where } let mut started_rules = Vec::new(); - for (feature, rule) in runnable + for (feat, rule) in runnable .iter() - .filter_map(|(f, r, _)| r.clone().map(|r| (Arc::clone(f), r))) + .filter_map(|(feat, rule, _)| { + rule.clone().map(|r| (Arc::clone(feat), r)) + }) .dedup() { let _ = self .rule_scenarios_count - .entry((feature.path.clone(), Arc::clone(&rule))) + .entry((feat.path.clone(), Arc::clone(&rule))) .or_insert_with(|| { - started_rules.push((feature, rule)); + started_rules.push((feat, rule)); 0.into() }); } @@ -1172,11 +1176,11 @@ impl Features { .map(|s| (&feature, Some(r), s)) .collect::>() })) - .map(|(f, r, s)| { + .map(|(feat, rule, scenario)| { ( - Arc::new(f.clone()), - r.map(|r| Arc::new(r.clone())), - Arc::new(s.clone()), + Arc::new(feat.clone()), + rule.map(|r| Arc::new(r.clone())), + Arc::new(scenario.clone()), ) }) .into_group_map_by(|(f, r, s)| { diff --git a/src/step.rs b/src/step.rs index 1ec15fa9..ebd6bf81 100644 --- a/src/step.rs +++ b/src/step.rs @@ -161,7 +161,7 @@ impl Collection { StepType::Then => &self.then, }; - let mut matches = collection + let mut captures = collection .iter() .filter_map(|((re, loc), step_fn)| { let mut captures = re.capture_locations(); @@ -170,13 +170,13 @@ impl Collection { }) .collect::>(); - let (_, _, whole_match, captures, step_fn) = match matches.len() { + let (_, _, whole_match, captures, step_fn) = match captures.len() { 0 => return Ok(None), // Instead of `.unwrap()` to avoid documenting `# Panics` section. - 1 => matches.pop().unwrap_or_else(|| unreachable!()), + 1 => captures.pop().unwrap_or_else(|| unreachable!()), _ => { return Err(AmbiguousMatchError { - possible_matches: matches + possible_matches: captures .into_iter() .map(|(re, loc, ..)| (re.clone(), *loc)) .collect(), diff --git a/src/writer/basic.rs b/src/writer/basic.rs index 6fe1e8e5..6cda1790 100644 --- a/src/writer/basic.rs +++ b/src/writer/basic.rs @@ -582,10 +582,10 @@ impl Basic { )); let step_value = captures.map_or_else( || self.styles.err(&step.value), - |captures| { + |capts| { format_captures( &step.value, - captures, + capts, |v| self.styles.err(v), |v| self.styles.err(self.styles.bold(v)), ) @@ -819,10 +819,10 @@ impl Basic { )); let step_value = captures.map_or_else( || self.styles.err(&step.value), - |captures| { + |capts| { format_captures( &step.value, - captures, + capts, |v| self.styles.err(v), |v| self.styles.err(self.styles.bold(v)), ) @@ -905,8 +905,8 @@ fn format_table(table: &gherkin::Table, indent: usize) -> String { .rows .iter() .fold(None, |mut acc: Option>, row| { - if let Some(acc) = acc.as_mut() { - for (cell, max_len) in row.iter().zip(acc) { + if let Some(existing_len) = acc.as_mut() { + for (cell, max_len) in row.iter().zip(existing_len) { *max_len = cmp::max(*max_len, cell.len()); } } else { diff --git a/src/writer/fail_on_skipped.rs b/src/writer/fail_on_skipped.rs index 049db49d..e9258d58 100644 --- a/src/writer/fail_on_skipped.rs +++ b/src/writer/fail_on_skipped.rs @@ -63,7 +63,7 @@ where async fn handle_event( &mut self, - ev: parser::Result>>, + event: parser::Result>>, cli: &Self::Cli, ) { use event::{ @@ -71,17 +71,17 @@ where }; let map_failed = |f: Arc<_>, r: Option>, sc: Arc<_>, st: _| { - let event = if (self.should_fail)(&f, r.as_deref(), &sc) { + let ev = if (self.should_fail)(&f, r.as_deref(), &sc) { Step::Failed(None, None, Panic(Arc::new("not allowed to skip"))) } else { Step::Skipped }; - Cucumber::scenario(f, r, sc, Scenario::Step(st, event)) + Cucumber::scenario(f, r, sc, Scenario::Step(st, ev)) }; - let ev = ev.map(|ev| { - ev.map(|ev| match ev { + let event = event.map(|outer| { + outer.map(|ev| match ev { Cucumber::Feature( f, Feature::Rule( @@ -99,7 +99,7 @@ where }) }); - self.writer.handle_event(ev, cli).await; + self.writer.handle_event(event, cli).await; } } diff --git a/src/writer/json.rs b/src/writer/json.rs index dc973673..3f23fc8c 100644 --- a/src/writer/json.rs +++ b/src/writer/json.rs @@ -65,7 +65,45 @@ impl Writer for Json { event: parser::Result>>, _: &Self::Cli, ) { - self.handle_event(event); + use event::{Cucumber, Rule}; + + match event.map(event::Event::split) { + Err(parser::Error::Parsing(e)) => { + let feature = Feature::parsing_err(&e); + self.features.push(feature); + } + Err(parser::Error::ExampleExpansion(e)) => { + let feature = Feature::example_expansion_err(&e); + self.features.push(feature); + } + Ok(( + Cucumber::Feature(f, event::Feature::Scenario(sc, ev)), + meta, + )) => { + self.handle_scenario_event(&f, None, &sc, ev, meta); + } + Ok(( + Cucumber::Feature( + f, + event::Feature::Rule(r, Rule::Scenario(sc, ev)), + ), + meta, + )) => { + self.handle_scenario_event(&f, Some(&r), &sc, ev, meta); + } + Ok((Cucumber::Finished, _)) => { + self.output + .write_all( + serde_json::to_string(&self.features) + .unwrap_or_else(|e| { + panic!("Failed to serialize JSON: {}", e) + }) + .as_bytes(), + ) + .unwrap_or_else(|e| panic!("Failed to write JSON: {}", e)); + } + _ => {} + } } } @@ -116,52 +154,6 @@ impl Json { } } - /// Handles the given [`event::Cucumber`]. - fn handle_event( - &mut self, - event: parser::Result>>, - ) { - use event::{Cucumber, Rule}; - - match event.map(event::Event::split) { - Err(parser::Error::Parsing(e)) => { - let feature = Feature::parsing_err(&e); - self.features.push(feature); - } - Err(parser::Error::ExampleExpansion(e)) => { - let feature = Feature::example_expansion_err(&e); - self.features.push(feature); - } - Ok(( - Cucumber::Feature(f, event::Feature::Scenario(sc, ev)), - meta, - )) => { - self.handle_scenario_event(&f, None, &sc, ev, meta); - } - Ok(( - Cucumber::Feature( - f, - event::Feature::Rule(r, Rule::Scenario(sc, ev)), - ), - meta, - )) => { - self.handle_scenario_event(&f, Some(&r), &sc, ev, meta); - } - Ok((Cucumber::Finished, _)) => { - self.output - .write_all( - serde_json::to_string(&self.features) - .unwrap_or_else(|e| { - panic!("Failed to serialize JSON: {}", e) - }) - .as_bytes(), - ) - .unwrap_or_else(|e| panic!("Failed to write JSON: {}", e)); - } - _ => {} - } - } - /// Handles the given [`event::Scenario`]. fn handle_scenario_event( &mut self, diff --git a/src/writer/normalize.rs b/src/writer/normalize.rs index fcbf5f0f..0e33ec86 100644 --- a/src/writer/normalize.rs +++ b/src/writer/normalize.rs @@ -68,7 +68,7 @@ impl> Writer for Normalize { async fn handle_event( &mut self, - ev: parser::Result>>, + event: parser::Result>>, cli: &Self::Cli, ) { use event::{Cucumber, Feature, Rule}; @@ -78,11 +78,11 @@ impl> Writer for Normalize { // This is done to avoid panic if this `Writer` happens to be wrapped // inside `writer::Repeat` or similar. if self.queue.is_finished_and_emitted() { - self.writer.handle_event(ev, cli).await; + self.writer.handle_event(event, cli).await; return; } - match ev.map(Event::split) { + match event.map(Event::split) { res @ (Err(_) | Ok((Cucumber::Started, _))) => { self.writer .handle_event(res.map(|(ev, meta)| meta.insert(ev)), cli) @@ -524,11 +524,11 @@ impl FeatureQueue { scenario: Arc, ev: Event>, ) { - if let Some(rule) = rule { + if let Some(r) = rule { match self .queue - .get_mut(&Either::Left(Arc::clone(&rule))) - .unwrap_or_else(|| panic!("No Rule {}", rule.name)) + .get_mut(&Either::Left(Arc::clone(&r))) + .unwrap_or_else(|| panic!("No Rule {}", r.name)) { Either::Left(rules) => rules .queue diff --git a/src/writer/repeat.rs b/src/writer/repeat.rs index 2d438c39..837aa669 100644 --- a/src/writer/repeat.rs +++ b/src/writer/repeat.rs @@ -57,17 +57,17 @@ where async fn handle_event( &mut self, - ev: parser::Result>>, + event: parser::Result>>, cli: &Self::Cli, ) { - if (self.filter)(&ev) { - self.events.push(ev.clone()); + if (self.filter)(&event) { + self.events.push(event.clone()); } let is_finished = - matches!(ev.as_deref(), Ok(event::Cucumber::Finished)); + matches!(event.as_deref(), Ok(event::Cucumber::Finished)); - self.writer.handle_event(ev, cli).await; + self.writer.handle_event(event, cli).await; if is_finished { for ev in mem::take(&mut self.events) {