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

Wrap event::Cucumber in Event to allow metadata #145

Merged
merged 29 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f031fc2
Add --verbose flag and remove legacy CLI options
ilslv Oct 20, 2021
a4a8f62
Add --scenario-name and --scenario-tags CLI options
ilslv Oct 20, 2021
f0220bb
Add --features option
ilslv Oct 20, 2021
696de67
Correction
ilslv Oct 20, 2021
7b24407
Add Cucumber methods for additional user CLI
ilslv Oct 20, 2021
d9c2589
Correction
ilslv Oct 21, 2021
ba2eccf
Merge branch 'main' into 134-cli-redesign
ilslv Oct 21, 2021
0b58059
Corrections
ilslv Oct 21, 2021
d7bb724
Corrections
ilslv Oct 21, 2021
c48d942
Corrections and add todo_or_die crate
ilslv Oct 21, 2021
4119545
Corrections
ilslv Oct 21, 2021
f0c65db
Lint
ilslv Oct 21, 2021
7c47f3b
Try removing todo-or-die crate
ilslv Oct 21, 2021
90cab88
Add Custom generic parameter to cli::Opts
ilslv Oct 21, 2021
2e83a94
Mention CLI in Features chapter of the book
ilslv Oct 21, 2021
d104147
Bump version to 0.11 and add CHANGELOG entry
ilslv Oct 21, 2021
75f542a
Some corrections [skip ci]
tyranron Oct 21, 2021
cf8e669
Corrections
ilslv Oct 22, 2021
d345c4c
Move trait bound from impls to struct in Cucumber
ilslv Oct 22, 2021
c2d254c
Corrections
ilslv Oct 22, 2021
ff891a9
Pair DateTime with each event
ilslv Oct 22, 2021
598d0bc
Correction
ilslv Oct 22, 2021
be20764
Fix chrono at 0.4.19 to avoid problems with minimal-versions
ilslv Oct 25, 2021
57ed9ea
Merge branch 'main' into add-datetime-to-events
tyranron Oct 25, 2021
aab2e27
Refactor `(Event, DateTime<Utc>)` into `DateTimed<Event>`
ilslv Oct 26, 2021
0f4d2ea
Wrap `event::Cucumber` in `Event<T>`
ilslv Oct 26, 2021
7808d76
Correction
ilslv Oct 26, 2021
39ba0df
Corrections [run ci]
tyranron Oct 26, 2021
3c4e144
Add changelog entry
tyranron Oct 26, 2021
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
- Ability to run `Scenario`s concurrently. ([#128])
- Highlighting of regex capture groups in terminal output with __bold__ style. ([#136])
- Error on a step matching multiple step functions ([#143]).
- `timestamps` Cargo feature that enables collecting of timestamps for all the happened events during tests execution (useful for `Writer`s which format requires them) ([#145]).

[#128]: /../../pull/128
[#136]: /../../pull/136
[#137]: /../../pull/137
[#142]: /../../pull/142
[#143]: /../../pull/143
[#144]: /../../pull/144
[#145]: /../../pull/145



Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["macros"]
# Enables step attributes and auto-wiring.
macros = ["cucumber-codegen", "inventory"]
# Enables timestamps collecting for all events.
timestamps = []

[dependencies]
async-trait = "0.1.40"
atty = "0.2.14"
console = "0.15"
derive_more = { version = "0.99.16", features = ["deref", "deref_mut", "display", "error", "from"], default_features = false }
derive_more = { version = "0.99.16", features = ["as_ref", "deref", "deref_mut", "display", "error", "from"], default_features = false }
either = "1.6"
futures = "0.3.17"
gherkin = { package = "gherkin_rust", version = "0.10" }
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ cargo.fmt:
# make cargo.lint

cargo.lint:
cargo clippy --workspace -- -D warnings
cargo clippy --workspace --all-features -- -D warnings



Expand Down
4 changes: 2 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ another one:
```rust
# use async_trait::async_trait;
# use cucumber::{
# cli, event, parser, ArbitraryWriter, FailureWriter, World, Writer,
# cli, event, parser, ArbitraryWriter, Event, FailureWriter, World, Writer,
# };
# use structopt::StructOpt;
#
Expand All @@ -201,7 +201,7 @@ where

async fn handle_event(
&mut self,
ev: parser::Result<event::Cucumber<W>>,
ev: parser::Result<Event<event::Cucumber<W>>>,
cli: &Self::Cli,
) {
// Some custom logic including `cli.left.custom_option`.
Expand Down
9 changes: 5 additions & 4 deletions src/cucumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ use regex::Regex;
use structopt::{StructOpt, StructOptInternal};

use crate::{
cli, event, parser, runner, step, tag::Ext as _, writer, FailureWriter,
Parser, Runner, ScenarioType, Step, World, Writer, WriterExt as _,
cli, event, parser, runner, step, tag::Ext as _, writer, Event,
FailureWriter, Parser, Runner, ScenarioType, Step, World, Writer,
WriterExt as _,
};

/// Top-level [Cucumber] executor.
Expand Down Expand Up @@ -387,7 +388,7 @@ where
/// use cucumber::event::{Cucumber, Feature, Rule, Scenario, Step};
///
/// matches!(
/// ev,
/// ev.as_deref(),
/// Ok(Cucumber::Feature(
/// _,
/// Feature::Rule(
Expand Down Expand Up @@ -434,7 +435,7 @@ where
filter: F,
) -> Cucumber<W, P, I, R, writer::Repeat<W, Wr, F>, Cli>
where
F: Fn(&parser::Result<event::Cucumber<W>>) -> bool,
F: Fn(&parser::Result<Event<event::Cucumber<W>>>) -> bool,
{
Cucumber {
parser: self.parser,
Expand Down
87 changes: 86 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@

use std::{any::Any, fmt, sync::Arc};

use derive_more::{Display, Error, From};
#[cfg(feature = "timestamps")]
use std::time::SystemTime;

use derive_more::{AsRef, Deref, DerefMut, Display, Error, From};

use crate::{step, writer::basic::coerce_error};

Expand All @@ -30,6 +33,88 @@ use crate::{step, writer::basic::coerce_error};
/// [`catch_unwind()`]: std::panic::catch_unwind()
pub type Info = Arc<dyn Any + Send + 'static>;

/// Arbitrary event, optionally paired with additional metadata.
///
/// Any metadata is added by enabling the correspondent library feature:
/// - `timestamps`: adds time of when this [`Event`] has happened.
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut)]
#[non_exhaustive]
pub struct Event<T: ?Sized> {
/// [`SystemTime`] when this [`Event`] has happened.
#[cfg(feature = "timestamps")]
pub at: SystemTime,

/// Actual value of this [`Event`].
#[as_ref]
#[deref]
#[deref_mut]
pub value: T,
}

impl<T> Event<T> {
/// Creates a new [`Event`] out of the given `value`.
#[must_use]
pub fn new(value: T) -> Self {
Self {
#[cfg(feature = "timestamps")]
at: SystemTime::now(),
value,
}
}

/// Unwraps the inner [`Event::value`] loosing all the attached metadata.
#[allow(clippy::missing_const_for_fn)] // false positive: drop in const
#[must_use]
pub fn into_inner(self) -> T {
self.value
}

/// Splits this [`Event`] to the inner [`Event::value`] and its detached
/// metadata.
#[must_use]
pub fn split(self) -> (T, Metadata) {
self.replace(())
}

/// Replaces the inner [`Event::value`] with the given one, dropping the old
/// one in place.
#[must_use]
pub fn insert<V>(self, value: V) -> Event<V> {
self.replace(value).1
}

/// Maps the inner [`Event::value`] with the given function.
#[must_use]
pub fn map<V>(self, f: impl FnOnce(T) -> V) -> Event<V> {
let (val, meta) = self.split();
meta.insert(f(val))
}

/// Replaces the inner [`Event::value`] with the given one, returning the
/// old one along.
#[allow(clippy::missing_const_for_fn)] // false positive: drop in const
#[must_use]
pub fn replace<V>(self, value: V) -> (T, Event<V>) {
let event = Event {
#[cfg(feature = "timestamps")]
at: self.at,
value,
};
(self.value, event)
}
}

/// Shortcut for a detached metadata of an arbitrary [`Event`].
pub type Metadata = Event<()>;

impl Metadata {
/// Wraps the given `value` with this [`Event`] metadata.
#[must_use]
pub fn wrap<V>(self, value: V) -> Event<V> {
self.replace(value).1
}
}

/// Top-level [Cucumber] run event.
///
/// [Cucumber]: https://cucumber.io
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub use cucumber_codegen::{given, then, when, WorldInit};
#[doc(inline)]
pub use self::{
cucumber::Cucumber,
event::Event,
parser::Parser,
runner::{Runner, ScenarioType},
step::Step,
Expand Down
20 changes: 11 additions & 9 deletions src/runner/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use structopt::StructOpt;
use crate::{
event::{self, HookType, Info},
feature::Ext as _,
parser, step, Runner, Step, World,
parser, step, Event, Runner, Step, World,
};

// Workaround for overwritten doc-comments.
Expand Down Expand Up @@ -387,7 +387,7 @@ where
type Cli = Cli;

type EventStream =
LocalBoxStream<'static, parser::Result<event::Cucumber<W>>>;
LocalBoxStream<'static, parser::Result<Event<event::Cucumber<W>>>>;

fn run<S>(self, features: S, cli: Cli) -> Self::EventStream
where
Expand Down Expand Up @@ -442,7 +442,7 @@ async fn insert_features<W, S, F>(
into: Features,
features: S,
which_scenario: F,
sender: mpsc::UnboundedSender<parser::Result<event::Cucumber<W>>>,
sender: mpsc::UnboundedSender<parser::Result<Event<event::Cucumber<W>>>>,
) where
S: Stream<Item = parser::Result<gherkin::Feature>> + 'static,
F: Fn(
Expand Down Expand Up @@ -476,7 +476,7 @@ async fn execute<W, Before, After>(
features: Features,
max_concurrent_scenarios: Option<usize>,
collection: step::Collection<W>,
sender: mpsc::UnboundedSender<parser::Result<event::Cucumber<W>>>,
sender: mpsc::UnboundedSender<parser::Result<Event<event::Cucumber<W>>>>,
before_hook: Option<Before>,
after_hook: Option<After>,
) where
Expand Down Expand Up @@ -584,7 +584,7 @@ struct Executor<W, Before, After> {
/// Sender for notifying state of [`Feature`]s completion.
///
/// [`Feature`]: gherkin::Feature
sender: mpsc::UnboundedSender<parser::Result<event::Cucumber<W>>>,
sender: mpsc::UnboundedSender<parser::Result<Event<event::Cucumber<W>>>>,
}

impl<W: World, Before, After> Executor<W, Before, After>
Expand All @@ -609,7 +609,9 @@ where
collection: step::Collection<W>,
before_hook: Option<Before>,
after_hook: Option<After>,
sender: mpsc::UnboundedSender<parser::Result<event::Cucumber<W>>>,
sender: mpsc::UnboundedSender<
parser::Result<Event<event::Cucumber<W>>>,
>,
) -> Self {
Self {
features_scenarios_count: HashMap::new(),
Expand Down Expand Up @@ -1098,17 +1100,17 @@ where
fn send(&self, event: event::Cucumber<W>) {
// If the receiver end is dropped, then no one listens for events
// so we can just ignore it.
drop(self.sender.unbounded_send(Ok(event)));
drop(self.sender.unbounded_send(Ok(Event::new(event))));
}

/// Notifies with the given [`Cucumber`] events.
///
/// [`Cucumber`]: event::Cucumber
fn send_all(&self, events: impl Iterator<Item = event::Cucumber<W>>) {
for ev in events {
for v in events {
// If the receiver end is dropped, then no one listens for events
// so we can just stop from here.
if self.sender.unbounded_send(Ok(ev)).is_err() {
if self.sender.unbounded_send(Ok(Event::new(v))).is_err() {
break;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub mod basic;
use futures::Stream;
use structopt::StructOptInternal;

use crate::{event, parser};
use crate::{event, parser, Event};

#[doc(inline)]
pub use self::basic::{Basic, ScenarioType};
Expand Down Expand Up @@ -76,7 +76,9 @@ pub trait Runner<World> {
type Cli: StructOptInternal;

/// Output events [`Stream`].
type EventStream: Stream<Item = parser::Result<event::Cucumber<World>>>;
type EventStream: Stream<
Item = parser::Result<Event<event::Cucumber<World>>>,
>;

/// Executes the given [`Stream`] of [`Feature`]s transforming it into
/// a [`Stream`] of executed [`Cucumber`] events.
Expand Down
6 changes: 3 additions & 3 deletions src/writer/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
event::{self, Info},
parser,
writer::term::Styles,
ArbitraryWriter, World, Writer,
ArbitraryWriter, Event, World, Writer,
};

// Workaround for overwritten doc-comments.
Expand Down Expand Up @@ -105,7 +105,7 @@ impl<W: World + Debug> Writer<W> for Basic {
#[allow(clippy::unused_async)] // false positive: #[async_trait]
async fn handle_event(
&mut self,
ev: parser::Result<event::Cucumber<W>>,
ev: parser::Result<Event<event::Cucumber<W>>>,
cli: &Self::Cli,
) {
use event::{Cucumber, Feature};
Expand All @@ -116,7 +116,7 @@ impl<W: World + Debug> Writer<W> for Basic {
Coloring::Auto => {}
};

match ev {
match ev.map(Event::into_inner) {
Err(err) => self.parsing_failed(&err),
Ok(Cucumber::Started | Cucumber::Finished) => Ok(()),
Ok(Cucumber::Feature(f, ev)) => match ev {
Expand Down
40 changes: 23 additions & 17 deletions src/writer/fail_on_skipped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use std::sync::Arc;
use async_trait::async_trait;
use derive_more::Deref;

use crate::{event, parser, ArbitraryWriter, FailureWriter, World, Writer};
use crate::{
event, parser, ArbitraryWriter, Event, FailureWriter, World, Writer,
};

/// [`Writer`]-wrapper for transforming [`Skipped`] [`Step`]s into [`Failed`].
///
Expand Down Expand Up @@ -63,7 +65,7 @@ where

async fn handle_event(
&mut self,
ev: parser::Result<event::Cucumber<W>>,
ev: parser::Result<Event<event::Cucumber<W>>>,
cli: &Self::Cli,
) {
use event::{
Expand All @@ -77,23 +79,27 @@ where
Step::Skipped
};

Ok(Cucumber::scenario(f, r, sc, Scenario::Step(st, event)))
Cucumber::scenario(f, r, sc, Scenario::Step(st, event))
};

let ev = match ev {
Ok(Cucumber::Feature(
f,
Feature::Rule(
r,
Rule::Scenario(sc, Scenario::Step(st, Step::Skipped)),
),
)) => map_failed(f, Some(r), sc, st),
Ok(Cucumber::Feature(
f,
Feature::Scenario(sc, Scenario::Step(st, Step::Skipped)),
)) => map_failed(f, None, sc, st),
_ => ev,
};
let ev = ev.map(|ev| {
ev.map(|ev| match ev {
Cucumber::Feature(
f,
Feature::Rule(
r,
Rule::Scenario(sc, Scenario::Step(st, Step::Skipped)),
),
) => map_failed(f, Some(r), sc, st),
Cucumber::Feature(
f,
Feature::Scenario(sc, Scenario::Step(st, Step::Skipped)),
) => map_failed(f, None, sc, st),
Cucumber::Started
| Cucumber::Feature(..)
| Cucumber::Finished => ev,
})
});

self.writer.handle_event(ev, cli).await;
}
Expand Down
Loading