Skip to content

Commit

Permalink
Implement writer::Tee for outputting to multiple terminating `Write…
Browse files Browse the repository at this point in the history
…r`s simultaneously (#160)

- add `writer::discard::Arbitrary` and `writer::discard::Failure` for provoding no-op implementations of corresponding traits
- move `World` type parameter of `WriterExt` to methods

Co-authored-by: Kai Ren <[email protected]>
  • Loading branch information
ilslv and tyranron committed Nov 17, 2021
1 parent 1f901e3 commit ed478b2
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 23 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ All user visible changes to `cucumber` crate will be documented in this file. Th

[Diff](/../../compare/v0.10.2...v0.11.0) | [Milestone](/../../milestone/3)

### BC Breaks

- Moved `World` type parameter of `WriterExt` trait to methods. ([#160])

### Added

- Ability for step functions to return `Result`. ([#151])
- Arbitrary output for `writer::Basic`. ([#147])
- `writer::JUnit` ([JUnit XML report][0110-1]) behind the `output-junit` feature flag. ([#147])
- `writer::Json` ([Cucumber JSON format][0110-2]) behind the `output-json` feature flag. ([#159])
- `writer::Tee` for outputting to multiple terminating `Writer`s simultaneously. ([#160])
- `writer::discard::Arbitrary` and `writer::discard::Failure` for providing no-op implementations of the corresponding `Writer` traits. ([#160])

### Fixed

Expand All @@ -25,6 +31,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
[#147]: /../../pull/147
[#151]: /../../pull/151
[#159]: /../../pull/159
[#160]: /../../pull/160
[#163]: /../../pull/163
[0110-1]: https://llg.cubic.org/docs/junit
[0110-2]: https://github.com/cucumber/cucumber-json-schema
Expand Down
13 changes: 9 additions & 4 deletions book/src/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,13 +483,13 @@ Just enable `output-json` library feature in your `Cargo.toml`:
cucumber = { version = "0.11", features = ["output-json"] }
```

And configure [Cucumber]'s output to `writer::Json`:
And configure [Cucumber]'s output both to STDOUT and `writer::Json` (with `writer::Tee`):
```rust
# use std::{convert::Infallible, fs, io};
#
# use async_trait::async_trait;
# use cucumber::WorldInit;
use cucumber::writer;
use cucumber::{writer, WriterExt as _};

# #[derive(Debug, WorldInit)]
# struct World;
Expand All @@ -507,8 +507,13 @@ use cucumber::writer;
# async fn main() -> io::Result<()> {
let file = fs::File::create(dbg!(format!("{}/target/schema.json", env!("CARGO_MANIFEST_DIR"))))?;
World::cucumber()
.with_writer(writer::Json::new(file))
.run("tests/features/book")
.with_writer(
writer::Basic::default()
.summarized()
.tee::<World, _>(writer::Json::for_tee(file))
.normalized(),
)
.run_and_exit("tests/features/book")
.await;
# Ok(())
# }
Expand Down
141 changes: 141 additions & 0 deletions src/writer/discard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) 2018-2021 Brendan Molloy <[email protected]>,
// Ilya Solovyiov <[email protected]>,
// Kai Ren <[email protected]>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Wrappers providing no-op implementations.

use async_trait::async_trait;
use derive_more::{Deref, DerefMut};

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

/// Wrapper providing a no-op [`ArbitraryWriter`] implementation.
///
/// Intended to be used for feeding a non-[`ArbitraryWriter`] [`Writer`] into a
/// [`writer::Tee`], as the later accepts only [`ArbitraryWriter`]s.
///
/// [`writer::Tee`]: crate::writer::Tee
#[derive(Clone, Copy, Debug, Deref, DerefMut)]
pub struct Arbitrary<Wr: ?Sized>(Wr);

#[async_trait(?Send)]
impl<W: World, Wr: Writer<W> + ?Sized> Writer<W> for Arbitrary<Wr> {
type Cli = Wr::Cli;

async fn handle_event(
&mut self,
ev: crate::parser::Result<Event<Cucumber<W>>>,
cli: &Self::Cli,
) {
self.0.handle_event(ev, cli).await;
}
}

#[async_trait(?Send)]
impl<'val, W: World, Val: 'val, Wr: Writer<W> + ?Sized>
ArbitraryWriter<'val, W, Val> for Arbitrary<Wr>
{
/// Does nothing.
async fn write(&mut self, _: Val)
where
'val: 'async_trait,
{
// Intentionally no-op.
}
}

impl<W: World, Wr: FailureWriter<W> + ?Sized> FailureWriter<W>
for Arbitrary<Wr>
{
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()
}
}

impl<Wr> Arbitrary<Wr> {
/// Wraps the given [`Writer`] into a [`discard::Arbitrary`] one.
///
/// [`discard::Arbitrary`]: crate::writer::discard::Arbitrary
#[must_use]
pub const fn wrap(writer: Wr) -> Self {
Self(writer)
}
}

/// Wrapper providing a no-op [`FailureWriter`] implementation returning only
/// `0`.
///
/// Intended to be used for feeding a non-[`FailureWriter`] [`Writer`] into a
/// [`writer::Tee`], as the later accepts only [`FailureWriter`]s.
///
/// [`writer::Tee`]: crate::writer::Tee
#[derive(Clone, Copy, Debug, Deref, DerefMut)]
pub struct Failure<Wr: ?Sized>(Wr);

#[async_trait(?Send)]
impl<W: World, Wr: Writer<W> + ?Sized> Writer<W> for Failure<Wr> {
type Cli = Wr::Cli;

async fn handle_event(
&mut self,
ev: crate::parser::Result<Event<Cucumber<W>>>,
cli: &Self::Cli,
) {
self.0.handle_event(ev, cli).await;
}
}

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

impl<W: World, Wr: Writer<W> + ?Sized> FailureWriter<W> for Failure<Wr> {
/// Always returns `0`.
fn failed_steps(&self) -> usize {
0
}

/// Always returns `0`.
fn parsing_errors(&self) -> usize {
0
}

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

impl<Wr> Failure<Wr> {
/// Wraps the given [`Writer`] into a [`discard::Failure`] one.
///
/// [`discard::Failure`]: crate::writer::discard::Failure
#[must_use]
pub const fn wrap(writer: Wr) -> Self {
Self(writer)
}
}
24 changes: 23 additions & 1 deletion src/writer/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
cli, event,
feature::ExpandExamplesError,
parser,
writer::{self, basic::coerce_error},
writer::{self, basic::coerce_error, discard},
Event, World, Writer, WriterExt as _,
};

Expand Down Expand Up @@ -75,6 +75,28 @@ impl<Out: io::Write> Json<Out> {
Self::raw(output).normalized()
}

/// Creates a new unnormalized [`Json`] [`Writer`] outputting [JSON][1] into
/// the given `output`, and suitable for feeding into [`tee()`].
///
/// # Warning
///
/// It may panic in runtime as won't be able to form [correct JSON][1] from
/// unordered [`Cucumber` events][2], until is [`normalized()`].
///
/// So, either make it [`normalized()`] before feeding into [`tee()`], or
/// make the whole [`tee()`] pipeline [`normalized()`].
///
/// [`normalized()`]: crate::WriterExt::normalized
/// [`tee()`]: crate::WriterExt::tee
/// [1]: https://github.com/cucumber/cucumber-json-schema
/// [2]: crate::event::Cucumber
#[must_use]
pub fn for_tee(output: Out) -> discard::Arbitrary<discard::Failure<Self>> {
Self::raw(output)
.discard_failure_writes()
.discard_arbitrary_writes()
}

/// Creates a new raw and unnormalized [`Json`] [`Writer`] outputting
/// [JSON][1] into the given `output`.
///
Expand Down
24 changes: 24 additions & 0 deletions src/writer/junit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
writer::{
self,
basic::{coerce_error, Coloring},
discard,
out::WritableString,
Ext as _,
},
Expand Down Expand Up @@ -143,6 +144,29 @@ impl<W: Debug, Out: io::Write> JUnit<W, Out> {
Self::raw(output).normalized()
}

/// Creates a new unnormalized [`JUnit`] [`Writer`] outputting XML report
/// into the given `output`, and suitable for feeding into [`tee()`].
///
/// # Warning
///
/// It may panic in runtime as won't be able to correct
/// [JUnit `testsuite`s][1] from unordered [`Cucumber` events][2], until is
/// [`normalized()`].
///
/// So, either make it [`normalized()`] before feeding into [`tee()`], or
/// make the whole [`tee()`] pipeline [`normalized()`].
///
/// [`normalized()`]: crate::WriterExt::normalized
/// [`tee()`]: crate::WriterExt::tee
/// [1]: https://llg.cubic.org/docs/junit
/// [2]: crate::event::Cucumber
#[must_use]
pub fn for_tee(output: Out) -> discard::Arbitrary<discard::Failure<Self>> {
Self::raw(output)
.discard_failure_writes()
.discard_arbitrary_writes()
}

/// Creates a new raw and unnormalized [`JUnit`] [`Writer`] outputting XML
/// report into the given `output`.
///
Expand Down
Loading

0 comments on commit ed478b2

Please sign in to comment.