Skip to content

Commit

Permalink
Fix hanging on --fail-fast CLI option usage (#242, #241)
Browse files Browse the repository at this point in the history
Additionally:
- add `--ff` CLI alias for `--fail-fast` CLI option
  • Loading branch information
tyranron authored Nov 9, 2022
1 parent 7f52d4a commit b21d03d
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 12 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ All user visible changes to `cucumber` crate will be documented in this file. Th

- Bumped up [MSRV] to 1.65 for using `let`-`else` statements.

### Added

- `--ff` CLI alias for `--fail-fast` CLI option. ([#242])

### Fixed

- `--fail-fast` CLI option causing execution to hang. ([#242], [#241])

[#241]: /../../issues/241
[#242]: /../../pull/242




Expand Down Expand Up @@ -63,7 +74,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th

- Conflicting [`Id`][0151-1]s of CLI options. ([#232], [#231])

[#231]: /../../issue/231
[#231]: /../../issues/231
[#232]: /../../pull/232
[0151-1]: https://docs.rs/clap/latest/clap/struct.Id.html

Expand Down
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ repository = "https://github.com/cucumber-rs/cucumber"
readme = "README.md"
categories = ["asynchronous", "development-tools::testing"]
keywords = ["cucumber", "testing", "bdd", "atdd", "async"]
include = ["/src/", "/tests/after_hook.rs", "/tests/json.rs", "/tests/junit.rs", "/tests/libtest.rs", "/tests/retry.rs", "/tests/wait.rs", "/LICENSE-*", "/README.md", "/CHANGELOG.md"]
include = ["/src/", "/tests/after_hook.rs", "/tests/fail_fast.rs", "/tests/json.rs", "/tests/junit.rs", "/tests/libtest.rs", "/tests/retry.rs", "/tests/wait.rs", "/LICENSE-*", "/README.md", "/CHANGELOG.md"]

[package.metadata.docs.rs]
all-features = true
Expand All @@ -39,7 +39,6 @@ output-junit = ["dep:junit-report", "timestamps"]
timestamps = []

[dependencies]
anyhow = { version = "1.0.58", optional = true }
async-trait = "0.1.43"
atty = "0.2.14"
clap = { version = "4.0.6", features = ["derive", "wrap_help"] }
Expand All @@ -58,6 +57,7 @@ regex = "1.5.5"
sealed = "0.4"

# "macros" feature dependencies.
anyhow = { version = "1.0.58", optional = true }
cucumber-codegen = { version = "0.15", path = "./codegen", optional = true }
cucumber-expressions = { version = "0.2.1", features = ["into-regex"], optional = true }
inventory = { version = "0.3", optional = true }
Expand All @@ -82,6 +82,10 @@ tokio = { version = "1.12", features = ["macros", "rt-multi-thread", "sync", "ti
name = "after_hook"
harness = false

[[test]]
name = "fail_fast"
harness = false

[[test]]
name = "json"
required-features = ["output-json"]
Expand Down
3 changes: 2 additions & 1 deletion book/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Options:
--fail-fast
Run tests until the first failure
[aliases: ff]
--retry <int>
Number of times a scenario will be retried in case of a failure
Expand Down Expand Up @@ -63,7 +65,6 @@ Options:
-h, --help
Print help information (use `-h` for a summary)
```

![record](rec/cli.gif)
Expand Down
20 changes: 12 additions & 8 deletions src/runner/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub struct Cli {
pub concurrency: Option<usize>,

/// Run tests until the first failure.
#[arg(long, global = true)]
#[arg(long, global = true, visible_alias = "ff")]
pub fail_fast: bool,

/// Number of times a scenario will be retried in case of a failure.
Expand Down Expand Up @@ -735,7 +735,7 @@ where
cli.retry = cli.retry.or(retries);
cli.retry_after = cli.retry_after.or(retry_after);
cli.retry_tag_filter = cli.retry_tag_filter.or(retry_filter);
let fail_fast = if cli.fail_fast { true } else { fail_fast };
let fail_fast = cli.fail_fast || fail_fast;
let concurrency = cli.concurrency.or(max_concurrent_scenarios);

let buffer = Features::default();
Expand Down Expand Up @@ -813,11 +813,11 @@ async fn insert_features<W, S, F>(

into.insert(f, &which_scenario, &retries, &cli).await;
}
// If the receiver end is dropped, then no one listens for events
// so we can just stop from here.
Err(e) => {
parser_errors += 1;

// If the receiver end is dropped, then no one listens for the
// events, so we can just stop from here.
if sender.unbounded_send(Err(e)).is_err() || fail_fast {
break;
}
Expand Down Expand Up @@ -920,7 +920,7 @@ async fn execute<W, Before, After>(
let (runnable, sleep) =
features.get(map_break(started_scenarios)).await;
if run_scenarios.is_empty() && runnable.is_empty() {
if features.is_finished().await {
if features.is_finished(started_scenarios.is_break()).await {
break;
}

Expand Down Expand Up @@ -975,7 +975,7 @@ async fn execute<W, Before, After>(
executor.send_event(f);
}

if fail_fast && scenario_failed {
if fail_fast && scenario_failed && !retried {
started_scenarios = ControlFlow::Break(());
}
}
Expand Down Expand Up @@ -2091,10 +2091,14 @@ impl Features {

/// Indicates whether there are more [`Feature`]s to execute.
///
/// `fail_fast` argument indicates whether not yet executed scenarios should
/// be omitted.
///
/// [`Feature`]: gherkin::Feature
async fn is_finished(&self) -> bool {
async fn is_finished(&self, fail_fast: bool) -> bool {
self.finished.load(Ordering::SeqCst)
&& self.scenarios.lock().await.values().all(Vec::is_empty)
&& (fail_fast
|| self.scenarios.lock().await.values().all(Vec::is_empty))
}
}

Expand Down
54 changes: 54 additions & 0 deletions tests/fail_fast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use cucumber::{runner, then, writer::summarize::Stats, World as _};

#[derive(Clone, Copy, cucumber::World, Debug, Default)]
struct World;

#[then(expr = "step panics")]
fn step_panics(_: &mut World) {
panic!("this is a panic message");
}

#[then(expr = "nothing happens")]
fn nothing_happens(_: &mut World) {
// noop
}

#[tokio::main]
async fn main() {
for (feat, (p_sc, f_sc, r_sc, p_st, f_st, r_st)) in [
("no_retry", (0, 1, 0, 0, 1, 0)),
("retry", (0, 1, 1, 0, 1, 2)),
("retry_delayed", (1, 1, 1, 1, 1, 2)),
] {
let writer = World::cucumber()
.with_runner(
runner::Basic::default()
.steps(World::collection())
.max_concurrent_scenarios(1)
.fail_fast(),
)
.run(format!("tests/features/fail_fast/{feat}.feature"))
.await;

assert_eq!(
writer.scenarios,
Stats {
passed: p_sc,
skipped: 0,
failed: f_sc,
retried: r_sc,
},
"Wrong Stats for Scenarios",
);
assert_eq!(
writer.steps,
Stats {
passed: p_st,
skipped: 0,
failed: f_st,
retried: r_st,
},
"Wrong Stats for Steps",
);
}
}
6 changes: 6 additions & 0 deletions tests/features/fail_fast/no_retry.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Feature: A
Scenario: 1
Then step panics

Scenario: 2
Then nothing happens
7 changes: 7 additions & 0 deletions tests/features/fail_fast/retry.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: A
@retry(2)
Scenario: 1
Then step panics

Scenario: 2
Then nothing happens
7 changes: 7 additions & 0 deletions tests/features/fail_fast/retry_delayed.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: A
@retry(2).after(2s)
Scenario: 1
Then step panics

Scenario: 2
Then nothing happens

0 comments on commit b21d03d

Please sign in to comment.