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

Declare CLI options as global #216

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ All user visible changes to `cucumber` crate will be documented in this file. Th

### BC Breaks

- Bump up [MSRV] to 1.61 for more clever support of [Cargo feature]s and simplified codegen.
- Bump up [MSRV] to 1.61 for more clever support of [Cargo feature]s and simplified codegen. ([fbd08ec2], [cf055ac0])

### Changed

- Provided default CLI options are now global (allowed to be specified after custom subcommands). ([#216], [#215])

[#215]: /../../issues/215
[#216]: /../../pull/216
[cf055ac0]: /../../commit/cf055ac06c7b72f572882ce15d6a60da92ad60a0
[fbd08ec2]: /../../commit/fbd08ec24dbd036c89f5f0af4d936b616790a166



Expand Down
121 changes: 121 additions & 0 deletions book/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,123 @@ async fn main() {



## Aliasing

[Cargo alias] is a neat way to define shortcuts for regularly used customized tests running commands.

```rust
# use std::{convert::Infallible, time::Duration};
#
# use async_trait::async_trait;
# use cucumber::{cli, given, then, when, World, WorldInit};
# use futures::FutureExt as _;
# use tokio::time::sleep;
#
# #[derive(Debug, Default)]
# struct Animal {
# pub hungry: bool,
# }
#
# impl Animal {
# fn feed(&mut self) {
# self.hungry = false;
# }
# }
#
# #[derive(Debug, WorldInit)]
# pub struct AnimalWorld {
# cat: Animal,
# }
#
# #[async_trait(?Send)]
# impl World for AnimalWorld {
# type Error = Infallible;
#
# async fn new() -> Result<Self, Infallible> {
# Ok(Self {
# cat: Animal::default(),
# })
# }
# }
#
# #[given(regex = r"^a (hungry|satiated) cat$")]
# async fn hungry_cat(world: &mut AnimalWorld, state: String) {
# match state.as_str() {
# "hungry" => world.cat.hungry = true,
# "satiated" => world.cat.hungry = false,
# _ => unreachable!(),
# }
# }
#
# #[when("I feed the cat")]
# async fn feed_cat(world: &mut AnimalWorld) {
# world.cat.feed();
# }
#
# #[then("the cat is not hungry")]
# async fn cat_is_fed(world: &mut AnimalWorld) {
# assert!(!world.cat.hungry);
# }
#
#[derive(clap::Args)]
struct CustomOpts {
#[clap(subcommand)]
command: Option<SubCommand>,
}

#[derive(clap::Subcommand)]
enum SubCommand {
Smoke(Smoke),
}

#[derive(clap::Args)]
struct Smoke {
/// Additional time to wait in before hook.
#[clap(
long,
parse(try_from_str = humantime::parse_duration)
)]
pre_pause: Option<Duration>,
}

#[tokio::main]
async fn main() {
let opts = cli::Opts::<_, _, _, CustomOpts>::parsed();

let pre_pause = if let Some(SubCommand::Smoke(Smoke { pre_pause })) =
opts.custom.command
{
pre_pause
} else {
None
}
.unwrap_or_default();

AnimalWorld::cucumber()
.before(move |_, _, _, _| sleep(pre_pause).boxed_local())
.with_cli(opts)
.run_and_exit("tests/features/book/cli.feature")
.await;
}
```

The alias should be specified in `.cargo/config.toml` file of the project:
```yaml
[alias]
smoke = "test -p cucumber --test cli -- smoke --pre-pause=5s -vv --fail-fast"
```

Now it can be used as:
```bash
cargo smoke
cargo smoke --tags=@hungry
```

> __NOTE__: The default CLI options may be specified after a custom subcommand, because they are defined as [global][1] ones. This may be applied to custom CLI options too, if necessary.




[`cli::Compose`]: https://docs.rs/cucumber/*/cucumber/cli/struct.Compose.html
[`cli::Empty`]: https://docs.rs/cucumber/*/cucumber/cli/struct.Empty.html
[`cucumber`]: https://docs.rs/cucumber
Expand All @@ -162,3 +279,7 @@ async fn main() {
[`Runner::Cli`]: https://docs.rs/cucumber/*/cucumber/trait.Runner.html#associatedtype.Cli
[`Writer`]: architecture/writer.md
[`Writer::Cli`]: https://docs.rs/cucumber/*/cucumber/trait.Writer.html#associatedtype.Cli

[Cargo alias]: https://doc.rust-lang.org/cargo/reference/config.html#alias

[1]: https://docs.rs/clap/latest/clap/struct.Arg.html#method.global
5 changes: 4 additions & 1 deletion codegen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ All user visible changes to `cucumber-codegen` crate will be documented in this

### BC Breaks

- Bump up [MSRV] to 1.61 for more clever support of [Cargo feature]s and simplified codegen.
- Bump up [MSRV] to 1.61 for more clever support of [Cargo feature]s and simplified codegen. ([fbd08ec2], [cf055ac0])

[cf055ac0]: /../../commit/cf055ac06c7b72f572882ce15d6a60da92ad60a0
[fbd08ec2]: /../../commit/fbd08ec24dbd036c89f5f0af4d936b616790a166



Expand Down
6 changes: 4 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ where
short = 'n',
long = "name",
name = "regex",
visible_alias = "scenario-name"
visible_alias = "scenario-name",
global = true
)]
pub re_filter: Option<Regex>,

Expand All @@ -118,7 +119,8 @@ where
short = 't',
long = "tags",
name = "tagexpr",
conflicts_with = "regex"
conflicts_with = "regex",
global = true
)]
pub tags_filter: Option<TagOperation>,

Expand Down
2 changes: 1 addition & 1 deletion src/parser/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use super::{Error as ParseError, Parser};
pub struct Cli {
/// Glob pattern to look for feature files with. By default, looks for
/// `*.feature`s in the path configured tests runner.
#[clap(long = "input", short = 'i', name = "glob")]
#[clap(long = "input", short = 'i', name = "glob", global = true)]
pub features: Option<Walker>,
}

Expand Down
4 changes: 2 additions & 2 deletions src/runner/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ use crate::{
pub struct Cli {
/// Number of scenarios to run concurrently. If not specified, uses the
/// value configured in tests runner, or 64 by default.
#[clap(long, short, name = "int")]
#[clap(long, short, name = "int", global = true)]
pub concurrency: Option<usize>,

/// Run tests until the first failure.
#[clap(long)]
#[clap(long, global = true)]
pub fail_fast: bool,
}

Expand Down
9 changes: 7 additions & 2 deletions src/writer/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ pub struct Cli {
///
/// `-v` is default verbosity, `-vv` additionally outputs world on failed
/// steps, `-vvv` additionally outputs step's doc string (if present).
#[clap(short, parse(from_occurrences))]
#[clap(short, parse(from_occurrences), global = true)]
pub verbose: u8,

/// Coloring policy for a console output.
#[clap(long, name = "auto|always|never", default_value = "auto")]
#[clap(
long,
name = "auto|always|never",
default_value = "auto",
global = true
)]
pub color: Coloring,
}

Expand Down
2 changes: 1 addition & 1 deletion src/writer/junit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub struct Cli {
///
/// `0` is default verbosity, `1` additionally outputs world on failed
/// steps.
#[clap(long = "junit-v", name = "0|1")]
#[clap(long = "junit-v", name = "0|1", global = true)]
pub verbose: Option<u8>,
}

Expand Down
113 changes: 113 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::{convert::Infallible, panic::AssertUnwindSafe};

use async_trait::async_trait;
use clap::Parser;
use cucumber::{cli, given, WorldInit};
use futures::FutureExt as _;

#[derive(cli::Args)]
struct CustomCli {
#[clap(subcommand)]
command: Option<SubCommand>,
}

#[derive(clap::Subcommand)]
enum SubCommand {
Smoke(Smoke),
}

#[derive(cli::Args)]
struct Smoke {
#[clap(long)]
report_name: String,
}

#[derive(Clone, Copy, Debug, WorldInit)]
struct World;

#[async_trait(?Send)]
impl cucumber::World for World {
type Error = Infallible;

async fn new() -> Result<Self, Self::Error> {
Ok(World)
}
}

#[given("an invalid step")]
fn invalid_step(_world: &mut World) {
assert!(false);
}

// This test uses a subcommand with the global option `--tags` to filter on two
// failing tests and verifies that the error output contains 2 failing steps.
#[tokio::test]
async fn tags_option_filters_all_scenarios_with_subcommand() {
let cli = cli::Opts::<_, _, _, CustomCli>::try_parse_from(&[
"test",
"smoke",
r#"--report-name="smoke.report""#,
"--tags=@all",
])
.expect("Invalid command line");

let res = World::cucumber()
.with_cli(cli)
.run_and_exit("tests/features/cli");

let err = AssertUnwindSafe(res)
.catch_unwind()
.await
.expect_err("should err");
let err = err.downcast_ref::<String>().unwrap();

assert_eq!(err, "2 steps failed");
}

// This test uses a subcommand with the global option `--tags` to filter on one
// failing test and verifies that the error output contains 1 failing step.
#[tokio::test]
async fn tags_option_filters_scenario1_with_subcommand() {
let cli = cli::Opts::<_, _, _, CustomCli>::try_parse_from(&[
"test",
"smoke",
r#"--report-name="smoke.report""#,
"--tags=@scenario-1",
])
.expect("Invalid command line");

let res = World::cucumber()
.with_cli(cli)
.run_and_exit("tests/features/cli");

let err = AssertUnwindSafe(res)
.catch_unwind()
.await
.expect_err("should err");
let err = err.downcast_ref::<String>().unwrap();

assert_eq!(err, "1 step failed");
}

// This test verifies that the global option `--tags` is still available without
// subcommands and that the error output contains 1 failing step.
#[tokio::test]
async fn tags_option_filters_scenario1_no_subcommand() {
let cli = cli::Opts::<_, _, _, CustomCli>::try_parse_from(&[
"test",
"--tags=@scenario-1",
])
.expect("Invalid command line");

let res = World::cucumber()
.with_cli(cli)
.run_and_exit("tests/features/cli");

let err = AssertUnwindSafe(res)
.catch_unwind()
.await
.expect_err("should err");
let err = err.downcast_ref::<String>().unwrap();

assert_eq!(err, "1 step failed");
}
10 changes: 10 additions & 0 deletions tests/features/cli/subcomand_global_option.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Feature: Global option `--tags` with subcommands

@scenario-1 @all
Scenario: Two invalid steps
Given an invalid step
And an invalid step

@scenario-2 @all
Scenario: One invalid step
Given an invalid step