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

CLI redesign (#58, #134, #140) #144

Merged
merged 23 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from 20 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
- Replaced `#[given(step)]`, `#[when(step)]` and `#[then(step)]` function argument attributes with a single `#[step]`. ([#128])
- Made test callbacks first argument `&mut World` instead of `World`. ([#128])
- Made `#[step]` argument of step functions `Step` instead of `StepContext` again, while test callbacks still receive `StepContext` as a second parameter. ([#128])
- Deprecated `--nocapture` and `--debug` CLI options to be completely redesigned in `0.11` release. ([#137])
- Complete `CLI` redesign ([#144])
- [Hooks](https://cucumber.io/docs/cucumber/api/#hooks) now accept optional `&mut World` as their last parameter. ([#142])

### Added
Expand All @@ -35,6 +35,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
[#137]: /../../pull/137
[#142]: /../../pull/142
[#143]: /../../pull/143
[#144]: /../../pull/144



Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ macros = ["cucumber-codegen", "inventory"]
[dependencies]
async-trait = "0.1.40"
atty = "0.2.14"
clap = { version = "=3.0.0-beta.5", features = ["derive"] }
console = "0.15"
derive_more = { version = "0.99.16", features = ["deref", "deref_mut", "display", "error", "from"], default_features = false }
either = "1.6"
Expand All @@ -44,12 +43,14 @@ linked-hash-map = "0.5.3"
once_cell = { version = "1.8", features = ["parking_lot"] }
regex = "1.5"
sealed = "0.3"
structopt = "0.3.25"

# "macros" feature dependencies
cucumber-codegen = { version = "0.10", path = "./codegen", optional = true }
inventory = { version = "0.1.10", optional = true }

[dev-dependencies]
humantime = "2.1"
tokio = { version = "1.12", features = ["macros", "rt-multi-thread", "time"] }

[[test]]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct World {
capacity: usize,
}

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

Expand Down
52 changes: 52 additions & 0 deletions book/src/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,5 +382,57 @@ World::cucumber()



## CLI options

`Cucumber` provides several options that can be passed to the command-line.

Pass the `--help` option to print out all the available configuration options:

```
cargo test --test <test-name> -- --help
```

Default output is:

```
cucumber 0.10.0
Run the tests, pet a dog!

USAGE:
cucumber [FLAGS] [OPTIONS]

FLAGS:
-h, --help Prints help information
-V, --version Prints version information
--verbose Outputs Step's Doc String, if present

OPTIONS:
-c, --colors <auto|always|never> Indicates, whether output should be colored or not [default: auto]
-f, --features <glob> `.feature` files glob pattern
--concurrent <int> Number of concurrent scenarios
-n, --name <regex> Regex to filter scenarios with [aliases: scenario-name]
-t, --tags <tagexpr> Tag expression to filter scenarios with [aliases: scenario-tags]
```

Example with [tag expressions](https://cucumber.io/docs/cucumber/api/#tag-expressions) for filtering Scenarios:

```
cargo test --test <test-name> -- --tags='@cat or @dog or @ferris'
```

> Note: CLI overrides options set in the code.


### Customizing CLI options

All `CLI` options are designed to be composable.

For example all building-block traits have `CLI` associated type: [`Parser::Cli`](https://docs.rs/cucumber/*/cucumber/trait.Parser.html#associatedtype.Cli), [`Runner::Cli`](https://docs.rs/cucumber/*/cucumber/trait.Runner.html#associatedtype.Cli) and [`Writer::Cli`](https://docs.rs/cucumber/*/cucumber/trait.Writer.html#associatedtype.Cli). All of them are composed into a single `CLI`.

In case you want to add completely custom `CLI` options, check out [`Cucumber::with_cli()`](https://docs.rs/cucumber/*/cucumber/struct.Cucumber.html#method.with_cli) method.




[Cucumber]: https://cucumber.io
[Gherkin]: https://cucumber.io/docs/gherkin
3 changes: 3 additions & 0 deletions codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ mod derive;

use proc_macro::TokenStream;

/// Expands `given`, `when` and `then` proc-macro attributes.
macro_rules! step_attribute {
($name:ident) => {
/// Attribute to auto-wire the test to the [`World`] implementer.
Expand Down Expand Up @@ -195,6 +196,8 @@ macro_rules! step_attribute {
};
}

/// Expands `WorldInit` derive proc-macro and `given`, `when`, `then` proc-macro
/// attributes.
macro_rules! steps {
($($name:ident),*) => {
/// Derive macro for tests auto-wiring.
Expand Down
215 changes: 192 additions & 23 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,200 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! CLI options.
//! Tools for composing CLI options.
//!
//! Main part of this module is [`Opts`], which composes all strongly-typed
//! `CLI` options from [`Parser`], [`Runner`] and [`Writer`] and adds filtering
//! based on [`Regex`] or [`Tag Expressions`][1].
//!
//! [1]: https://cucumber.io/docs/cucumber/api/#tag-expressions
//! [`Parser`]: crate::Parser
//! [`Runner`]: crate::Runner
//! [`Writer`]: crate::Writer

use gherkin::tagexpr::TagOperation;
use regex::Regex;
use structopt::StructOpt;

/// Run the tests, pet a dog!.
///
/// __WARNING__ ⚠️: This CLI exists only for backwards compatibility. In `0.11`
/// it will be completely reworked:
/// [cucumber-rs/cucumber#134][1].
///
/// [1]: https://github.com/cucumber-rs/cucumber/issues/134
#[derive(clap::Parser, Debug)]
pub struct Opts {
/// Regex to select scenarios from.
#[clap(short = 'e', long = "expression", name = "regex")]
pub filter: Option<Regex>,

/// __WARNING__ ⚠️: This option does nothing at the moment and is deprecated
/// for removal in the next major release.
/// Any output of step functions is not captured by default.
#[clap(long)]
pub nocapture: bool,

/// __WARNING__ ⚠️: This option does nothing at the moment and is deprecated
/// for removal in the next major release.
#[clap(long)]
pub debug: bool,
#[derive(Debug, StructOpt)]
pub struct Opts<Parser, Runner, Writer, Custom = Empty>
where
Custom: StructOpt,
Parser: StructOpt,
Runner: StructOpt,
Writer: StructOpt,
{
/// Regex to filter scenarios with.
#[structopt(
short = "n",
long = "name",
name = "regex",
visible_alias = "scenario-name"
)]
pub re_filter: Option<Regex>,

/// Tag expression to filter scenarios with.
#[structopt(
short = "t",
long = "tags",
name = "tagexpr",
visible_alias = "scenario-tags",
conflicts_with = "regex"
)]
pub tags_filter: Option<TagOperation>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option and the one above have the same descriptions, which is quite confusing.


/// [`Parser`] CLI options.
///
/// [`Parser`]: crate::Parser
#[structopt(flatten)]
pub parser: Parser,

/// [`Runner`] CLI options.
///
/// [`Runner`]: crate::Runner
#[structopt(flatten)]
pub runner: Runner,

/// [`Writer`] CLI options.
///
/// [`Writer`]: crate::Writer
#[structopt(flatten)]
pub writer: Writer,

/// Custom CLI options.
#[structopt(flatten)]
pub custom: Custom,
}

// Workaround for overwritten doc-comments.
// https://github.com/TeXitoi/structopt/issues/333#issuecomment-712265332
#[cfg_attr(
not(doc),
allow(missing_docs, clippy::missing_docs_in_private_items)
)]
#[cfg_attr(doc, doc = "Empty CLI options.")]
#[derive(Clone, Copy, Debug, StructOpt)]
pub struct Empty {
/// This field exists only because [`StructOpt`] derive macro doesn't
/// support unit structs.
#[structopt(skip)]
skipped: (),
}

// Workaround for overwritten doc-comments.
// https://github.com/TeXitoi/structopt/issues/333#issuecomment-712265332
#[cfg_attr(
not(doc),
allow(missing_docs, clippy::missing_docs_in_private_items)
)]
#[cfg_attr(
doc,
doc = r#"
Composes two [`StructOpt`] derivers together.

# Example

This struct is especially useful, when implementing custom [`Writer`], which
wraps another [`Writer`].

```rust
# use async_trait::async_trait;
# use cucumber::{
# cli, event, parser, ArbitraryWriter, FailureWriter, World, Writer,
# };
# use structopt::StructOpt;
#
struct CustomWriter<Wr>(Wr);

#[derive(StructOpt)]
struct Cli {
#[structopt(long)]
custom_option: Option<String>,
}

#[async_trait(?Send)]
impl<W, Wr> Writer<W> for CustomWriter<Wr>
where
W: World,
Wr: Writer<W>,
{
type Cli = cli::Compose<Cli, Wr::Cli>;

async fn handle_event(
&mut self,
ev: parser::Result<event::Cucumber<W>>,
cli: &Self::Cli,
) {
// Some custom logic including `cli.left.custom_option`.

self.0.handle_event(ev, &cli.right).await;
}
}

// useful blanket impls

#[async_trait(?Send)]
impl<'val, W, Wr, Val> ArbitraryWriter<'val, W, Val> for CustomWriter<Wr>
where
W: World,
Self: Writer<W>,
Wr: ArbitraryWriter<'val, W, Val>,
Val: 'val,
{
async fn write(&mut self, val: Val)
where
'val: 'async_trait,
{
self.0.write(val).await;
}
}

impl<W, Wr> FailureWriter<W> for CustomWriter<Wr>
where
W: World,
Self: Writer<W>,
Wr: FailureWriter<W>,
{
fn failed_steps(&self) -> usize {
self.0.failed_steps()
}

fn parsing_errors(&self) -> usize {
self.0.parsing_errors()
}

fn hook_errors(&self) -> usize {
self.0.hook_errors()
}
}
```

[`Writer`]: crate::Writer"#
)]
#[derive(Debug, StructOpt)]
pub struct Compose<L, R>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need it anymore.

If it's still may be useful in some way for library users, it should have an example in its documentation (or on a module level), describing the case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it because it can be very useful for custom Writer wrappers. They can store their own cli options in addition to inner Writers.

where
L: StructOpt,
R: StructOpt,
{
/// [`StructOpt`] deriver.
#[structopt(flatten)]
pub left: L,

/// [`StructOpt`] deriver.
#[structopt(flatten)]
pub right: R,
}

impl<L, R> Compose<L, R>
where
L: StructOpt,
R: StructOpt,
{
/// Unpacks [`Compose`] into underlying `CLI`s.
pub fn into_inner(self) -> (L, R) {
let Compose { left, right } = self;
(left, right)
}
}
Loading