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

Support libtest's format=json unstable feature (#194, #203, #218) #220

Merged
merged 30 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dd7eeb3
Replace WorldInit macro with World
ilslv Jul 7, 2022
0f4a869
Docs
ilslv Jul 7, 2022
f9f724f
CHANGELOG
ilslv Jul 7, 2022
1ba639b
Fix doctest
ilslv Jul 7, 2022
191c08f
Remove WorldInit trait and move all of it's methods into the World trait
ilslv Jul 7, 2022
0d7c2ac
Add event::Cucumber::ParsingFinished variant
ilslv Jul 8, 2022
7a2ad47
WIP
ilslv Jul 8, 2022
67d0888
Merge branch 'main' into libtest-format-json
ilslv Jul 8, 2022
65b5247
Docs and clean ups
ilslv Jul 11, 2022
801c37c
Add IntelliJ Rust integration book chapter and CHANGELOG
ilslv Jul 11, 2022
ec2a971
Remove comment
ilslv Jul 11, 2022
207d569
Corrections
ilslv Jul 11, 2022
916d3e1
Clippy
ilslv Jul 11, 2022
977bc8a
Corrections
ilslv Jul 11, 2022
bd2ea5e
Docs
ilslv Jul 11, 2022
0f87bbe
Corrections
ilslv Jul 12, 2022
a80f853
Minor corrections [skip ci]
tyranron Jul 12, 2022
aeab036
Corrections [run ci]
ilslv Jul 15, 2022
f53bd4d
Docs [run ci]
ilslv Jul 15, 2022
0c01bc1
Docs [run ci]
ilslv Jul 15, 2022
4794a6f
Add commands for creating rust issue [run ci]
ilslv Jul 15, 2022
78af231
Nevermind, already reported [run ci]
ilslv Jul 15, 2022
60be14c
Fix book [run ci]
ilslv Jul 15, 2022
5c0aca8
Fix book [run ci]
ilslv Jul 15, 2022
5ba5d17
Fix book [run ci]
ilslv Jul 15, 2022
d48442a
Merge branch 'main' into libtest-format-json
tyranron Jul 18, 2022
e563633
Some corrections [skip ci]
tyranron Jul 18, 2022
fa70d42
Corrections [run ci]
ilslv Jul 19, 2022
30c9be8
Bump async-trait [run ci]
ilslv Jul 19, 2022
bac2b75
Corrections [run ci]
tyranron Jul 20, 2022
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ jobs:
- timestamps
- output-json
- output-junit
- libtest
ilslv marked this conversation as resolved.
Show resolved Hide resolved
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
- Bumped up [MSRV] to 1.62 for more clever support of [Cargo feature]s and simplified codegen. ([fbd08ec2], [cf055ac0], [8ad5cc86])
- Replaced `#[derive(WorldInit)]` with `#[derive(World)]` to remove the need of manual `World` trait implementation. ([#219], [#217])
- Merged `WorldInit` trait into the `World` trait. ([#219])
- Added `ParsingFinished` variant to `event::Cucumber`. ([#220])
- Reworked `writer::Failure`/`writer::discard::Failure` as `writer::Stats`/`writer::discard::Stats`. ([#220])
- Renamed `WriterExt::discard_failure_writes()` to `WriterExt::discard_stats_writes()`. ([#220])

### Added

- `writer::Libtest` (enables [IntelliJ Rust integration][0140-1]) behind the `libtest` feature flag. ([#220])
- `writer::Or` to alternate between 2 `Writer`s basing on a predicate. ([#220])
- `writer::Stats::passed_steps()` and `writer::Stats::skipped_steps()` methods. ([#220])
- `FeatureExt::count_steps()` method. ([#220])

### Changed

Expand All @@ -25,9 +35,11 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
[#216]: /../../pull/216
[#217]: /../../issues/217
[#219]: /../../pull/219
[#220]: /../../pull/220
[8ad5cc86]: /../../commit/8ad5cc866bb9d6b49470790e3b0dd40690f63a09
[cf055ac0]: /../../commit/cf055ac06c7b72f572882ce15d6a60da92ad60a0
[fbd08ec2]: /../../commit/fbd08ec24dbd036c89f5f0af4d936b616790a166
[0140-1]: book/src/output/intellij.md



Expand Down
14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ repository = "https://github.com/cucumber-rs/cucumber"
readme = "README.md"
categories = ["asynchronous", "development-tools::testing"]
keywords = ["cucumber", "testing", "bdd", "atdd", "async"]
include = ["/src/", "/tests/after_hook.rs", "/tests/json.rs", "/tests/junit.rs", "/tests/wait.rs", "/LICENSE-*", "/README.md", "/CHANGELOG.md"]
include = ["/src/", "/tests/after_hook.rs", "/tests/json.rs", "/tests/junit.rs", "/tests/libtest.rs", "/tests/wait.rs", "/LICENSE-*", "/README.md", "/CHANGELOG.md"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["macros"]
# Enables compatibility with Rust libtest (like outputting in its JSON format).
libtest = ["dep:serde", "dep:serde_json", "timestamps"]
# Enables step attributes and auto-wiring.
macros = ["dep:anyhow", "dep:cucumber-codegen", "dep:cucumber-expressions", "dep:inventory"]
# Enables support for outputting in Cucumber JSON format.
Expand All @@ -38,7 +40,7 @@ timestamps = []

[dependencies]
anyhow = { version = "1.0.58", optional = true }
async-trait = "0.1.40"
async-trait = "0.1.43"
atty = "0.2.14"
clap = { version = "3.0", features = ["derive"] }
console = "0.15"
Expand All @@ -58,7 +60,7 @@ cucumber-codegen = { version = "0.14.0-dev", path = "./codegen", optional = true
cucumber-expressions = { version = "0.2.1", features = ["into-regex"], optional = true }
inventory = { version = "0.3", optional = true }

# "output-json" feature dependencies.
# "output-json" and/or "libtest" features dependencies.
serde = { version = "1.0.103", features = ["derive"], optional = true }
serde_json = { version = "1.0.18", optional = true }
Inflector = { version = "0.11", default-features = false, optional = true }
Expand Down Expand Up @@ -86,8 +88,14 @@ name = "junit"
required-features = ["output-junit"]
harness = false

[[test]]
name = "libtest"
required-features = ["libtest"]
harness = false

[[test]]
name = "wait"
required-features = ["libtest"]
harness = false

[workspace]
Expand Down
3 changes: 3 additions & 0 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [JUnit XML report](output/junit.md)
- [Cucumber JSON format](output/json.md)
- [Multiple outputs](output/multiple.md)
- [IntelliJ Rust integration](output/intellij.md)
- [Architecture](architecture/index.md)
- [Custom `Parser`](architecture/parser.md)
- [Custom `Runner`](architecture/runner.md)
Expand Down
1 change: 1 addition & 0 deletions book/src/output/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ This chapter describes possible way and tweaks of outputting test suite results.
2. [JUnit XML report](junit.md)
3. [Cucumber JSON format](json.md)
4. [Multiple outputs](multiple.md)
5. [IntelliJ Rust integration](intellij.md)
41 changes: 41 additions & 0 deletions book/src/output/intellij.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
IntelliJ Rust integration
=========================

[`writer::Libtest`] (enabled by `libtest` feature in `Cargo.toml`) allows [IntelliJ Rust] plugin to interpret output of [`cucumber`] tests similar to unit tests. To use it, just add [Cargo configuration][1] (current example uses `cargo test --test wait --features libtest` command) or run it via [Cargo command][2]. This automatically adds `--format=json` CLI option, which makes the [`cucumber`]'s output IDE-compatible.

Example below is set up to output with the default [`writer::Basic`] if there is no `--format=json` option, or with [`writer::Libtest`] otherwise.
```toml
cucumber = { version = "0.13", features = ["libtest"] }
```
```rust
use cucumber::{writer, World as _};

# #[derive(cucumber::World, Debug, Default)]
# struct World;
#
# #[tokio::main]
# async fn main() {
World::cucumber()
.with_writer(writer::Libtest::or_basic())
.run("tests/features/book")
.await;
# }
```

![record](../rec/output_intellij.gif)

> __NOTE__: There are currently 2 caveats with [IntelliJ Rust] integration:
> 1. Because of [output interpretation issue][3], current timing reports for individual tests are accurate only for serial tests (or for all in case `--concurrency=1` CLI option is used);
> 2. Although debugger works, test window may select `Step` that didn't trigger the breakpoint. To fix this, use `--concurrency=1` CLI option.




[`cucumber`]: https://docs.rs/cucumber
[`writer::Basic`]: https://docs.rs/cucumber/*/cucumber/writer/struct.Basic.html
[`writer::Libtest`]: https://docs.rs/cucumber/*/cucumber/writer/struct.Libtest.html
[IntelliJ Rust]: https://www.jetbrains.com/rust/

[1]: https://plugins.jetbrains.com/plugin/8182-rust/docs/rust-testing.html
[2]: https://plugins.jetbrains.com/plugin/8182-rust/docs/cargo-command-configuration.html
[3]: https://github.com/intellij-rust/intellij-rust/issues/9041
Binary file added book/src/rec/output_intellij.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion book/tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ publish = false
[dependencies]
async-trait = "0.1"
clap = { version = "3.0", features = ["derive"] }
cucumber = { path = "../..", features = ["output-json", "output-junit"] }
cucumber = { path = "../..", features = ["libtest", "output-json", "output-junit"] }
futures = "0.3"
humantime = "2.1"
once_cell = "1.8"
Expand Down
2 changes: 1 addition & 1 deletion codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ synthez = "0.2"

[dev-dependencies]
async-trait = "0.1"
cucumber = { path = "..", features = ["macros"] }
cucumber = { path = "..", features = ["libtest", "macros"] }
derive_more = "0.99.17"
futures = "0.3.17"
tempfile = "3.2"
Expand Down
16 changes: 9 additions & 7 deletions codegen/tests/two_worlds.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::time::Duration;

use cucumber::{gherkin::Step, given, when, World};
use cucumber::{gherkin::Step, given, when, writer, StatsWriter as _, World};
use tokio::time;

#[derive(Debug, Default, World)]
Expand Down Expand Up @@ -43,19 +43,21 @@ fn test_regex_sync_slice(w: &mut SecondWorld, step: &Step, matches: &[String]) {
async fn main() {
let writer = FirstWorld::cucumber()
.max_concurrent_scenarios(None)
.with_writer(writer::Libtest::or_basic())
.run("./tests/features")
.await;

assert_eq!(writer.steps.passed, 7);
assert_eq!(writer.steps.skipped, 5);
assert_eq!(writer.steps.failed, 0);
assert_eq!(writer.passed_steps(), 7);
assert_eq!(writer.skipped_steps(), 5);
assert_eq!(writer.failed_steps(), 0);

let writer = SecondWorld::cucumber()
.max_concurrent_scenarios(None)
.with_writer(writer::Libtest::or_basic())
.run("./tests/features")
.await;

assert_eq!(writer.steps.passed, 1);
assert_eq!(writer.steps.skipped, 8);
assert_eq!(writer.steps.failed, 0);
assert_eq!(writer.passed_steps(), 1);
assert_eq!(writer.skipped_steps(), 8);
assert_eq!(writer.failed_steps(), 0);
}
16 changes: 11 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,9 @@ impl Colored for Empty {}
/// #[async_trait(?Send)]
/// impl<'val, W, Wr, Val> writer::Arbitrary<'val, W, Val> for CustomWriter<Wr>
/// where
/// W: World,
/// Self: Writer<W>,
/// Wr: writer::Arbitrary<'val, W, Val>,
/// Val: 'val,
/// Self: Writer<W>,
/// {
/// async fn write(&mut self, val: Val)
/// where
Expand All @@ -229,12 +228,19 @@ impl Colored for Empty {}
/// }
/// }
///
/// impl<W, Wr> writer::Failure<W> for CustomWriter<Wr>
/// impl<W, Wr> writer::Stats<W> for CustomWriter<Wr>
/// where
/// W: World,
/// Wr: writer::Stats<W>,
/// Self: Writer<W>,
/// Wr: writer::Failure<W>,
/// {
/// fn passed_steps(&self) -> usize {
/// self.0.failed_steps()
/// }
///
/// fn skipped_steps(&self) -> usize {
/// self.0.failed_steps()
/// }
///
/// fn failed_steps(&self) -> usize {
/// self.0.failed_steps()
/// }
Expand Down
11 changes: 1 addition & 10 deletions src/cucumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,7 @@ where
_parser_input: PhantomData,
}
}
}

impl<W, P, I, R, Wr, Cli> Cucumber<W, P, I, R, Wr, Cli>
where
W: World,
P: Parser<I>,
R: Runner<W>,
Wr: Writer<W> + for<'val> writer::Arbitrary<'val, W, String>,
Cli: clap::Args,
{
/// Consider [`Skipped`] [`Background`] or regular [`Step`]s as [`Failed`]
/// if their [`Scenario`] isn't marked with `@allow.skipped` tag.
///
Expand Down Expand Up @@ -1067,7 +1058,7 @@ where
W: World,
P: Parser<I>,
R: Runner<W>,
Wr: writer::Failure<W> + writer::Normalized,
Wr: writer::Stats<W> + writer::Normalized,
Cli: clap::Args,
{
/// Runs [`Cucumber`].
Expand Down
48 changes: 46 additions & 2 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ impl Metadata {
/// Top-level [Cucumber] run event.
///
/// [Cucumber]: https://cucumber.io
#[allow(variant_size_differences)]
#[derive(Debug)]
pub enum Cucumber<World> {
/// [`Cucumber`] execution being started.
Expand All @@ -126,17 +127,60 @@ pub enum Cucumber<World> {
/// [`Feature`] event.
Feature(Arc<gherkin::Feature>, Feature<World>),

/// All [`Feature`]s have been parsed.
///
/// [`Feature`]: gherkin::Feature
ParsingFinished {
/// Number of parsed [`Feature`]s.
///
/// [`Feature`]: gherkin::Feature
features: usize,

/// Number of parsed [`Rule`]s.
///
/// [`Rule`]: gherkin::Rule
rules: usize,

/// Number of parsed [`Scenario`]s.
///
/// [`Scenario`]: gherkin::Scenario
scenarios: usize,

/// Number of parsed [`Step`]s.
///
/// [`Step`]: gherkin::Step
steps: usize,

/// Number of happened [`Parser`] errors.
///
/// [`Parser`]: crate::Parser
parser_errors: usize,
},

/// [`Cucumber`] execution being finished.
Finished,
}

// Manual implementation is required to omit the redundant `World: Clone` trait
// bound imposed by `#[derive(Clone)]`.
// Implemented manually to omit redundant `World: Clone` trait bound, imposed by
// `#[derive(Clone)]`.
impl<World> Clone for Cucumber<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Feature(f, ev) => Self::Feature(Arc::clone(f), ev.clone()),
Self::ParsingFinished {
features,
rules,
scenarios,
steps,
parser_errors,
} => Self::ParsingFinished {
features: *features,
rules: *rules,
scenarios: *scenarios,
steps: *steps,
parser_errors: *parser_errors,
},
Self::Finished => Self::Finished,
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ pub trait Ext: Sized {
/// [`Scenario`]: gherkin::Scenario
#[must_use]
fn count_scenarios(&self) -> usize;

/// Counts all the [`Feature`]'s [`Step`]s.
///
/// [`Feature`]: gherkin::Feature
/// [`Step`]: gherkin::Step
#[must_use]
fn count_steps(&self) -> usize;
}

#[sealed]
Expand All @@ -107,6 +114,16 @@ impl Ext for gherkin::Feature {
self.scenarios.len()
+ self.rules.iter().map(|r| r.scenarios.len()).sum::<usize>()
}

fn count_steps(&self) -> usize {
self.scenarios.iter().map(|s| s.steps.len()).sum::<usize>()
+ self
.rules
.iter()
.flat_map(|r| &r.scenarios)
.map(|s| s.steps.len())
.sum::<usize>()
}
}

/// Expands [`Scenario`] [`Examples`], if any.
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ pub use self::{
runner::{Runner, ScenarioType},
step::Step,
writer::{
Arbitrary as ArbitraryWriter, Ext as WriterExt,
Failure as FailureWriter, Writer,
Arbitrary as ArbitraryWriter, Ext as WriterExt, Stats as StatsWriter,
Writer,
},
};

Expand Down
Loading