Skip to content

Commit

Permalink
Fix conflicting Ids of CLI options (#232, #231)
Browse files Browse the repository at this point in the history
- document in Book how to deal with actually overlapping CLI options
  • Loading branch information
ilslv committed Oct 12, 2022
1 parent c86c64b commit f81c556
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 7 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ All user visible changes to `cucumber` crate will be documented in this file. Th



## [0.15.1] · 2022-10-??
[0.15.1]: /../../tree/v0.15.1

[Diff](/../../compare/v0.15.0...v0.15.1) | [Milestone](/../../milestone/16)

### Fixed

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

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




## [0.15.0] · 2022-10-05
[0.15.0]: /../../tree/v0.15.0

Expand Down
98 changes: 96 additions & 2 deletions book/src/output/multiple.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ use cucumber::{writer, World as _, WriterExt as _};
#
# #[tokio::main]
# async fn main() -> io::Result<()> {
let file = fs::File::create(dbg!(format!("{}/report.json", env!("OUT_DIR"))))?;
let file = fs::File::create(format!("{}/report.xml", env!("OUT_DIR")))?;
World::cucumber()
.with_writer(
// NOTE: `Writer`s pipeline is constructed in a reversed order.
writer::Basic::stdout() // And output to STDOUT.
.summarized() // Simultaneously, add execution summary.
.tee::<World, _>(writer::Json::for_tee(file)) // Then, output to JSON file.
.tee::<World, _>(writer::JUnit::for_tee(file, 0)) // Then, output to XML file.
.normalized() // First, normalize events order.
)
.run_and_exit("tests/features/book")
Expand All @@ -30,4 +30,98 @@ World::cucumber()



## Using the same [`Writer`] multiple times

While using [`writer::Tee`] for different [`Writer`]s is OK and straightforward most of the time, reusing the same [`Writer`] multiple times isn't so obvious, because of the [`clap`] complaining about identical CLI options (unfortunately, in a form of runtime panic only).
```rust,should_panic
# use std::{fs, io};
use cucumber::{writer, World as _, WriterExt as _};
# #[derive(cucumber::World, Debug, Default)]
# struct World;
#
# #[tokio::main]
# async fn main() -> io::Result<()> {
let file = fs::File::create(format!("{}/report.txt", env!("OUT_DIR")))?;
World::cucumber()
.with_writer(
writer::Basic::raw(
io::stdout(),
writer::Coloring::Auto,
writer::Verbosity::Default,
)
.tee::<World, _>(writer::Basic::raw(
file,
writer::Coloring::Never,
2,
))
.summarized()
.normalized(),
)
.run_and_exit("tests/features/book")
.await;
# Ok(())
# }
```
```
thread 'main' panicked at 'Command cucumber: Argument names must be unique, but 'verbose' is in use by more than one argument or group'
```

To avoid this, you should manually construct the desired [`cli::Opts`] and supply them via [`Cucumber::with_cli()`] method. Example below uses two different [`writer::Basic`]s, where one outputs to [STDOUT] and another one outputs to a file:
```rust
# use std::{fs, io};
use cucumber::{cli, writer, World as _, WriterExt as _};

# #[derive(cucumber::World, Debug, Default)]
# struct World;
#
# #[tokio::main]
# async fn main() -> io::Result<()> {
// Parse CLI arguments for a single `writer::Basic`.
let cli = cli::Opts::<_, _, writer::basic::Cli>::parsed();
let cli = cli::Opts {
re_filter: cli.re_filter,
tags_filter: cli.tags_filter,
parser: cli.parser,
runner: cli.runner,
// Replicate CLI arguments for every `writer::Basic`.
writer: cli::Compose {
left: cli.writer.clone(),
right: cli.writer,
},
custom: cli.custom,
};

let file = fs::File::create(format!("{}/report.txt", env!("OUT_DIR")))?;
World::cucumber()
.with_writer(
writer::Basic::raw(
io::stdout(),
writer::Coloring::Auto,
writer::Verbosity::Default,
)
.tee::<World, _>(writer::Basic::raw(
file,
writer::Coloring::Never,
2,
))
.summarized()
.normalized(),
)
.with_cli(cli) // Supply the parsed `cli::Opts`.
.run_and_exit("tests/features/book")
.await;
# Ok(())
# }
```




[`clap`]: https://docs.rs/clap
[`cli::Opts`]: https://docs.rs/cucumber/*/cucumber/cli/struct.Opts.html
[`Writer`]: https://docs.rs/cucumber/*/cucumber/writer/trait.Writer.html
[`writer::Basic`]: https://docs.rs/cucumber/*/cucumber/writer/struct.Basic.html
[`writer::Tee`]: https://docs.rs/cucumber/*/cucumber/writer/struct.Tee.html
[`Cucumber::with_cli()`]: https://docs.rs/cucumber/*/cucumber/struct.Cucumber.html#method.with_cli
[STDOUT]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)
8 changes: 5 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ where
{
/// Regex to filter scenarios by their name.
#[arg(
short = 'n',
id = "name",
long = "name",
short = 'n',
value_name = "regex",
visible_alias = "scenario-name",
global = true
Expand All @@ -106,10 +107,11 @@ where
/// Note: Tags from Feature, Rule and Scenario are merged together on
/// filtering, so be careful about conflicting tags on different levels.
#[arg(
short = 't',
id = "tags",
long = "tags",
short = 't',
value_name = "tagexpr",
conflicts_with = "re_filter",
conflicts_with = "name",
global = true
)]
pub tags_filter: Option<TagOperation>,
Expand Down
8 changes: 7 additions & 1 deletion src/parser/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ 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.
#[arg(long = "input", short = 'i', value_name = "glob", global = true)]
#[arg(
id = "input",
long = "input",
short = 'i',
value_name = "glob",
global = true
)]
pub features: Option<Walker>,
}

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

Expand Down

0 comments on commit f81c556

Please sign in to comment.