diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ee0636e2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 + +[*.md] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false +max_line_length = off + +[*.rs] +indent_style = space +indent_size = 4 + +[*.feature] +indent_style = space +indent_size = 2 + +[*.toml] +indent_style = space +indent_size = 4 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0d4de31..6b6ef1bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,33 +1,214 @@ -on: [push, pull_request] - name: CI +on: + push: + branches: ["main"] + tags: ["v*"] + pull_request: + branches: ["main"] + +env: + RUST_BACKTRACE: 1 + jobs: - build: + + ########################## + # Linting and formatting # + ########################## + + clippy: + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + + - run: make cargo.lint + + rustfmt: + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt + + - run: make cargo.fmt check=yes + + + + + ########### + # Testing # + ########### + + test: + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: + fail-fast: false matrix: - rust: + crate: + - cucumber_rust_codegen + - cucumber_rust + os: + - ubuntu + - macOS + - windows + toolchain: - stable - beta - nightly + runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: ${{ matrix.rust }} + toolchain: ${{ matrix.toolchain }} override: true - components: rustfmt, clippy - - uses: actions-rs/cargo@v1 + + - run: make test.cargo crate=${{ matrix.crate }} + + test-book: + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 with: - command: build - args: --all-features - - uses: actions-rs/cargo@v1 + profile: minimal + toolchain: stable + + - run: make test.book + + + + + ################# + # Documentation # + ################# + + rustdoc: + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + + - run: make cargo.doc open=no + + + + + ############# + # Releasing # + ############# + + release-github: + name: Release on GitHub + needs: ["clippy", "rustfmt", "rustdoc", "test", "test-book"] + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Parse release version + id: release + run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} + + - name: Verify release version matches `cucumber_rust_codegen` Cargo manifest + run: >- + test "${{ steps.release.outputs.VERSION }}" \ + == "$(grep -m1 'version = "' codegen/Cargo.toml | cut -d '"' -f2)" + - name: Verify release version matches `cucumber_rust` Cargo manifest + run: >- + test "${{ steps.release.outputs.VERSION }}" \ + == "$(grep -m1 'version = "' Cargo.toml | cut -d '"' -f2)" + + - name: Parse CHANGELOG link + id: changelog + run: echo ::set-output + name=LINK::https://github.com/${{ github.repository }}/blob/v${{ steps.release.outputs.VERSION }}/CHANGELOG.md#$(sed -n '/^### \[${{ steps.release.outputs.VERSION }}\]/{s/^### \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' CHANGELOG.md) + + - uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: ${{ steps.release.outputs.VERSION }} + body: | + [API docs](https://docs.rs/cucumber_rust/${{ steps.release.outputs.VERSION }}) + [Changelog](${{ steps.changelog.outputs.LINK }}) + prerelease: ${{ contains(steps.release.outputs.VERSION, '-') }} + + release-crate: + name: Release on crates.io + needs: ["release-github"] + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 with: - command: test - args: -p cucumber_rust_codegen - - uses: actions-rs/cargo@v1 + profile: minimal + toolchain: stable + + - name: Publish `cucumber_rust_codegen` crate + run: cargo publish --token ${{ secrets.CRATESIO_TOKEN }} + working-directory: ./codegen + + - name: Wait crates.io index is updated + run: sleep 120 + + - name: Publish `cucumber_rust` crate + run: cargo publish --token ${{ secrets.CRATESIO_TOKEN }} + working-directory: ./ + + + + + ########## + # Deploy # + ########## + + deploy-book: + name: Deploy Book + needs: ["test-book"] + if: ${{ github.ref == 'refs/heads/main' + || startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: peaceiris/actions-mdbook@v1 + + - run: make book.build out=gh-pages/main + if: ${{ github.ref == 'refs/heads/main' }} + + - run: make book.build out=gh-pages/current + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 with: - command: test - args: --all-features + github_token: ${{ secrets.GITHUB_TOKEN }} + keep_files: true + publish_dir: book/gh-pages diff --git a/.gitignore b/.gitignore index a39b82fe..6b6c23cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ +/.idea/ +/.vscode/launch.json +/*.iml +.DS_Store -/target +/target/ +/Cargo.lock **/*.rs.bk - -/target -**/*.rs.bk -Cargo.lock -.idea/ -.vscode/launch.json diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..e444a1e5 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,16 @@ +# Project configuration for rustfmt Rust code formatter. +# See full list of configurations at: +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md + +max_width = 80 +format_strings = false +imports_granularity = "Crate" + +format_code_in_doc_comments = true +format_macro_matchers = true +use_try_shorthand = true + +error_on_line_overflow = true +error_on_unformatted = true + +unstable_features = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1b2356..12c7e00a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,17 @@ - `Scenario Outline` is treated the same as `Outline` or `Example` in the parser ([gherkin/#19](https://github.com/bbqsrc/gherkin-rust/issues/19)) -### 0.9.0 — 2021-07-19 +### [0.10.0] — ??? +[0.10.0]: /../../tree/v0.10.0 + +- **Breaking change** Complete redesign: + - Introduce new abstractions: `Parser`, `Runner`, `Writer`. + - Provide reference implementations for those abstractions. + - Add ability to run `Scenario`s concurrently. + - Update book to reflect redesign. + +### [0.9.0] — 2021-07-19 +[0.9.0]: /../../tree/v0.9.0 - **Breaking change:** the second parameter in the test callbacks is now a `StepContext` object, which contains the `Step` as a `step` field. - Feature: Add `before` and `after` lifecycle functions to the Cucumber builder @@ -12,45 +22,54 @@ - Fix: literal paths to .feature files will now work in the Cucumber builder - Fix: remove unnecessary internal `Rc` usage. -### 0.8.4 — 2021-02-18 +### [0.8.4] — 2021-02-18 +[0.8.4]: /../../tree/v0.8.4 - Feature: add `language` argument to Cucumber builder to set default language for all feature files - Feature: add `--debug` flag to always print stdout and stderr per step -### 0.8.3 — 2021-02-09 +### [0.8.3] — 2021-02-09 +[0.8.3]: /../../tree/v0.8.3 - Update `t!` macro to support specifying type of world argument in closure -### 0.8.2 — 2021-01-30 +### [0.8.2] — 2021-01-30 +[0.8.2]: /../../tree/v0.8.2 - Re-export `async_trait::async_trait` and `futures` crate for convenience - Update examples to use `tokio` -### 0.8.1 — 2021-01-30 +### [0.8.1] — 2021-01-30 +[0.8.1]: /../../tree/v0.8.1 - Added proper i18n support via gherkin 0.9 -### 0.8.0 — 2021-01-18 +### [0.8.0] — 2021-01-18 +[0.8.0]: /../../tree/v0.8.0 - Fixed filtering of tests by tag ([#67](https://github.com/bbqsrc/cucumber-rust/issues/67)) - Implemented failure reporting ([#91](https://github.com/bbqsrc/cucumber-rust/issues/91)) - Removed unnecessary dependent traits from `World` trait - Added proc-macro variant (thanks Ilya Solovyiov and Kai Ren) -### 0.7.3 — 2020-09-20 +### [0.7.3] — 2020-09-20 +[0.7.3]: /../../tree/v0.7.3 - Fix missing mut in t! macro for regexes ([#68](https://github.com/bbqsrc/cucumber-rust/issues/68)) — thanks [@stefanpieck](https://github.com/stefanpieck)! -### 0.7.2 — 2020-09-14 +### [0.7.2] — 2020-09-14 +[0.7.2]: /../../tree/v0.7.2 - Enforce `UnwindSafe` on async test types -### 0.7.1 — 2020-09-09 +### [0.7.1] — 2020-09-09 +[0.7.1]: /../../tree/v0.7.1 - Fix issue with `t!` macro for unbraced blocks -### 0.7.0 — 2020-09-07 +### [0.7.0] — 2020-09-07 +[0.7.0]: /../../tree/v0.7.0 - **Breaking changes**: the macro approach provided in 0.6.x and lower has been entirely removed. It was hard to maintain and limited maintenance of the tests themselves. - A new builder approach has been implemented. -- Support for asynchronous tests has been implemented — this is runtime agnostic. \ No newline at end of file +- Support for asynchronous tests has been implemented — this is runtime agnostic. diff --git a/Cargo.toml b/Cargo.toml index 39658b80..083e0afc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,61 +1,61 @@ [package] -authors = ["Brendan Molloy "] -categories = ["asynchronous", "development-tools::testing"] -description = "Cucumber testing framework for Rust, with async support. Fully native, no external test runners or dependencies." -documentation = "https://docs.rs/cucumber_rust" +name = "cucumber_rust" +version = "0.10.0" edition = "2018" -homepage = "https://github.com/bbqsrc/cucumber-rust" -keywords = ["cucumber", "testing", "bdd", "atdd", "async"] +resolver = "2" +description = """\ + Cucumber testing framework for Rust, with async support. \ + Fully native, no external test runners or dependencies.\ + """ license = "MIT OR Apache-2.0" -name = "cucumber_rust" -readme = "README.md" +authors = [ + "Brendan Molloy ", + "Ilya Solovyiov ", + "Kai Ren ", +] +documentation = "https://docs.rs/cucumber_rust" +homepage = "https://github.com/bbqsrc/cucumber-rust" repository = "https://github.com/bbqsrc/cucumber-rust" -version = "0.8.4" +readme = "README.md" +categories = ["asynchronous", "development-tools::testing"] +keywords = ["cucumber", "testing", "bdd", "atdd", "async"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] +default = ["macros"] macros = ["cucumber_rust_codegen", "inventory"] [dependencies] -async-stream = "0.3.0" async-trait = "0.1.40" -clap = "2.33" -cute_custom_default = "2.1.0" -futures = "0.3.5" -futures-timer = "3.0.2" -gherkin = {package = "gherkin_rust", version = "0.10"} -globwalk = "0.8.0" -pathdiff = "0.2.0" -regex = "1.3.9" -shh = "1.0.1" -termcolor = "1.1.0" -textwrap = {version = "0.12.1", features = ["terminal_size"]} -thiserror = "1.0.20" -tracing = "0.1.25" - -# Codegen dependencies -cucumber_rust_codegen = {version = "0.1", path = "./codegen", optional = true} -inventory = {version = "0.1", optional = true} -once_cell = "1.7.0" +atty = "0.2.14" +console = "0.14.1" +derive_more = { version = "0.99.16", features = ["deref", "display", "error", "from"], default_features = false } +either = "1.6" +futures = "0.3.17" +gherkin = { package = "gherkin_rust", version = "0.10" } +globwalk = "0.8" +itertools = "0.10" +linked-hash-map = "0.5" +regex = "1.5" +sealed = "0.3" + +# "macros" feature dependencies +cucumber_rust_codegen = { version = "0.10", path = "./codegen", optional = true } +inventory = { version = "0.1.10", optional = true } [dev-dependencies] -capture-runner = {path = "tests/fixtures/capture-runner"} -serial_test = "0.5.0" -tokio = {version = "1", features = ["macros", "rt-multi-thread"]} -tracing-subscriber = {version = "0.2.16", features = ["fmt"]} +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "time"] } [[test]] +name = "wait" harness = false -name = "cucumber_builder" [[test]] -edition = "2018" -harness = true -name = "integration_test" +name = "output" [workspace] -default-members = [".", "codegen"] -members = ["codegen", "tests/fixtures/capture-runner"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] +members = ["codegen"] +exclude = ["book/tests"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f4a3cce2 --- /dev/null +++ b/Makefile @@ -0,0 +1,129 @@ +############################### +# Common defaults/definitions # +############################### + +comma := , + +# Checks two given strings for equality. +eq = $(if $(or $(1),$(2)),$(and $(findstring $(1),$(2)),\ + $(findstring $(2),$(1))),1) + + + + +########### +# Aliases # +########### + +book: book.build + + +docs: cargo.doc + + +fmt: cargo.fmt + + +lint: cargo.lint + + +test: test.cargo test.book + + + + +################## +# Cargo commands # +################## + +# Generate crates documentation from Rust sources. +# +# Usage: +# make cargo.doc [crate=] [open=(yes|no)] [clean=(no|yes)] + +cargo.doc: +ifeq ($(clean),yes) + @rm -rf target/doc/ +endif + cargo +stable doc $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + --all-features \ + $(if $(call eq,$(open),no),,--open) + + +# Format Rust sources with rustfmt. +# +# Usage: +# make cargo.fmt [check=(no|yes)] + +cargo.fmt: + cargo +nightly fmt --all $(if $(call eq,$(check),yes),-- --check,) + + +# Lint Rust sources with Clippy. +# +# Usage: +# make cargo.lint + +cargo.lint: + cargo +stable clippy --workspace -- -D clippy::pedantic -D warnings + + + + +#################### +# Testing commands # +#################### + +# Run Rust tests of project crates. +# +# Usage: +# make test.cargo [crate=] + +test.cargo: + cargo +stable test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + --all-features + + +# Run Rust tests of Book. +# +# Usage: +# make test.book + +test.book: + cargo +stable test --manifest-path book/tests/Cargo.toml + + + + +################# +# Book commands # +################# + +# Build Book. +# +# Usage: +# make book.build [out=] + +book.build: + mdbook build book/ $(if $(call eq,$(out),),,-d $(out)) + + +# Serve Book on some port. +# +# Usage: +# make book.serve [port=(3000|)] + +book.serve: + mdbook serve book/ -p=$(or $(port),3000) + + + + +################## +# .PHONY section # +################## + +.PHONY: book docs fmt lint test \ + cargo.doc cargo.fmt cargo.lint \ + book.build book.serve \ + test.cargo test.book diff --git a/README.md b/README.md index f5eb6b62..ec7653b6 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,119 @@ [![Documentation](https://docs.rs/cucumber_rust/badge.svg)](https://docs.rs/cucumber_rust) [![Actions Status](https://github.com/bbqsrc/cucumber-rust/workflows/CI/badge.svg)](https://github.com/bbqsrc/cucumber-rust/actions) +[![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) + +An implementation of the [Cucumber] testing framework for Rust. Fully native, no external test runners or dependencies. + +- Book ([current][1] | [edge][2]) +- [Changelog](https://github.com/bbqsrc/cucumber-rust/blob/main/CHANGELOG.md) + -An implementation of the Cucumber testing framework for Rust. Fully native, no external test runners or dependencies. -- [Changelog](CHANGELOG.md) ## Usage -See our [example repository](https://github.com/bbqsrc/cucumber-rust-example). +Describe testing scenarios in `.feature` files: +```gherkin +## /tests/features/readme/eating.feature + +Feature: Eating too much cucumbers may not be good for you + + Scenario: Eating a few isn't a problem + Given Alice is hungry + When she eats 3 cucumbers + Then she is full +``` + +Implement `World` trait and describe steps: + ```rust +//! tests/readme.rs + +use std::{convert::Infallible, time::Duration}; + +use async_trait::async_trait; +use cucumber_rust::{self as cucumber, given, then, when, WorldInit}; +use tokio::time::sleep; + +#[derive(Debug, WorldInit)] +struct World { + user: Option, + capacity: usize, +} + +#[async_trait(? Send)] +impl cucumber::World for World { + type Error = Infallible; + + async fn new() -> Result { + Ok(Self { user: None, capacity: 0 }) + } +} + +#[given(regex = r"^(\S+) is hungry$")] +async fn someone_is_hungry(w: &mut World, user: String) { + sleep(Duration::from_secs(2)).await; + + w.user = Some(user); +} + +#[when(regex = r"^(?:he|she|they) eats? (\d+) cucumbers?$")] +async fn eat_cucumbers(w: &mut World, count: usize) { + sleep(Duration::from_secs(2)).await; + + w.capacity += count; + + assert!(w.capacity < 4, "{} exploded!", w.user.as_ref().unwrap()); +} + +#[then(regex = r"^(?:he|she|they) (?:is|are) full$")] +async fn is_full(w: &mut World) { + sleep(Duration::from_secs(2)).await; + + assert_eq!(w.capacity, 3, "{} isn't full!", w.user.as_ref().unwrap()); +} + +#[tokio::main] +async fn main() { + World::run("tests/features/readme").await; +} +``` -### Supporting crates +Add test to `Cargo.toml`: +```toml +[[test]] +name = "readme" +harness = false # allows Cucumber to print output instead of libtest +``` -The full gamut of Cucumber's Gherkin language is implemented by the -[gherkin-rust](https://github.com/bbqsrc/gherkin-rust) project. Most features of the Gherkin -language are parsed already and accessible via the relevant structs. +[![asciicast](https://asciinema.org/a/YP24WIM1PGr2I9znFYKfmbkyo.svg)](https://asciinema.org/a/YP24WIM1PGr2I9znFYKfmbkyo) -### License +For more examples check out the Book ([current][1] | [edge][2]). + + + + +## Supporting crates + +The full gamut of Cucumber's [Gherkin] language is implemented by the [`gherkin-rust`](https://github.com/bbqsrc/gherkin-rust) crate. Most features of the [Gherkin] language are parsed already and accessible via the relevant structs. + + + + +## License This project is licensed under either of - * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + * Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/bbqsrc/cucumber-rust/blob/main/LICENSE-APACHE) or ) + * MIT license ([LICENSE-MIT](https://github.com/bbqsrc/cucumber-rust/blob/main/LICENSE-MIT) or ) at your option. + + + + +[Cucumber]: https://cucumber.io +[Gherkin]: https://cucumber.io/docs/gherkin/reference + +[1]: https://bbqsrc.github.io/cucumber-rust/current +[2]: https://bbqsrc.github.io/cucumber-rust/main diff --git a/book/.gitignore b/book/.gitignore index 7585238e..16a69e47 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1 +1 @@ -book +/_rendered/ diff --git a/book/README.md b/book/README.md new file mode 100644 index 00000000..ece8d1c1 --- /dev/null +++ b/book/README.md @@ -0,0 +1,64 @@ +# cucumber-rust book + +Book containing the [`cucumber_rust`](https://crates.io/crates/cucumber_rust) documentation. + + + + +## Contributing + + +### Requirements + +The Book is built with [mdBook](https://github.com/rust-lang-nursery/mdBook). + +You can install it with: +```bash +cargo install mdbook +``` + + +### Local test server + +To launch a local test server that continually re-builds the Book and auto-reloads the page, run: +```bash +mdbook serve + +# or from project root dir: +make book.serve +``` + + +### Building + +You can build the Book to rendered HTML with this command: +```bash +mdbook build + +# or from project root dir: +make book +``` + +The output will be in the `_rendered/` directory. + + +### Running tests + +To run the tests validating all code examples in the book, run: + +```bash +cd tests/ +cargo test + +# or from project root dir: +make test.book +``` + + + + +## Test setup + +All Rust code examples in the Book are compiled on the CI. + +This is done using the [`skeptic`](https://github.com/budziq/rust-skeptic) crate. diff --git a/book/book.toml b/book/book.toml index 91a2ccd9..d29ab2f1 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,6 +1,13 @@ [book] -authors = ["alvskar"] +authors = ["alvskar", "ilslv"] language = "en" multilingual = false src = "src" title = "Cucumber" + +[build] +build-dir = "_rendered" +create-missing = false + +[output.html] +git_repository_url = "https://github.com/bbqsrc/cucumber-rust" diff --git a/book/src/Getting_Started.md b/book/src/Getting_Started.md index 34c378a9..6043b376 100644 --- a/book/src/Getting_Started.md +++ b/book/src/Getting_Started.md @@ -1,49 +1,45 @@ # Getting Started -Adding Cucumber to your project requires some ground work. Cucumber tests are ran along with other tests via `cargo test`, but rely on a `.feature` file corresponding to the given test, as well as a set of steps described in code that corresponds to the steps in the feature file. +Adding [Cucumber] to your project requires some groundwork. [Cucumber] tests are run along with other tests via `cargo test`, but rely on `.feature` files corresponding to the given test, as well as a set of step matchers described in code corresponding to the steps in those `.feature` files. -To start, create a directory called `tests/` in the root of your project and add a file to represent your test target; in this walkthrough we use `cucumber.rs`. +To start, create a directory called `tests/` in the root of your project and add a file to represent your test target (in this walkthrough we use `example.rs`). -Add this to your Cargo.toml: - -``` -[dependencies] -async-trait = "0.1.42" # This is currently required to properly initialize the world in cucumber-rust +Add this to your `Cargo.toml`: +```toml +[dev-dependencies] +async-trait = "0.1" +cucumber_rust = "0.10" +futures = "0.3" [[test]] -name = "cucumber" # This should be the same as the filename of your test target -harness = false # Allows Cucumber to print output instead of libtest - -[dev-dependencies] -cucumber = { package = "cucumber_rust", version = "^0.7.0" } +name = "example" # this should be the same as the filename of your test target +harness = false # allows Cucumber to print output instead of libtest ``` -At this point, while it won't do anything, you should be able to successfully run `cargo test --test cucumber` without errors as long as your `cucumber.rs` has at least a `main()` function. - -Create a directory called `features/` in the root of your project. Put a feature file there, such as `animal.feature`. This should contain the Gherkin for a scenario that you want to test. Here's a very simple example: +At this point, while it won't do anything, you should be able to successfully run `cargo test --test example` without errors, as long as your `example.rs` has at least a `main()` function defined. -``` +Create a directory to store `.feature` files somewhere in your project (in this walkthrough we use `tests/features/book/` directory), and put a `.feature` file there (such as `animal.feature`). This should contain the [Gherkin] spec for a scenario that you want to test. Here's a very simple example: +```gherkin Feature: Animal feature Scenario: If we feed a hungry cat it will no longer be hungry - Given A hungry cat + Given a hungry cat When I feed the cat - Then The cat is not hungry - + Then the cat is not hungry ``` -Here is how we actually relate the text in this feature file to the tests themselves. - -Every test scenario needs a `World` object. Often `World` holds state that is changing as Cucumber goes through each step in a scenario. The basic requirement for a `World` object is a `new()` function. +Here is how we actually relate the text in this `.feature` file to the tests themselves: every test scenario needs a `World` object. Often `World` holds a state that is changing as [Cucumber] goes through each step in a scenario. The basic requirement for a `World` object is a `new()` function. -To enable testing of our Animal feature, add this code to `cucumber.rs`: +To enable testing of our `animal.feature`, add this code to `example.rs`: +```rust +use std::convert::Infallible; -``` use async_trait::async_trait; -use std::convert::Infallible; +use cucumber_rust::{given, World, WorldInit}; -// These `Cat` definitions would normally be inside your project's code, but -// we create them here to contain the test to just `cucumber.rs` +// These `Cat` definitions would normally be inside your project's code, +// not test code, but we create them here for the show case. +#[derive(Debug)] struct Cat { pub hungry: bool, } @@ -54,15 +50,17 @@ impl Cat { } } -/// A World is your shared, likely mutable state +// `World` is your shared, likely mutable state. +#[derive(Debug, WorldInit)] pub struct AnimalWorld { cat: Cat, } -/// `cucumber::World` needs to be implemented so this World is accepted in `Steps` +// `World` needs to be implemented, so Cucumber knows how to construct it on +// each `Scenario`. #[async_trait(?Send)] -impl cucumber::World for AnimalWorld { - // We require some error type +impl World for AnimalWorld { + // We require some error type. type Error = Infallible; async fn new() -> Result { @@ -72,177 +70,514 @@ impl cucumber::World for AnimalWorld { } } -// These are the actual test steps that will be matched against all your features -mod addition_steps { - use cucumber::Steps; - - pub fn steps() -> Steps { - let mut builder: Steps = Steps::new(); - - builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }); - - builder - } +// Steps are defined with `given`, `when` and `then` macros. +#[given("a hungry cat")] +fn hungry_cat(world: &mut AnimalWorld) { + world.cat.hungry = true; } -// This runs before everything else, so you can setup things here +// This runs before everything else, so you can setup things here. fn main() { - let runner = cucumber::Cucumber::::new() - .features(&["./features"]) - .steps(addition_steps::steps()); - - // You may choose any executor you like (Tokio, async-std, etc) - // You may even have an async main, it doesn't matter. The point is that + // You may choose any executor you like (`tokio`, `async-std`, etc.). + // You may even have an `async` main, it doesn't matter. The point is that // Cucumber is composable. :) - futures::executor::block_on(runner.run()); + futures::executor::block_on(AnimalWorld::run("/tests/features/book")); } ``` If you run this, you should see an output like: -![Cucumber run with just the Given step](cucumber_start.png "Given Step") + -You will see a checkmark next to "Given A hungry cat", which means that test step has been matched and executed. +You will see a checkmark next to `Given A hungry cat`, which means that test step has been matched and executed. -But then for the next step: "I feed the cat", there is a "⚡ Not yet implemented (skipped)" below it. This is because we have nothing in our steps that matches this sentence. The remaining steps in the scenario, since they depend on this skipped one, are not looked at at all. +But then for the next step `I feed the cat` there is a `? ... (skipped)`. This is because we have nothing in our steps that matches this sentence. The remaining steps in the scenario, since they depend on this skipped one, are not looked and run at all. -The steps are created as such: +There are 3 types of steps: +- `given`: for defining the starting conditions and often initializing the data in the `World`; +- `when`: for events or actions that are may trigger certain changes in the `World`; +- `then`: to validate that the `World` has changed the way we would expect. + +These various `Step` functions are executed to transform the `World`. As such, mutable reference to the world must always be passed in. The `Step` itself is also made available. + +The steps matchers take a string, which is the name of the given `Step` (i.e., the literal string, such as `A hungry cat`), and then a function closure that takes a `World` and then the `Step` itself. + +We also support regexes: +```rust +# use std::convert::Infallible; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, World, WorldInit}; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +#[given(regex = r"^a hungry (\S+)$")] +fn hungry_someone(world: &mut AnimalWorld, who: String) { + assert_eq!(who, "cat"); + world.cat.hungry = true; +} +# +# fn main() { +# futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +# } +``` +We can add a `when` step after our `given` step: +```rust +# use std::convert::Infallible; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, when, World, WorldInit}; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +# #[given("a hungry cat")] +# fn hungry_cat(world: &mut AnimalWorld) { +# world.cat.hungry = true; +# } +# +// Don't forget to additionally `use cucumber_rust::when`. + +#[when("I feed the cat")] +fn feed_cat(world: &mut AnimalWorld) { + world.cat.feed(); +} +# +# fn main() { +# futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +# } ``` -builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }); + +If you run the tests again, you'll see that two lines are green now and the next one is marked as not yet implemented: + + + +Finally: how do we validate our result? We expect that this will cause some change in the cat and that the cat will no longer be hungry since it has been fed. The `then()` step follows to assert this, as our feature says: +```rust +# use std::convert::Infallible; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, then, when, World, WorldInit}; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +# #[given("a hungry cat")] +# fn hungry_cat(world: &mut AnimalWorld) { +# world.cat.hungry = true; +# } +# +# #[when("I feed the cat")] +# fn feed_cat(world: &mut AnimalWorld) { +# world.cat.feed(); +# } +# +// Don't forget to additionally `use cucumber_rust::then`. + +#[then("the cat is not hungry")] +fn cat_is_fed(world: &mut AnimalWorld) { + assert!(!world.cat.hungry); +} +# +# fn main() { +# futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +# } ``` -There are 3 types of steps: +If you run the test now, you'll see that all steps are accounted for and the test succeeds: -- given, which is for defining the starting conditions and often initializing the data in the `World` -- when, for events or actions that are may trigger certain changes in the `World` -- then, to validate that the `World` has changed the way we would expect + + +If you want to be assured that your validation is indeed happening, you can change the assertion for the cat being hungry from `true` to `false` temporarily: +```rust,should_panic +# use std::convert::Infallible; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, then, when, World, WorldInit}; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# // We require some error type +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +# #[given("a hungry cat")] +# fn hungry_cat(world: &mut AnimalWorld) { +# world.cat.hungry = true; +# } +# +# #[when("I feed the cat")] +# fn feed_cat(world: &mut AnimalWorld) { +# world.cat.feed(); +# } +# +#[then("the cat is not hungry")] +fn cat_is_fed(world: &mut AnimalWorld) { + assert!(world.cat.hungry); +} +# fn main() { +# futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +# } +``` -In order to hook up code to these steps, we use the functions provided by `cucumber-rust`: https://docs.rs/cucumber_rust/0.7.3/cucumber_rust/struct.Steps.html +And you should see the test failing: -These various Steps functions are executed to transform the world and then return it. As such, the world must always be passed in. The step itself is also made available. + -The steps functions take a string, which is the name of the given `Step` (i.e., the literal string, such as "A hungry cat"), and then a function closure that takes a `World` and then the `Step` itself. +What if we also wanted to validate that even if the cat was never hungry to begin with, it wouldn't end up hungry after it was fed? We can add another scenario that looks quite similar: +```gherkin +Feature: Animal feature -We can add a `when` step after our `given` step: + Scenario: If we feed a hungry cat it will no longer be hungry + Given a hungry cat + When I feed the cat + Then the cat is not hungry + + Scenario: If we feed a satiated cat it will not become hungry + Given a satiated cat + When I feed the cat + Then the cat is not hungry ``` -builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }) - .when("I feed the cat", |mut world, _step| { - world - }); + +The only thing that is different is the `Given` step. But we don't have to write a new matcher! We can leverage regex support: +```rust +# use std::convert::Infallible; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, then, when, World, WorldInit}; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +#[given(regex = r"^a (hungry|satiated) cat$")] +fn hungry_cat(world: &mut AnimalWorld, state: String) { + match state.as_str() { + "hungry" => world.cat.hungry = true, + "satiated" => world.cat.hungry = false, + _ => unreachable!(), + } +} +# +# #[when("I feed the cat")] +# fn feed_cat(world: &mut AnimalWorld) { +# world.cat.feed(); +# } +# +# #[then("the cat is not hungry")] +# fn cat_is_fed(world: &mut AnimalWorld) { +# assert!(!world.cat.hungry); +# } +# +# fn main() { +# futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +# } ``` -If you run the tests again, you'll see that two lines are green now and the next one is marked as not yet implemented: +We surround regex with `^..$` to ensure the __exact__ match. This is much more useful as you add more and more steps, so they wouldn't interfere with each other. -![Cucumber run with a Given and When step](cucumber_when.png "Given and When Step") +[Cucumber] will reuse these steps: -But we should actually do something in our When step so we can test our Animal feature. Or When step mentions feeding the cat, so let's change the `when()`: + -``` - .when("I feed the cat", |mut world, _step| { - world.cat.feed(); - world - }); -``` +A contrived example, but this demonstrates that steps can be reused as long as they are sufficiently precise in both their description and implementation. If, for example, the wording for our `Then` step was `The cat is no longer hungry`, it'd imply something about the expected initial state, when that is not the purpose of a `Then` step, but rather of the `Given` step. -Finally, how do we validate our result? We expect that this will cause some change in the cat and that the cat will no longer be hungry since it has been fed. The `then()` step follows to assert this, as our feature says: +
+Full example so far: +
-``` -builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }) - .when("I feed the cat", |mut world, _step| { - world.cat.feed(); - world - }) - .then("The cat is not hungry", |world, _step| { - assert_eq!(world.cat.hungry, false); - world - }); -``` +```rust +use std::convert::Infallible; -If you run the test now, you'll see that all steps are accounted for and the test succeeds: +use async_trait::async_trait; +use cucumber_rust::{given, then, when, World, WorldInit}; + +#[derive(Debug)] +struct Cat { + pub hungry: bool, +} -![Full Cucumber run](cucumber_then.png "Complete scenario") +impl Cat { + fn feed(&mut self) { + self.hungry = false; + } +} + +#[derive(Debug, WorldInit)] +pub struct AnimalWorld { + cat: Cat, +} -If you want to be assured that your validation is indeed happening, you can change the assert for the cat being hungry from `true` to `false` temporarily: +#[async_trait(?Send)] +impl World for AnimalWorld { + type Error = Infallible; + async fn new() -> Result { + Ok(Self { + cat: Cat { hungry: false }, + }) + } +} + +#[given(regex = r"^a (hungry|satiated) cat$")] +fn hungry_cat(world: &mut AnimalWorld, state: String) { + match state.as_str() { + "hungry" => world.cat.hungry = true, + "satiated" => world.cat.hungry = false, + _ => unreachable!(), + } +} + +#[when("I feed the cat")] +fn feed_cat(world: &mut AnimalWorld) { + world.cat.feed(); +} + +#[then("the cat is not hungry")] +fn cat_is_fed(world: &mut AnimalWorld) { + assert!(!world.cat.hungry); +} + +fn main() { + futures::executor::block_on(AnimalWorld::run("/tests/features/book")); +} ``` -builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }) - .when("I feed the cat", |mut world, _step| { - world.cat.feed(); - world - }) - .then("The cat is not hungry", |world, _step| { - assert_eq!(world.cat.hungry, true); - world - }); +
+ + + + +## Asyncness + +Let's play with `async` support a bit! + +For that switch `futures` for `tokio` in dependencies: + +```toml +[dev-dependencies] +async-trait = "0.1" +cucumber_rust = "0.10" +tokio = { version = "1.10", features = ["macros", "rt-multi-thread", "time"] } + +[[test]] +name = "cucumber" # this should be the same as the filename of your test target +harness = false # allows Cucumber to print output instead of libtest ``` -And you should see the test fail: +And simply `sleep` on each step to test the `async` support. In the real world you of course will switch it up to web/database requests, etc. +```rust +# use std::{convert::Infallible, time::Duration}; +# +# use async_trait::async_trait; +# use cucumber_rust::{given, then, when, World, WorldInit}; +# use tokio::time::sleep; +# +# #[derive(Debug)] +# struct Cat { +# pub hungry: bool, +# } +# +# impl Cat { +# fn feed(&mut self) { +# self.hungry = false; +# } +# } +# +# #[derive(Debug, WorldInit)] +# pub struct AnimalWorld { +# cat: Cat, +# } +# +# #[async_trait(?Send)] +# impl World for AnimalWorld { +# type Error = Infallible; +# +# async fn new() -> Result { +# Ok(Self { +# cat: Cat { hungry: false }, +# }) +# } +# } +# +#[given(regex = r"^a (hungry|satiated) cat$")] +async fn hungry_cat(world: &mut AnimalWorld, state: String) { + sleep(Duration::from_secs(2)).await; + + match state.as_str() { + "hungry" => world.cat.hungry = true, + "satiated" => world.cat.hungry = false, + _ => unreachable!(), + } +} -![Failing step](cucumber_fail.png "Failing step") +#[when("I feed the cat")] +async fn feed_cat(world: &mut AnimalWorld) { + sleep(Duration::from_secs(2)).await; -What if we also wanted to validate that even if the cat was never hungry to begin with, it wouldn't end up hungry after it was fed? We can add another scenario that looks quite similar: + world.cat.feed(); +} + +#[then("the cat is not hungry")] +async fn cat_is_fed(world: &mut AnimalWorld) { + sleep(Duration::from_secs(2)).await; + + assert!(!world.cat.hungry); +} +#[tokio::main] +async fn main() { + AnimalWorld::run("/tests/features/book").await; +} ``` + + + +Hm, it looks like the executor waited only for the first `Feature` 🤔, what's going on? + +By default `Cucumber` executes `Scenarios` [concurrently](https://en.wikipedia.org/wiki/Concurrent_computing)! That means executor actually did wait for all the steps, but overlapped! This allows you to execute tests much faster! + +If for some reason you don't want to run your `Scenarios` concurrently, use `@serial` tag on them: + +```gherkin Feature: Animal feature + @serial Scenario: If we feed a hungry cat it will no longer be hungry - Given A hungry cat + Given a hungry cat When I feed the cat - Then The cat is not hungry + Then the cat is not hungry + @serial Scenario: If we feed a satiated cat it will not become hungry - Given A satiated cat + Given a satiated cat When I feed the cat - Then The cat is not hungry - + Then the cat is not hungry ``` -The only thing that is different is the Given. That is then the only new step we need to add to our builder (remember that the order of the steps, or the relationship between them, is expressed in the `.feature` file and not in the code): + -``` -builder - .given("A hungry cat", |mut world, _step| { - world.cat.hungry = true; - world - }) - .given("A satiated cat", |mut world, _step| { - world.cat.hungry = true; - world - }) - .when("I feed the cat", |mut world, _step| { - world.cat.feed(); - world - }) - .then("The cat is not hungry", |world, _step| { - assert_eq!(world.cat.hungry, false); - world - }); -``` -Cucumber reuses the steps: -![Steps reused between two scenarious](cucumber_reuse.png "Step reuse") -A contrived example, but this demonstrates that steps can be reused as long as they are sufficiently precise in both their description and implementation. If, for example, the wording for our "Then" step was "The cat is no longer hungry", it'd imply something about the expected initial state, when that is not the purpose of a "Then" step, but rather of the "Given" step. +[Cucumber]: https://cucumber.io +[Gherkin]: https://cucumber.io/docs/gherkin/reference diff --git a/book/src/Introduction.md b/book/src/Introduction.md index 4f965491..7db8d502 100644 --- a/book/src/Introduction.md +++ b/book/src/Introduction.md @@ -1,8 +1,8 @@ # Cucumber -[Cucumber](https://cucumber.io/) is a specification for running tests in a behavioral driven development style workflow ([BDD](https://en.wikipedia.org/wiki/Behavior-driven_development)). It assumes involvement of non-technical members on a project and as such provides a human-readable syntax for the definition of features, via the langauge [Gherkin](https://cucumber.io/docs/gherkin/reference/). A typical feature could look something like this: +[Cucumber] is a specification for running tests in a behavioral driven development style workflow ([BDD](https://en.wikipedia.org/wiki/Behavior-driven_development)). It assumes involvement of non-technical members on a project and as such provides a human-readable syntax for the definition of features, via the language [Gherkin]. A typical feature could look something like this: -``` +```gherkin Feature: User login Scenario: User tries to log in with an incorrect password @@ -11,20 +11,32 @@ Feature: User login Then the user will see a messagebox with an alert that their password is wrong ``` -These features are agnostic to the implementation, the only requirement is that they follow the expected format of phrases followed by the keywords ("Given", "When", "Then"). Gherkin offers support for languages other than English, as well. +These features are agnostic to the implementation, the only requirement is that they follow the expected format of phrases followed by the keywords (`Given`, `When`, `Then`). -Cucumber implementations then simply hook into these keywords and execute the logic that corresponds to the keywords. `cucumber-rust` is one such implementation and is the subject of this book. +[Gherkin] offers support for languages other than English, as well. [Cucumber] implementations then simply hook into these keywords and execute the logic that corresponds to the keywords. [`cucumber-rust`] is one of such implementations and is the subject of this book. -``` -.given("a user portal where one can login", +```rust,ignore +#[given("a user portal where one can login")] +fn portal(w: &mut World) { /* initial setup of the feature being tested */ -) -.when("a user types in the correct username but incorrect password", +} + +#[when("a user types in the correct username but incorrect password")] +fn incorrect_password(w: &mut World) { /* performing the relevant actions against the feature */ -) -.then("the user will see a messagebox with an alert that their password is wrong", +} + +#[then("the user will see a messagebox with an alert that their password is wrong")] +fn alert(w: &mut World) { /* assertions and validation that the feature is working as intended */ -) +} ``` -Since the goal is the testing of externally identifiable behavior of some feature, it would be a misnomer to use Cucumber to test specific private aspects or isolated modules. Cucumber tests are more likely to take the form of integration or functional testing. +Since the goal is the testing of externally identifiable behavior of some feature, it would be a misnomer to use [Cucumber] to test specific private aspects or isolated modules. [Cucumber] tests are more likely to take the form of integration, functional or E2E testing. + + + + +[Cucumber]: https://cucumber.io +[Gherkin]: https://cucumber.io/docs/gherkin/reference +[`cucumber-rust`]: https://docs.rs/cucumber-rust diff --git a/book/src/cucumber_all.png b/book/src/cucumber_all.png deleted file mode 100644 index 41b390a5..00000000 Binary files a/book/src/cucumber_all.png and /dev/null differ diff --git a/book/src/cucumber_fail.png b/book/src/cucumber_fail.png deleted file mode 100644 index b076c707..00000000 Binary files a/book/src/cucumber_fail.png and /dev/null differ diff --git a/book/src/cucumber_reuse.png b/book/src/cucumber_reuse.png deleted file mode 100644 index 7197b86d..00000000 Binary files a/book/src/cucumber_reuse.png and /dev/null differ diff --git a/book/src/cucumber_start.png b/book/src/cucumber_start.png deleted file mode 100644 index da10ed9c..00000000 Binary files a/book/src/cucumber_start.png and /dev/null differ diff --git a/book/src/cucumber_then.png b/book/src/cucumber_then.png deleted file mode 100644 index 6c23438a..00000000 Binary files a/book/src/cucumber_then.png and /dev/null differ diff --git a/book/src/cucumber_when.png b/book/src/cucumber_when.png deleted file mode 100644 index 67c15277..00000000 Binary files a/book/src/cucumber_when.png and /dev/null differ diff --git a/book/tests/.gitignore b/book/tests/.gitignore new file mode 100644 index 00000000..6aa10640 --- /dev/null +++ b/book/tests/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/book/tests/Cargo.toml b/book/tests/Cargo.toml new file mode 100644 index 00000000..bf8efdea --- /dev/null +++ b/book/tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cucumber_rust_book_tests" +version = "0.1.0" +edition = "2018" +resolver = "2" +authors = [ + "Brendan Molloy ", + "Ilya Solovyiov ", + "Kai Ren ", +] +publish = false + +[dependencies] +async-trait = "0.1" +cucumber_rust = { path = "../.." } +futures = "0.3" +skeptic = "0.13" +tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } + +[build-dependencies] +skeptic = "0.13" diff --git a/book/tests/build.rs b/book/tests/build.rs new file mode 100644 index 00000000..80e0cbe1 --- /dev/null +++ b/book/tests/build.rs @@ -0,0 +1,4 @@ +fn main() { + let files = skeptic::markdown_files_of_directory("../src/"); + skeptic::generate_doc_tests(&files); +} diff --git a/book/tests/src/lib.rs b/book/tests/src/lib.rs new file mode 100644 index 00000000..e1d5434b --- /dev/null +++ b/book/tests/src/lib.rs @@ -0,0 +1,3 @@ +#![deny(warnings)] + +include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs")); diff --git a/codegen/CHANGELOG.md b/codegen/CHANGELOG.md new file mode 100644 index 00000000..04377d64 --- /dev/null +++ b/codegen/CHANGELOG.md @@ -0,0 +1,17 @@ +### Current + +#### Known issues: + +- `Scenario Outline` is treated the same as `Outline` or `Example` in the parser ([gherkin/#19](https://github.com/bbqsrc/gherkin-rust/issues/19)) + +### [0.10.0] — ??? +[0.10.0]: /../../tree/v0.10.0 + +- Update attributes according to the redesign done in `0.10` of `cucumber_rust` crate. +- Replace `#[given(step)]`, `#[when(step)]` and `#[then(step)]` with single `#[step]` attribute. + +### 0.1.0 — 2021-01-18 + +- Initial implementation for [`given`](https://docs.rs/cucumber_rust_codegen/0.9.0/cucumber_rust_codegen/attr.given.html), + [`when`](https://docs.rs/cucumber_rust_codegen/0.9.0/cucumber_rust_codegen/attr.when.html), + [`then`](https://docs.rs/cucumber_rust_codegen/0.9.0/cucumber_rust_codegen/attr.then.html) attribute macros. diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index c1ab3686..411a47e8 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,42 +1,39 @@ [package] name = "cucumber_rust_codegen" -version = "0.1.0" +version = "0.10.0" # should be the same as main crate version edition = "2018" +resolver = "2" +description = "Code generation for `cucumber_rust` crate." +license = "MIT OR Apache-2.0" authors = [ "Ilya Solovyiov ", - "Kai Ren " + "Kai Ren ", ] -description = "Code generation for `cucumber_rust` crate." -license = "MIT OR Apache-2.0" -keywords = ["cucumber", "codegen", "macros"] -categories = ["asynchronous", "development-tools::testing"] repository = "https://github.com/bbqsrc/cucumber-rust" documentation = "https://docs.rs/cucumber_rust_codegen" homepage = "https://github.com/bbqsrc/cucumber-rust" +readme = "README.md" +categories = ["asynchronous", "development-tools::testing"] +keywords = ["cucumber", "codegen", "macros"] [lib] proc-macro = true [dependencies] inflections = "1.1" -itertools = "0.9" +itertools = "0.10" proc-macro2 = "1.0" quote = "1.0" regex = "1.4" syn = { version = "1.0", features = ["derive", "extra-traits", "full"] } [dev-dependencies] -async-trait = "0.1.41" +async-trait = "0.1" futures = "0.3" -cucumber_rust = { path = "../", features = ["macros"] } -tokio = { version = "0.3", features = ["macros", "rt-multi-thread", "time"] } +cucumber_rust = { path = "..", features = ["macros"] } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "time"] } [[test]] name = "example" path = "tests/example.rs" harness = false - -[[test]] -name = "readme" -path = "tests/readme.rs" -harness = false diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 00000000..eecffcd0 --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,28 @@ +# cucumber-rust-codegen + +[![Documentation](https://docs.rs/cucumber_rust_codegen/badge.svg)](https://docs.rs/cucumber_rust_codegen) +[![Actions Status](https://github.com/bbqsrc/cucumber-rust/workflows/CI/badge.svg)](https://github.com/bbqsrc/cucumber-rust/actions) +[![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance) + +- [Changelog](https://github.com/bbqsrc/cucumber-rust/blob/main/codegen/CHANGELOG.md) + +Code generation for [`cucumber_rust`] tests auto-wiring. + +DO NOT use it directly, use [`cucumber_rust`] crate instead. + + + + +## License + +This project is licensed under either of + +* Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/bbqsrc/cucumber-rust/blob/main/LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](https://github.com/bbqsrc/cucumber-rust/blob/main/LICENSE-MIT) or ) + +at your option. + + + + +[`cucumber_rust`]: https://docs.rs/cucumber_rust diff --git a/codegen/src/attribute.rs b/codegen/src/attribute.rs index 5f970168..de50dbc1 100644 --- a/codegen/src/attribute.rs +++ b/codegen/src/attribute.rs @@ -14,12 +14,14 @@ use std::mem; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use regex::{self, Regex}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned as _, }; -/// Generates code of `#[given]`, `#[when]` and `#[then]` attribute macros expansion. +/// Generates code of `#[given]`, `#[when]` and `#[then]` attribute macros +/// expansion. pub(crate) fn step( attr_name: &'static str, args: TokenStream, @@ -28,7 +30,8 @@ pub(crate) fn step( Step::parse(attr_name, args, input).and_then(Step::expand) } -/// Parsed state (ready for code generation) of the attribute and the function it's applied to. +/// Parsed state (ready for code generation) of the attribute and the function +/// it's applied to. #[derive(Clone, Debug)] struct Step { /// Name of the attribute (`given`, `when` or `then`). @@ -40,27 +43,34 @@ struct Step { /// Function the attribute is applied to. func: syn::ItemFn, - /// Name of the function argument representing a [`cucumber::StepContext`][1] reference. + /// Name of the function argument representing a [`gherkin::Step`] + /// reference. /// - /// [1]: cucumber_rust::StepContext - ctx_arg_name: Option, + /// [`gherkin::Step`]: https://bit.ly/3j42hcd + step_arg_name: Option, } impl Step { /// Parses [`Step`] definition from the attribute macro input. - fn parse(attr_name: &'static str, attr: TokenStream, body: TokenStream) -> syn::Result { + fn parse( + attr_name: &'static str, + attr: TokenStream, + body: TokenStream, + ) -> syn::Result { let attr_arg = syn::parse2::(attr)?; let mut func = syn::parse2::(body)?; - let ctx_arg_name = { - let (arg_marked_as_step, _) = remove_all_attrs((attr_name, "context"), &mut func); + let step_arg_name = { + let (arg_marked_as_step, _) = + remove_all_attrs_if_needed("step", &mut func); match arg_marked_as_step.len() { 0 => Ok(None), 1 => { // Unwrapping is OK here, because // `arg_marked_as_step.len() == 1`. - let (ident, _) = parse_fn_arg(arg_marked_as_step.first().unwrap())?; + let (ident, _) = + parse_fn_arg(arg_marked_as_step.first().unwrap())?; Ok(Some(ident.clone())) } _ => Err(syn::Error::new( @@ -83,10 +93,10 @@ impl Step { }); Ok(Self { - attr_arg, attr_name, + attr_arg, func, - ctx_arg_name, + step_arg_name, }) } @@ -97,12 +107,9 @@ impl Step { let func = &self.func; let func_name = &func.sig.ident; - let mut func_args = TokenStream::default(); - let mut addon_parsing = None; - let mut is_ctx_arg_considered = false; - if is_regex { - if let Some(elem_ty) = parse_slice_from_second_arg(&func.sig) { - addon_parsing = Some(quote! { + let (func_args, addon_parsing) = if is_regex { + if let Some(elem_ty) = find_first_slice(&func.sig) { + let addon_parsing = Some(quote! { let __cucumber_matches = __cucumber_ctx .matches .iter() @@ -110,72 +117,80 @@ impl Step { .enumerate() .map(|(i, s)| { s.parse::<#elem_ty>().unwrap_or_else(|e| panic!( - "Failed to parse {} element '{}': {}", i, s, e, + "Failed to parse element at {} '{}': {}", + i, s, e, )) }) .collect::>(); }); - func_args = quote! { - __cucumber_matches.as_slice(), - } + let func_args = func + .sig + .inputs + .iter() + .skip(1) + .map(|arg| self.borrow_step_or_slice(arg)) + .collect::>()?; + + (func_args, addon_parsing) } else { #[allow(clippy::redundant_closure_for_method_calls)] - let (idents, parsings): (Vec<_>, Vec<_>) = itertools::process_results( - func.sig - .inputs - .iter() - .skip(1) - .map(|arg| self.arg_ident_and_parse_code(arg)), - |i| i.unzip(), - )?; - is_ctx_arg_considered = true; - - addon_parsing = Some(quote! { - let mut __cucumber_iter = __cucumber_ctx.matches.iter().skip(1); + let (idents, parsings): (Vec<_>, Vec<_>) = + itertools::process_results( + func.sig + .inputs + .iter() + .skip(1) + .map(|arg| self.arg_ident_and_parse_code(arg)), + |i| i.unzip(), + )?; + + let addon_parsing = Some(quote! { + let mut __cucumber_iter = __cucumber_ctx + .matches.iter() + .skip(1); #( #parsings )* }); - func_args = quote! { + let func_args = quote! { #( #idents, )* - } + }; + + (func_args, addon_parsing) } - } - if self.ctx_arg_name.is_some() && !is_ctx_arg_considered { - func_args = quote! { - #func_args - ::std::borrow::Borrow::borrow(&__cucumber_ctx), - }; - } + } else if self.step_arg_name.is_some() { + ( + quote! { ::std::borrow::Borrow::borrow(&__cucumber_ctx.step), }, + None, + ) + } else { + (TokenStream::default(), None) + }; let world = parse_world_from_args(&self.func.sig)?; let constructor_method = self.constructor_method(); - let step_matcher = self.attr_arg.literal().value(); - let step_caller = if func.sig.asyncness.is_none() { - let caller_name = format_ident!("__cucumber_{}_{}", self.attr_name, func_name); - quote! { - { - #[automatically_derived] - fn #caller_name( - mut __cucumber_world: #world, - __cucumber_ctx: ::cucumber_rust::StepContext, - ) -> #world { - #addon_parsing - #func_name(&mut __cucumber_world, #func_args); - __cucumber_world - } - - #caller_name - } - } + let step_matcher = self.attr_arg.regex_literal().value(); + let caller_name = + format_ident!("__cucumber_{}_{}", self.attr_name, func_name); + let awaiting = if func.sig.asyncness.is_some() { + quote! { .await } } else { - quote! { - ::cucumber_rust::t!( - |mut __cucumber_world, __cucumber_ctx| { + quote! {} + }; + let step_caller = quote! { + { + #[automatically_derived] + fn #caller_name<'w>( + __cucumber_world: &'w mut #world, + __cucumber_ctx: ::cucumber_rust::step::Context, + ) -> ::cucumber_rust::codegen::LocalBoxFuture<'w, ()> { + let f = async move { #addon_parsing - #func_name(&mut __cucumber_world, #func_args).await; - __cucumber_world - } - ) + #func_name(__cucumber_world, #func_args)#awaiting; + }; + ::std::boxed::Box::pin(f) + } + + #caller_name } }; @@ -183,11 +198,15 @@ impl Step { #func #[automatically_derived] - ::cucumber_rust::private::submit!( - #![crate = ::cucumber_rust::private] { - <#world as ::cucumber_rust::private::WorldInventory< - _, _, _, _, _, _, _, _, _, _, _, _, - >>::#constructor_method(#step_matcher, #step_caller) + ::cucumber_rust::codegen::submit!( + #![crate = ::cucumber_rust::codegen] { + <#world as ::cucumber_rust::codegen::WorldInventory< + _, _, _, + >>::#constructor_method( + ::cucumber_rust::codegen::Regex::new(#step_matcher) + .unwrap(), + #step_caller, + ) } ); }) @@ -196,21 +215,7 @@ impl Step { /// Composes name of the [`WorldInventory`] method to wire this [`Step`] /// with. fn constructor_method(&self) -> syn::Ident { - let regex = match &self.attr_arg { - AttributeArgument::Regex(_) => "_regex", - AttributeArgument::Literal(_) => "", - }; - format_ident!( - "new_{}{}{}", - self.attr_name, - regex, - self.func - .sig - .asyncness - .as_ref() - .map(|_| "_async") - .unwrap_or_default(), - ) + format_ident!("new_{}", self.attr_name) } /// Returns [`syn::Ident`] and parsing code of the given function's @@ -225,16 +230,23 @@ impl Step { ) -> syn::Result<(&'a syn::Ident, TokenStream)> { let (ident, ty) = parse_fn_arg(arg)?; - let is_ctx_arg = self.ctx_arg_name.as_ref().map(|i| *i == *ident) == Some(true); + let is_ctx_arg = + self.step_arg_name.as_ref().map(|i| *i == *ident) == Some(true); let decl = if is_ctx_arg { quote! { - let #ident = ::std::borrow::Borrow::borrow(&__cucumber_ctx); + let #ident = + ::std::borrow::Borrow::borrow(&__cucumber_ctx.step); } } else { let ty = match ty { syn::Type::Path(p) => p, - _ => return Err(syn::Error::new(ty.span(), "Type path expected")), + _ => { + return Err(syn::Error::new( + ty.span(), + "Type path expected", + )) + } }; let not_found_err = format!("{} not found", ident); @@ -255,6 +267,28 @@ impl Step { Ok((ident, decl)) } + + /// Generates code that borrows [`gherkin::Step`] from context if the given + /// `arg` matches `step_arg_name`, or else borrows parsed slice. + /// + /// [`gherkin::Step`]: https://bit.ly/3j42hcd + fn borrow_step_or_slice( + &self, + arg: &syn::FnArg, + ) -> syn::Result { + if let Some(name) = &self.step_arg_name { + let (ident, _) = parse_fn_arg(arg)?; + if name == ident { + return Ok(quote! { + ::std::borrow::Borrow::borrow(&__cucumber_ctx.step), + }); + } + } + + Ok(quote! { + __cucumber_matches.as_slice(), + }) + } } /// Argument of the attribute macro. @@ -268,10 +302,14 @@ enum AttributeArgument { } impl AttributeArgument { - /// Returns the underlying [`syn::LitStr`]. - fn literal(&self) -> &syn::LitStr { + /// Returns [`syn::LitStr`] to construct a [`Regex`] with. + fn regex_literal(&self) -> syn::LitStr { match self { - Self::Regex(l) | Self::Literal(l) => l, + Self::Regex(l) => l.clone(), + Self::Literal(l) => syn::LitStr::new( + &format!("^{}$", regex::escape(&l.value())), + l.span(), + ), } } } @@ -284,9 +322,14 @@ impl Parse for AttributeArgument { if arg.path.is_ident("regex") { let str_lit = to_string_literal(arg.lit)?; - let _ = regex::Regex::new(str_lit.value().as_str()).map_err(|e| { - syn::Error::new(str_lit.span(), format!("Invalid regex: {}", e.to_string())) - })?; + drop(Regex::new(str_lit.value().as_str()).map_err( + |e| { + syn::Error::new( + str_lit.span(), + format!("Invalid regex: {}", e.to_string()), + ) + }, + )?); Ok(AttributeArgument::Regex(str_lit)) } else { @@ -294,7 +337,9 @@ impl Parse for AttributeArgument { } } - syn::NestedMeta::Lit(l) => Ok(AttributeArgument::Literal(to_string_literal(l)?)), + syn::NestedMeta::Lit(l) => { + Ok(AttributeArgument::Literal(to_string_literal(l)?)) + } syn::NestedMeta::Meta(_) => Err(syn::Error::new( arg.span(), @@ -304,18 +349,34 @@ impl Parse for AttributeArgument { } } -/// Removes all `#[attr_path(attr_arg)]` attributes from the given function -/// signature and returns these attributes along with the corresponding -/// function's arguments. -fn remove_all_attrs<'a>( - (attr_path, attr_arg): (&str, &str), +/// Removes all `#[attr_arg]` attributes from the given function signature and +/// returns these attributes along with the corresponding function's arguments +/// in case there are no more `#[given]`, `#[when]` or `#[then]` attributes. +fn remove_all_attrs_if_needed<'a>( + attr_arg: &str, func: &'a mut syn::ItemFn, ) -> (Vec<&'a syn::FnArg>, Vec) { + let has_other_step_arguments = func.attrs.iter().any(|attr| { + attr.path + .segments + .last() + .map(|segment| { + ["given", "when", "then"] + .iter() + .any(|step| segment.ident == step) + }) + .unwrap_or_default() + }); + func.sig .inputs .iter_mut() .filter_map(|arg| { - if let Some(attr) = remove_attr((attr_path, attr_arg), arg) { + if has_other_step_arguments { + if let Some(attr) = find_attr(attr_arg, arg) { + return Some((&*arg, attr)); + } + } else if let Some(attr) = remove_attr(attr_arg, arg) { return Some((&*arg, attr)); } None @@ -323,56 +384,57 @@ fn remove_all_attrs<'a>( .unzip() } -/// Removes attribute `#[attr_path(attr_arg)]` from function's argument, if any. -fn remove_attr( - (attr_path, attr_arg): (&str, &str), - arg: &mut syn::FnArg, -) -> Option { +/// Finds attribute `#[attr_arg]` from function's argument, if any. +fn find_attr(attr_arg: &str, arg: &mut syn::FnArg) -> Option { + if let syn::FnArg::Typed(typed_arg) = arg { + typed_arg + .attrs + .iter() + .find(|attr| { + attr.path + .get_ident() + .map(|ident| ident == attr_arg) + .unwrap_or_default() + }) + .cloned() + } else { + None + } +} + +/// Removes attribute `#[attr_arg]` from function's argument, if any. +fn remove_attr(attr_arg: &str, arg: &mut syn::FnArg) -> Option { use itertools::{Either, Itertools as _}; if let syn::FnArg::Typed(typed_arg) = arg { let attrs = mem::take(&mut typed_arg.attrs); - let (mut other, mut removed): (Vec<_>, Vec<_>) = attrs.into_iter().partition_map(|attr| { - if eq_path_and_arg((attr_path, attr_arg), &attr) { - Either::Right(attr) - } else { + let (mut other, mut removed): (Vec<_>, Vec<_>) = + attrs.into_iter().partition_map(|attr| { + if let Some(ident) = attr.path.get_ident() { + if ident == attr_arg { + return Either::Right(attr); + } + } Either::Left(attr) - } - }); + }); if removed.len() == 1 { typed_arg.attrs = other; // Unwrapping is OK here, because `step_idents.len() == 1`. return Some(removed.pop().unwrap()); - } else { - other.append(&mut removed); - typed_arg.attrs = other; } + other.append(&mut removed); + typed_arg.attrs = other; } None } -/// Compares attribute's path and argument. -fn eq_path_and_arg((attr_path, attr_arg): (&str, &str), attr: &syn::Attribute) -> bool { - if let Ok(meta) = attr.parse_meta() { - if let syn::Meta::List(meta_list) = meta { - if meta_list.path.is_ident(attr_path) && meta_list.nested.len() == 1 { - // Unwrapping is OK here, because `meta_list.nested.len() == 1`. - if let syn::NestedMeta::Meta(m) = meta_list.nested.first().unwrap() { - return m.path().is_ident(attr_arg); - } - } - } - } - false -} - /// Parses [`syn::Ident`] and [`syn::Type`] from the given [`syn::FnArg`]. fn parse_fn_arg(arg: &syn::FnArg) -> syn::Result<(&syn::Ident, &syn::Type)> { let arg = match arg { syn::FnArg::Typed(t) => t, - _ => { + syn::FnArg::Receiver(_) => { return Err(syn::Error::new( arg.span(), "Expected regular argument, found `self`", @@ -388,28 +450,30 @@ fn parse_fn_arg(arg: &syn::FnArg) -> syn::Result<(&syn::Ident, &syn::Type)> { Ok((ident, arg.ty.as_ref())) } -/// Parses type of a slice element from a second argument of the given function -/// signature. -fn parse_slice_from_second_arg(sig: &syn::Signature) -> Option<&syn::TypePath> { - sig.inputs - .iter() - .nth(1) - .and_then(|second_arg| match second_arg { +/// Parses type of a first slice element of the given function signature. +fn find_first_slice(sig: &syn::Signature) -> Option<&syn::TypePath> { + sig.inputs.iter().find_map(|arg| { + match arg { syn::FnArg::Typed(typed_arg) => Some(typed_arg), - _ => None, - }) - .and_then(|typed_arg| match typed_arg.ty.as_ref() { - syn::Type::Reference(r) => Some(r), - _ => None, - }) - .and_then(|ty_ref| match ty_ref.elem.as_ref() { - syn::Type::Slice(s) => Some(s), - _ => None, - }) - .and_then(|slice| match slice.elem.as_ref() { - syn::Type::Path(ty) => Some(ty), - _ => None, + syn::FnArg::Receiver(_) => None, + } + .and_then(|typed_arg| { + match typed_arg.ty.as_ref() { + syn::Type::Reference(r) => Some(r), + _ => None, + } + .and_then(|ty_ref| { + match ty_ref.elem.as_ref() { + syn::Type::Slice(s) => Some(s), + _ => None, + } + .and_then(|slice| match slice.elem.as_ref() { + syn::Type::Path(ty) => Some(ty), + _ => None, + }) + }) }) + }) } /// Parses [`cucumber::World`] from arguments of the function signature. @@ -421,7 +485,7 @@ fn parse_world_from_args(sig: &syn::Signature) -> syn::Result<&syn::TypePath> { .ok_or_else(|| sig.ident.span()) .and_then(|first_arg| match first_arg { syn::FnArg::Typed(a) => Ok(a), - _ => Err(first_arg.span()), + syn::FnArg::Receiver(_) => Err(first_arg.span()), }) .and_then(|typed_arg| match typed_arg.ty.as_ref() { syn::Type::Reference(r) => Ok(r), @@ -436,7 +500,10 @@ fn parse_world_from_args(sig: &syn::Signature) -> syn::Result<&syn::TypePath> { _ => Err(world_mut_ref.span()), }) .map_err(|span| { - syn::Error::new(span, "First function argument expected to be `&mut World`") + syn::Error::new( + span, + "First function argument expected to be `&mut World`", + ) }) } diff --git a/codegen/src/derive.rs b/codegen/src/derive.rs index 7c2c0eb2..596312a1 100644 --- a/codegen/src/derive.rs +++ b/codegen/src/derive.rs @@ -11,11 +11,14 @@ //! `#[derive(WorldInit)]` macro implementation. use inflections::case::to_pascal_case; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; /// Generates code of `#[derive(WorldInit)]` macro expansion. -pub(crate) fn world_init(input: TokenStream, steps: &[&str]) -> syn::Result { +pub(crate) fn world_init( + input: TokenStream, + steps: &[&str], +) -> syn::Result { let input = syn::parse2::(input)?; let step_types = step_types(steps); @@ -24,7 +27,7 @@ pub(crate) fn world_init(input: TokenStream, steps: &[&str]) -> syn::Result for #world {} @@ -36,75 +39,53 @@ pub(crate) fn world_init(input: TokenStream, steps: &[&str]) -> syn::Result Vec { steps .iter() - .flat_map(|step| { + .map(|step| { let step = to_pascal_case(step); - vec![ - format_ident!("Cucumber{}", step), - format_ident!("Cucumber{}Regex", step), - format_ident!("Cucumber{}Async", step), - format_ident!("Cucumber{}RegexAsync", step), - ] + format_ident!("Cucumber{}", step) }) .collect() } /// Generates structs and their implementations of private traits. -fn generate_step_structs(steps: &[&str], world: &syn::DeriveInput) -> Vec { +fn generate_step_structs( + steps: &[&str], + world: &syn::DeriveInput, +) -> Vec { let (world, world_vis) = (&world.ident, &world.vis); - let idents = [ - ( - syn::Ident::new("Step", Span::call_site()), - syn::Ident::new("CucumberFn", Span::call_site()), - ), - ( - syn::Ident::new("StepRegex", Span::call_site()), - syn::Ident::new("CucumberRegexFn", Span::call_site()), - ), - ( - syn::Ident::new("StepAsync", Span::call_site()), - syn::Ident::new("CucumberAsyncFn", Span::call_site()), - ), - ( - syn::Ident::new("StepRegexAsync", Span::call_site()), - syn::Ident::new("CucumberAsyncRegexFn", Span::call_site()), - ), - ]; - step_types(steps) .iter() - .zip(idents.iter().cycle()) - .map(|(ty, (trait_ty, func))| { + .map(|ty| { quote! { #[automatically_derived] #[doc(hidden)] #world_vis struct #ty { #[doc(hidden)] - pub name: &'static str, + pub regex: ::cucumber_rust::codegen::Regex, #[doc(hidden)] - pub func: ::cucumber_rust::private::#func<#world>, + pub func: ::cucumber_rust::Step<#world>, } #[automatically_derived] - impl ::cucumber_rust::private::#trait_ty<#world> for #ty { + impl ::cucumber_rust::codegen::StepConstructor<#world> for #ty { fn new ( - name: &'static str, - func: ::cucumber_rust::private::#func<#world>, + regex: ::cucumber_rust::codegen::Regex, + func: ::cucumber_rust::Step<#world>, ) -> Self { - Self { name, func } + Self { regex, func } } fn inner(&self) -> ( - &'static str, - ::cucumber_rust::private::#func<#world>, + ::cucumber_rust::codegen::Regex, + ::cucumber_rust::Step<#world>, ) { - (self.name, self.func.clone()) + (self.regex.clone(), self.func.clone()) } } #[automatically_derived] - ::cucumber_rust::private::collect!(#ty); + ::cucumber_rust::codegen::collect!(#ty); } }) .collect() diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 4f957c15..c8d35aeb 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -8,18 +8,20 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Code generation for [`cucumber_rust`] tests auto-wiring. - +#![doc = include_str!("../README.md")] #![deny( - missing_debug_implementations, nonstandard_style, rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, trivial_casts, trivial_numeric_casts )] +#![forbid(non_ascii_idents, unsafe_code)] #![warn( deprecated_in_future, missing_copy_implementations, + missing_debug_implementations, missing_docs, unreachable_pub, unused_import_braces, @@ -38,19 +40,19 @@ macro_rules! step_attribute { /// Attribute to auto-wire the test to the [`World`] implementer. /// /// There are 3 step-specific attributes: - /// - [`given`] - /// - [`when`] - /// - [`then`] + /// - [`macro@given`] + /// - [`macro@when`] + /// - [`macro@then`] /// /// # Example /// /// ``` - /// # use std::{convert::Infallible, rc::Rc}; + /// # use std::{convert::Infallible}; /// # /// # use async_trait::async_trait; /// use cucumber_rust::{given, World, WorldInit}; /// - /// #[derive(WorldInit)] + /// #[derive(Debug, WorldInit)] /// struct MyWorld; /// /// #[async_trait(?Send)] @@ -70,29 +72,27 @@ macro_rules! step_attribute { /// /// #[tokio::main] /// async fn main() { - /// let runner = MyWorld::init(&["./features"]); - /// runner.run().await; + /// MyWorld::run("./tests/features/doctests.feature").await; /// } /// ``` /// /// # Arguments /// - /// - First argument has to be mutable refence to the [`WorldInit`] deriver (your [`World`] - /// implementer). - /// - Other argument's types have to implement [`FromStr`] or it has to be a slice where the - /// element type also implements [`FromStr`]. - /// - To use [`cucumber::StepContext`], name the argument as `context`, **or** mark the argument with - /// a `#[given(context)]` attribute. + /// - First argument has to be mutable reference to the [`WorldInit`] + /// deriver (your [`World`] implementer). + /// - Other argument's types have to implement [`FromStr`] or it has to + /// be a slice where the element type also implements [`FromStr`]. + /// - To use [`gherkin::Step`], name the argument as `step`, + /// **or** mark the argument with a `#[step]` attribute. /// /// ``` /// # use std::convert::Infallible; - /// # use std::rc::Rc; /// # /// # use async_trait::async_trait; - /// # use cucumber_rust::{StepContext, given, World, WorldInit}; + /// # use cucumber_rust::{gherkin::Step, given, World, WorldInit}; /// # - /// #[derive(WorldInit)] - /// struct MyWorld; + /// # #[derive(Debug, WorldInit)] + /// # struct MyWorld; /// # /// # #[async_trait(?Send)] /// # impl World for MyWorld { @@ -106,23 +106,23 @@ macro_rules! step_attribute { /// #[given(regex = r"(\S+) is not (\S+)")] /// fn test_step( /// w: &mut MyWorld, - /// #[given(context)] s: &StepContext, + /// #[step] s: &Step, + /// matches: &[String], /// ) { - /// assert_eq!(s.matches.get(0).unwrap(), "foo"); - /// assert_eq!(s.matches.get(1).unwrap(), "bar"); - /// assert_eq!(s.step.value, "foo is bar"); + /// assert_eq!(matches[0], "foo"); + /// assert_eq!(matches[1], "bar"); + /// assert_eq!(s.value, "foo is not bar"); /// } /// # /// # #[tokio::main] /// # async fn main() { - /// # let runner = MyWorld::init(&["./features"]); - /// # runner.run().await; + /// # MyWorld::run("./tests/features/doctests.feature").await; /// # } /// ``` /// /// [`FromStr`]: std::str::FromStr - /// [`cucumber::StepContext`]: cucumber_rust::StepContext - /// [`World`]: cucumber_rust::World + /// [`gherkin::Step`]: https://bit.ly/3j42hcd + /// [`World`]: https://bit.ly/3j0aWw7 #[proc_macro_attribute] pub fn $name(args: TokenStream, input: TokenStream) -> TokenStream { attribute::step(std::stringify!($name), args.into(), input.into()) @@ -136,7 +136,8 @@ macro_rules! steps { ($($name:ident),*) => { /// Derive macro for tests auto-wiring. /// - /// See [`given`], [`when`] and [`then`] attributes for further details. + /// See [`macro@given`], [`macro@when`] and [`macro@then`] attributes + /// for further details. #[proc_macro_derive(WorldInit)] pub fn derive_init(input: TokenStream) -> TokenStream { derive::world_init(input.into(), &[$(std::stringify!($name)),*]) diff --git a/codegen/tests/example.rs b/codegen/tests/example.rs index ff0be553..029bce66 100644 --- a/codegen/tests/example.rs +++ b/codegen/tests/example.rs @@ -1,12 +1,12 @@ use std::{convert::Infallible, time::Duration}; use async_trait::async_trait; -use cucumber_rust::{given, StepContext, World, WorldInit}; +use cucumber_rust::{gherkin::Step, given, when, World, WorldInit}; use tokio::time; -#[derive(WorldInit)] +#[derive(Debug, WorldInit)] pub struct MyWorld { - pub foo: i32, + foo: i32, } #[async_trait(?Send)] @@ -18,38 +18,51 @@ impl World for MyWorld { } } +#[given("non-regex")] +fn test_non_regex_sync(w: &mut MyWorld) { + w.foo += 1; +} + +#[given("non-regex async")] +async fn test_non_regex_async(w: &mut MyWorld, #[step] ctx: &Step) { + time::sleep(Duration::new(1, 0)).await; + + assert_eq!(ctx.value, "non-regex async"); + + w.foo += 1; +} + #[given(regex = r"(\S+) is (\d+)")] +#[when(regex = r"(\S+) is (\d+)")] async fn test_regex_async( w: &mut MyWorld, step: String, - #[given(context)] ctx: &StepContext, + #[step] ctx: &Step, num: usize, ) { time::sleep(Duration::new(1, 0)).await; assert_eq!(step, "foo"); assert_eq!(num, 0); - assert_eq!(ctx.step.value, "foo is 0"); + assert_eq!(ctx.value, "foo is 0"); w.foo += 1; } #[given(regex = r"(\S+) is sync (\d+)")] -async fn test_regex_sync( - w: &mut MyWorld, - s: String, - #[given(context)] ctx: &StepContext, - num: usize, -) { - assert_eq!(s, "foo"); - assert_eq!(num, 0); - assert_eq!(ctx.step.value, "foo is sync 0"); +fn test_regex_sync_slice(w: &mut MyWorld, step: &Step, matches: &[String]) { + assert_eq!(matches[0], "foo"); + assert_eq!(matches[1].parse::().unwrap(), 0); + assert_eq!(step.value, "foo is sync 0"); w.foo += 1; } #[tokio::main] async fn main() { - let runner = MyWorld::init(&["./tests/features"]); - runner.run().await; + MyWorld::cucumber() + .max_concurrent_scenarios(None) + .fail_on_skipped() + .run_and_exit("./tests/features") + .await; } diff --git a/codegen/features/doctests.feature b/codegen/tests/features/doctests.feature similarity index 68% rename from codegen/features/doctests.feature rename to codegen/tests/features/doctests.feature index 65ab0f13..b7db28dd 100644 --- a/codegen/features/doctests.feature +++ b/codegen/tests/features/doctests.feature @@ -1,7 +1,8 @@ Feature: Doctests Scenario: Foo - Given foo is 10 + Given foo is 0 + @allow_skipped Scenario: Bar Given foo is not bar diff --git a/codegen/tests/features/example.feature b/codegen/tests/features/example.feature index 4dcb453b..04506169 100644 --- a/codegen/tests/features/example.feature +++ b/codegen/tests/features/example.feature @@ -2,6 +2,7 @@ Feature: Example feature Scenario: An example scenario Given foo is 0 + When foo is 0 Scenario: An example sync scenario Given foo is sync 0 diff --git a/codegen/tests/readme.rs b/codegen/tests/readme.rs deleted file mode 100644 index 2dc6f34d..00000000 --- a/codegen/tests/readme.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::{cell::RefCell, convert::Infallible}; - -use async_trait::async_trait; -use cucumber_rust::{given, then, when, World, WorldInit}; - -#[derive(WorldInit)] -pub struct MyWorld { - // You can use this struct for mutable context in scenarios. - foo: String, - bar: usize, - some_value: RefCell, -} - -impl MyWorld { - async fn test_async_fn(&mut self) { - *self.some_value.borrow_mut() = 123u8; - self.bar = 123; - } -} - -#[async_trait(?Send)] -impl World for MyWorld { - type Error = Infallible; - - async fn new() -> Result { - Ok(Self { - foo: "wat".into(), - bar: 0, - some_value: RefCell::new(0), - }) - } -} - -#[given("a thing")] -async fn a_thing(world: &mut MyWorld) { - world.foo = "elho".into(); - world.test_async_fn().await; -} - -#[when(regex = "something goes (.*)")] -async fn something_goes(_: &mut MyWorld, _wrong: String) {} - -#[given("I am trying out Cucumber")] -fn i_am_trying_out(world: &mut MyWorld) { - world.foo = "Some string".to_string(); -} - -#[when("I consider what I am doing")] -fn i_consider(world: &mut MyWorld) { - let new_string = format!("{}.", &world.foo); - world.foo = new_string; -} - -#[then("I am interested in ATDD")] -fn i_am_interested(world: &mut MyWorld) { - assert_eq!(world.foo, "Some string."); -} - -#[then(regex = r"^we can (.*) rules with regex$")] -fn we_can_regex(_: &mut MyWorld, action: String) { - // `action` can be anything implementing `FromStr`. - assert_eq!(action, "implement"); -} - -fn main() { - let runner = MyWorld::init(&["./features"]); - futures::executor::block_on(runner.run()); -} diff --git a/features/basic/test.feature b/features/basic/test.feature deleted file mode 100644 index 18fe0fc9..00000000 --- a/features/basic/test.feature +++ /dev/null @@ -1,26 +0,0 @@ -Feature: Basic functionality - # Comment - @foo - Scenario: foo - Given a thing - When nothing - - Scenario: bar - # comment - Given a thing - When something goes wrong - - Scenario Outline: scenario with examples - Given a number - Then twice that number should be - - Examples: - | num | double | - | 2 | 4 | - | 3 | 6 | - - Rule: A rule - - Scenario: a scenario inside a rule - Given I am in inside a rule - Then things are working diff --git a/features/capture/enable_capture.feature b/features/capture/enable_capture.feature deleted file mode 100644 index 6c3f53e4..00000000 --- a/features/capture/enable_capture.feature +++ /dev/null @@ -1,5 +0,0 @@ -Feature: The enable_capture flag controls whether stdout/stderr printing is captured or not - Scenario: In which some printing is attempted - When we print "everything is great" to stdout - And we print "something went wrong" to stderr - Then it is up to the cucumber configuration to decide whether the content gets printed diff --git a/features/integration/step_variety.feature b/features/integration/step_variety.feature deleted file mode 100644 index bc58e4d8..00000000 --- a/features/integration/step_variety.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Variety of scenario outcomes get exposed for integration - Scenario: A successful scenario - When something - Then it's okay - - Scenario: A failing scenario - When another thing - Then it's not okay - - Scenario: A scenario with an unimplemented step - When not implemented - Then it's okay - - Scenario: A timing out scenario - When something - Then it takes a long time diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index d2bacbec..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,44 +0,0 @@ -use clap::{App, Arg}; - -#[derive(Default)] -pub struct CliOptions { - pub scenario_filter: Option, - pub nocapture: bool, - pub debug: bool, -} - -pub fn make_app() -> CliOptions { - let matches = App::new("cucumber") - .version(env!("CARGO_PKG_VERSION")) - .author("Brendan Molloy ") - .about("Run the tests, pet a dog!") - .arg( - Arg::with_name("filter") - .short("e") - .long("expression") - .value_name("regex") - .help("Regex to select scenarios from") - .takes_value(true), - ) - .arg( - Arg::with_name("nocapture") - .long("nocapture") - .help("Use this flag to disable suppression of output from tests"), - ) - .arg( - Arg::with_name("debug") - .long("debug") - .help("Enable verbose test logging (debug mode)"), - ) - .get_matches(); - - let nocapture = matches.is_present("nocapture"); - let scenario_filter = matches.value_of("filter").map(|v| v.to_string()); - let debug = matches.is_present("debug"); - - CliOptions { - nocapture, - scenario_filter, - debug, - } -} diff --git a/src/codegen.rs b/src/codegen.rs new file mode 100644 index 00000000..417e015b --- /dev/null +++ b/src/codegen.rs @@ -0,0 +1,197 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Helper type-level glue for [`cucumber_rust_codegen`] crate. + +use std::{fmt::Debug, path::Path}; + +use async_trait::async_trait; + +use crate::{cucumber::DefaultCucumber, step, Cucumber, Step, World}; + +pub use futures::future::LocalBoxFuture; +pub use inventory::{self, collect, submit}; +pub use regex::Regex; + +/// [`World`] extension with auto-wiring capabilities. +#[async_trait(?Send)] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub trait WorldInit: WorldInventory +where + Self: Debug, + G: StepConstructor + inventory::Collect, + W: StepConstructor + inventory::Collect, + T: StepConstructor + inventory::Collect, +{ + /// Returns runner for tests with auto-wired steps marked by [`given`], + /// [`when`] and [`then`] attributes. + /// + /// [`given`]: crate::given + /// [`then`]: crate::then + /// [`when`]: crate::when + #[must_use] + fn collection() -> step::Collection { + let mut out = step::Collection::new(); + + for given in Self::cucumber_given() { + let (regex, fun) = given.inner(); + out = out.given(regex, fun); + } + + for when in Self::cucumber_when() { + let (regex, fun) = when.inner(); + out = out.when(regex, fun); + } + + for then in Self::cucumber_then() { + let (regex, fun) = then.inner(); + out = out.then(regex, fun); + } + + out + } + + /// Returns default [`Cucumber`] with all auto-wired [`Step`]s. + #[must_use] + fn cucumber>() -> DefaultCucumber { + Cucumber::new().steps(Self::collection()) + } + + /// Runs [`Cucumber`]. + /// + /// [`Feature`]s sourced by [`Parser`] are fed into [`Runner`] where the + /// later produces events handled by [`Writer`]. + /// + /// # Panics + /// + /// If encountered errors while parsing [`Feature`]s or at least one + /// [`Step`] panicked. + /// + /// [`Feature`]: gherkin::Feature + /// [`Parser`]: crate::Parser + /// [`Runner`]: crate::Runner + /// [`Step`]: crate::Step + /// [`Writer`]: crate::Writer + async fn run>(input: I) { + Self::cucumber().run_and_exit(input).await; + } + + /// Runs [`Cucumber`] with [`Scenario`]s filter. + /// + /// [`Feature`]s sourced by [`Parser`] are fed into [`Runner`] where the + /// later produces events handled by [`Writer`]. + /// + /// # Panics + /// + /// If encountered errors while parsing [`Feature`]s or at least one + /// [`Step`] panicked. + /// + /// [`Feature`]: gherkin::Feature + /// [`Parser`]: crate::Parser + /// [`Runner`]: crate::Runner + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + /// [`Writer`]: crate::Writer + async fn filter_run(input: I, filter: F) + where + I: AsRef, + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool + + 'static, + { + Self::cucumber().filter_run_and_exit(input, filter).await; + } +} + +impl WorldInit for E +where + Self: Debug, + G: StepConstructor + inventory::Collect, + W: StepConstructor + inventory::Collect, + T: StepConstructor + inventory::Collect, + E: WorldInventory, +{ +} + +/// [`World`] extension allowing to register steps in [`inventory`]. +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub trait WorldInventory: World +where + G: StepConstructor + inventory::Collect, + W: StepConstructor + inventory::Collect, + T: StepConstructor + inventory::Collect, +{ + /// Returns an [`Iterator`] over items with [`given`] attribute. + /// + /// [`given`]: crate::given + #[must_use] + fn cucumber_given() -> inventory::iter { + inventory::iter + } + + /// Creates a new [`Given`] [`Step`] value. Used by [`given`] attribute. + /// + /// [`given`]: crate::given + /// [Given]: https://cucumber.io/docs/gherkin/reference/#given + fn new_given(regex: Regex, fun: Step) -> G { + G::new(regex, fun) + } + + /// Returns an [`Iterator`] over items with [`when`] attribute. + /// + /// [`when`]: crate::when + #[must_use] + fn cucumber_when() -> inventory::iter { + inventory::iter + } + + /// Creates a new [`When`] [`Step`] value. Used by [`when`] attribute. + /// + /// [`when`]: crate::when + /// [When]: https://cucumber.io/docs/gherkin/reference/#when + fn new_when(regex: Regex, fun: Step) -> W { + W::new(regex, fun) + } + + /// Returns an [`Iterator`] over items with [`then`] attribute. + /// + /// [`then`]: crate::then + #[must_use] + fn cucumber_then() -> inventory::iter { + inventory::iter + } + + /// Creates a new [`Then`] [`Step`] value. Used by [`then`] attribute. + /// + /// [`then`]: crate::then + /// [Then]: https://cucumber.io/docs/gherkin/reference/#then + fn new_then(regex: Regex, fun: Step) -> T { + T::new(regex, fun) + } +} + +/// Trait for creating [`Step`]s to be registered by [`given`], [`when`] and +/// [`then`] attributes. +/// +/// [`given`]: crate::given +/// [`when`]: crate::when +/// [`then`]: crate::then +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub trait StepConstructor { + /// Creates a new [`Step`] with the corresponding [`Regex`]. + #[must_use] + fn new(_: Regex, _: Step) -> Self; + + /// Returns an inner [`Step`] with the corresponding [`Regex`]. + fn inner(&self) -> (Regex, Step); +} diff --git a/src/collection.rs b/src/collection.rs deleted file mode 100644 index 0920c09e..00000000 --- a/src/collection.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2018-2021 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::collections::BTreeMap; - -use cute_custom_default::CustomDefault; -use regex::Regex; - -use crate::regex::HashableRegex; -use crate::runner::{StepFn, TestFunction}; -use crate::World; -use gherkin::{Step, StepType}; - -#[derive(CustomDefault)] -struct StepMaps { - #[def_exp = "BTreeMap::new()"] - basic: BTreeMap<&'static str, StepFn>, - #[def_exp = "BTreeMap::new()"] - regex: BTreeMap>, -} - -#[derive(CustomDefault)] -pub(crate) struct StepsCollection { - given: StepMaps, - when: StepMaps, - then: StepMaps, -} - -impl StepsCollection { - pub(crate) fn append(&mut self, mut other: StepsCollection) { - self.given.basic.append(&mut other.given.basic); - self.when.basic.append(&mut other.when.basic); - self.then.basic.append(&mut other.then.basic); - self.given.regex.append(&mut other.given.regex); - self.when.regex.append(&mut other.when.regex); - self.then.regex.append(&mut other.then.regex); - } - - pub(crate) fn insert_basic(&mut self, ty: StepType, name: &'static str, callback: StepFn) { - match ty { - StepType::Given => self.given.basic.insert(name, callback), - StepType::When => self.when.basic.insert(name, callback), - StepType::Then => self.then.basic.insert(name, callback), - }; - } - - pub(crate) fn insert_regex(&mut self, ty: StepType, regex: Regex, callback: StepFn) { - let name = HashableRegex(regex); - - match ty { - StepType::Given => self.given.regex.insert(name, callback), - StepType::When => self.when.regex.insert(name, callback), - StepType::Then => self.then.regex.insert(name, callback), - }; - } - - pub(crate) fn resolve(&self, step: &Step) -> Option> { - // Attempt to find literal variant of steps first - let test_fn = match step.ty { - StepType::Given => self.given.basic.get(&*step.value), - StepType::When => self.when.basic.get(&*step.value), - StepType::Then => self.then.basic.get(&*step.value), - }; - - if let Some(function) = test_fn { - return Some(TestFunction::from(function)); - } - - #[allow(clippy::mutable_key_type)] - let regex_map = match step.ty { - StepType::Given => &self.given.regex, - StepType::When => &self.when.regex, - StepType::Then => &self.then.regex, - }; - - // Then attempt to find a regex variant of that test - if let Some((regex, function)) = regex_map - .iter() - .find(|(regex, _)| regex.is_match(&step.value)) - { - let matches = regex - .0 - .captures(&step.value) - .unwrap() - .iter() - .map(|match_| { - match_ - .map(|match_| match_.as_str().to_owned()) - .unwrap_or_default() - }) - .collect(); - - return Some(match *function { - StepFn::Sync(x) => TestFunction::RegexSync(x, matches), - StepFn::Async(x) => TestFunction::RegexAsync(x, matches), - }); - } - - None - } -} diff --git a/src/criteria.rs b/src/criteria.rs deleted file mode 100644 index 365f0667..00000000 --- a/src/criteria.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::ops::{BitAnd, BitOr}; - -use gherkin::{Feature, Rule, Scenario}; - -#[non_exhaustive] -#[derive(Debug, Clone)] -pub enum Pattern { - Regex(regex::Regex), - Literal(String), -} - -impl From for Pattern { - fn from(x: String) -> Self { - Pattern::Literal(x) - } -} -impl From<&str> for Pattern { - fn from(x: &str) -> Self { - Pattern::Literal(x.to_string()) - } -} - -impl From for Pattern { - fn from(x: regex::Regex) -> Self { - Pattern::Regex(x) - } -} - -impl Pattern { - fn eval(&self, input: &str) -> bool { - match self { - Pattern::Regex(regex) => regex.is_match(input), - Pattern::Literal(literal) => literal == input, - } - } -} - -#[derive(Debug, Clone)] -pub enum Criteria { - And(Box, Box), - Or(Box, Box), - Scenario(Pattern), - Rule(Pattern), - Feature(Pattern), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum Context { - Scenario, - Rule, - Feature, -} - -impl Context { - pub fn is_scenario(&self) -> bool { - match self { - Context::Scenario => true, - _ => false, - } - } - - pub fn is_rule(&self) -> bool { - match self { - Context::Rule => true, - _ => false, - } - } - - pub fn is_feature(&self) -> bool { - match self { - Context::Feature => true, - _ => false, - } - } -} - -impl Criteria { - pub(crate) fn context(&self) -> Context { - match self { - Criteria::And(a, b) => std::cmp::min(a.context(), b.context()), - Criteria::Or(a, b) => std::cmp::min(a.context(), b.context()), - Criteria::Scenario(_) => Context::Scenario, - Criteria::Rule(_) => Context::Rule, - Criteria::Feature(_) => Context::Feature, - } - } - - pub(crate) fn eval( - &self, - feature: &Feature, - rule: Option<&Rule>, - scenario: Option<&Scenario>, - ) -> bool { - match self { - Criteria::And(a, b) => { - a.eval(feature, rule, scenario) && b.eval(feature, rule, scenario) - } - Criteria::Or(a, b) => { - a.eval(feature, rule, scenario) || b.eval(feature, rule, scenario) - } - Criteria::Scenario(pattern) if scenario.is_some() => { - pattern.eval(&scenario.unwrap().name) - } - Criteria::Rule(pattern) if rule.is_some() => pattern.eval(&rule.unwrap().name), - Criteria::Feature(pattern) => pattern.eval(&feature.name), - _ => false, - } - } -} - -impl BitAnd for Criteria { - type Output = Criteria; - - fn bitand(self, rhs: Self) -> Self::Output { - Criteria::And(Box::new(self), Box::new(rhs)) - } -} - -impl BitOr for Criteria { - type Output = Criteria; - - fn bitor(self, rhs: Self) -> Self::Output { - Criteria::Or(Box::new(self), Box::new(rhs)) - } -} - -pub fn scenario>(pattern: P) -> Criteria { - Criteria::Scenario(pattern.into()) -} - -pub fn rule>(pattern: P) -> Criteria { - Criteria::Rule(pattern.into()) -} - -pub fn feature>(pattern: P) -> Criteria { - Criteria::Feature(pattern.into()) -} diff --git a/src/cucumber.rs b/src/cucumber.rs index 9d4e06f8..a05e780c 100644 --- a/src/cucumber.rs +++ b/src/cucumber.rs @@ -1,4 +1,6 @@ -// Copyright (c) 2018-2021 Brendan Molloy +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -6,343 +8,504 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Top-level [Cucumber] executor. +//! +//! [Cucumber]: https://cucumber.io + use std::{ - any::{Any, TypeId}, - collections::HashMap, + borrow::Cow, + fmt::{Debug, Formatter}, + marker::PhantomData, + mem, path::Path, - rc::Rc, }; -use std::{pin::Pin, time::Duration}; -use futures::{Future, StreamExt}; -use gherkin::ParseFileError; +use futures::StreamExt as _; use regex::Regex; -use crate::{criteria::Criteria, steps::Steps}; -use crate::{EventHandler, World}; - -pub(crate) type LifecycleFuture = Pin>>; - -#[derive(Clone)] -pub struct LifecycleContext { - pub(crate) context: Rc, - pub feature: Rc, - pub rule: Option>, - pub scenario: Option>, -} - -impl LifecycleContext { - #[inline] - pub fn get(&self) -> Option<&T> { - self.context.get() - } -} - -pub type LifecycleFn = fn(LifecycleContext) -> LifecycleFuture; - -pub struct Cucumber { - context: Context, - - steps: Steps, - features: Vec, - event_handler: Box, - - /// If `Some`, enforce an upper bound on the amount - /// of time a step is allowed to execute. - /// If `Some`, also avoid indefinite locks during - /// step clean-up handling (i.e. to recover panic info) - step_timeout: Option, - - /// If true, capture stdout and stderr content - /// during tests. - enable_capture: bool, - - /// If given, filters the scenario which are run - scenario_filter: Option, - - language: Option, - - debug: bool, - - before: Vec<(Criteria, LifecycleFn)>, - - after: Vec<(Criteria, LifecycleFn)>, -} +use crate::{ + parser, runner, step, writer, ArbitraryWriter, FailureWriter, Parser, + Runner, ScenarioType, Step, World, Writer, WriterExt as _, +}; -pub struct StepContext { - context: Rc, - pub step: Rc, - pub matches: Vec, +/// Top-level [Cucumber] executor. +/// +/// Most of the time you don't need to work with it directly, just use +/// [`WorldInit::run()`] or [`WorldInit::cucumber()`] on your [`World`] deriver +/// to get [Cucumber] up and running. +/// +/// Otherwise use [`Cucumber::new()`] to get the default [Cucumber] executor, +/// provide [`Step`]s with [`WorldInit::collection()`] or by hand with +/// [`Cucumber::given()`], [`Cucumber::when()`] and [`Cucumber::then()`]. +/// +/// In case you want custom [`Parser`], [`Runner`] or [`Writer`] or +/// some other finer control, use [`Cucumber::custom()`] with +/// [`Cucumber::with_parser()`], [`Cucumber::with_runner()`] and +/// [`Cucumber::with_writer()`] to construct your dream [Cucumber] executor! +/// +/// [Cucumber]: https://cucumber.io +/// [`WorldInit::collection()`]: crate::WorldInit::collection() +/// [`WorldInit::cucumber()`]: crate::WorldInit::cucumber() +/// [`WorldInit::run()`]: crate::WorldInit::run() +pub struct Cucumber { + parser: P, + runner: R, + writer: Wr, + _world: PhantomData, + _parser_input: PhantomData, } -impl StepContext { - #[inline] - pub(crate) fn new(context: Rc, step: Rc, matches: Vec) -> Self { +impl Cucumber { + /// Creates an empty [`Cucumber`] executor. + /// + /// Use [`Cucumber::with_parser()`], [`Cucumber::with_runner()`] and + /// [`Cucumber::with_writer()`] to be able to [`Cucumber::run()`] it. + #[must_use] + pub fn custom() -> Self { Self { - context, - step, - matches, + parser: (), + runner: (), + writer: (), + _world: PhantomData, + _parser_input: PhantomData, } } - - #[inline] - pub fn get(&self) -> Option<&T> { - self.context.get() - } } -#[derive(Default)] -pub struct Context { - data: HashMap>, -} - -impl Context { - pub fn new() -> Self { - Default::default() - } - - pub fn get(&self) -> Option<&T> { - self.data - .get(&TypeId::of::()) - .and_then(|x| x.downcast_ref::()) - } - - pub fn insert(&mut self, value: T) { - self.data.insert(TypeId::of::(), Box::new(value)); +impl Cucumber { + /// Replaces [`Parser`]. + #[must_use] + pub fn with_parser( + self, + parser: NewP, + ) -> Cucumber + where + NewP: Parser, + { + let Self { runner, writer, .. } = self; + Cucumber { + parser, + runner, + writer, + _world: PhantomData, + _parser_input: PhantomData, + } } - pub fn add(mut self, value: T) -> Self { - self.insert(value); - self + /// Replaces [`Runner`]. + #[must_use] + pub fn with_runner(self, runner: NewR) -> Cucumber + where + NewR: Runner, + { + let Self { parser, writer, .. } = self; + Cucumber { + parser, + runner, + writer, + _world: PhantomData, + _parser_input: PhantomData, + } } -} -impl Default for Cucumber { - fn default() -> Self { + /// Replaces [`Writer`]. + #[must_use] + pub fn with_writer( + self, + writer: NewWr, + ) -> Cucumber + where + NewWr: Writer, + { + let Self { parser, runner, .. } = self; Cucumber { - context: Default::default(), - steps: Default::default(), - features: Default::default(), - event_handler: Box::new(crate::output::BasicOutput::new(false)), - step_timeout: None, - enable_capture: true, - debug: false, - scenario_filter: None, - language: None, - before: vec![], - after: vec![], + parser, + runner, + writer, + _world: PhantomData, + _parser_input: PhantomData, } } } -impl Cucumber { - /// Construct a default `Cucumber` instance. +impl Cucumber +where + W: World, + Wr: Writer, +{ + /// Consider [`Skipped`] steps as [`Failed`] if their [`Scenario`] isn't + /// marked with `@allow_skipped` tag. + /// + /// It's useful option for ensuring that all the steps were covered. /// - /// Comes with the default `EventHandler` implementation responsible for - /// printing test execution progress. - pub fn new() -> Cucumber { - Default::default() + /// [`Failed`]: crate::event::Step::Failed + /// [`Scenario`]: gherkin::Scenario + /// [`Skipped`]: crate::event::Step::Skipped + #[must_use] + pub fn fail_on_skipped( + self, + ) -> Cucumber> { + Cucumber { + parser: self.parser, + runner: self.runner, + writer: self.writer.fail_on_skipped(), + _world: PhantomData, + _parser_input: PhantomData, + } } - /// Construct a `Cucumber` instance with a custom `EventHandler`. - pub fn with_handler(event_handler: O) -> Self { + /// Consider [`Skipped`] steps as [`Failed`] if the given `filter` predicate + /// returns `true`. + /// + /// [`Failed`]: crate::event::Step::Failed + /// [`Scenario`]: gherkin::Scenario + /// [`Skipped`]: crate::event::Step::Skipped + #[must_use] + pub fn fail_on_skipped_with( + self, + filter: Filter, + ) -> Cucumber> + where + Filter: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool, + { Cucumber { - context: Default::default(), - steps: Default::default(), - features: Default::default(), - event_handler: Box::new(event_handler), - step_timeout: None, - enable_capture: true, - debug: false, - scenario_filter: None, - language: None, - before: vec![], - after: vec![], + parser: self.parser, + runner: self.runner, + writer: self.writer.fail_on_skipped_with(filter), + _world: PhantomData, + _parser_input: PhantomData, } } +} - /// Add some steps to the Cucumber instance. +impl Cucumber +where + W: World, + P: Parser, + R: Runner, + Wr: Writer, +{ + /// Runs [`Cucumber`]. /// - /// Does *not* replace any previously added steps. - pub fn steps(mut self, steps: Steps) -> Self { - self.steps.append(steps); - self + /// [`Feature`]s sourced by [`Parser`] are fed to [`Runner`], which produces + /// events handled by [`Writer`]. + /// + /// [`Feature`]: gherkin::Feature + pub async fn run(self, input: I) -> Wr { + let Cucumber { + parser, + runner, + mut writer, + .. + } = self; + + let events_stream = runner.run(parser.parse(input)); + futures::pin_mut!(events_stream); + while let Some(ev) = events_stream.next().await { + writer.handle_event(ev).await; + } + writer } - /// A collection of directory paths that will be walked to - /// find ".feature" files. + /// Runs [`Cucumber`] with [`Scenario`]s filter. /// - /// Removes any previously-supplied features. - pub fn features>(mut self, feature_paths: impl IntoIterator) -> Self { - let mut features = feature_paths - .into_iter() - .map(|path| match path.as_ref().canonicalize() { - Ok(v) => v, - Err(e) => { - eprintln!("{}", e); - eprintln!("There was an error parsing {:?}; aborting.", path.as_ref()); - std::process::exit(1); - } - }) - .map(|path| { - let env = match self.language.as_ref() { - Some(lang) => gherkin::GherkinEnv::new(lang).unwrap(), - None => Default::default(), - }; - - if path.is_file() { - vec![gherkin::Feature::parse_path(&path, env)] - } else { - let walker = globwalk::GlobWalkerBuilder::new(path, "*.feature") - .case_insensitive(true) - .build() - .expect("feature path is invalid"); - walker - .filter_map(Result::ok) - .map(|entry| { - let env = match self.language.as_ref() { - Some(lang) => gherkin::GherkinEnv::new(lang).unwrap(), - None => Default::default(), - }; - gherkin::Feature::parse_path(entry.path(), env) - }) - .collect::>() - } - }) - .flatten() - .collect::, _>>() - .unwrap_or_else(|e| match e { - ParseFileError::Reading { path, source } => { - eprintln!("Error reading '{}':", path.display()); - eprintln!("{:?}", source); - std::process::exit(1); - } - ParseFileError::Parsing { - path, - error, - source, - } => { - eprintln!("Error parsing '{}':", path.display()); - if let Some(error) = error { - eprintln!("{}", error); - } - eprintln!("{:?}", source); - std::process::exit(1); - } - }); - - features.sort(); - - self.features = features; - self - } + /// [`Feature`]s sourced [`Parser`] are fed to [`Runner`], which produces + /// events handled by [`Writer`]. + /// + /// [`Feature`]: gherkin::Feature + /// [`Scenario`]: gherkin::Scenario + pub async fn filter_run(self, input: I, filter: F) -> Wr + where + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool + + 'static, + { + let Cucumber { + parser, + runner, + mut writer, + .. + } = self; + + let features = parser.parse(input); + + let filtered = features.map(move |feature| { + let mut feature = feature?; + let scenarios = mem::take(&mut feature.scenarios); + feature.scenarios = scenarios + .into_iter() + .filter(|s| filter(&feature, None, s)) + .collect(); + + let mut rules = mem::take(&mut feature.rules); + for r in &mut rules { + let scenarios = mem::take(&mut r.scenarios); + r.scenarios = scenarios + .into_iter() + .filter(|s| filter(&feature, Some(r), s)) + .collect(); + } + feature.rules = rules; - /// If `Some`, enforce an upper bound on the amount - /// of time a step is allowed to execute. - /// If `Some`, also avoid indefinite locks during - /// step clean-up handling (i.e. to recover panic info) - pub fn step_timeout(mut self, step_timeout: Duration) -> Self { - self.step_timeout = Some(step_timeout); - self - } + Ok(feature) + }); - /// If true, capture stdout and stderr content - /// during tests. - pub fn enable_capture(mut self, enable_capture: bool) -> Self { - self.enable_capture = enable_capture; - self + let events_stream = runner.run(filtered); + futures::pin_mut!(events_stream); + while let Some(ev) = events_stream.next().await { + writer.handle_event(ev).await; + } + writer } +} - pub fn scenario_regex(mut self, regex: &str) -> Self { - let regex = Regex::new(regex).expect("Error compiling scenario regex"); - self.scenario_filter = Some(regex); - self +impl Debug for Cucumber +where + P: Debug, + R: Debug, + Wr: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cucumber") + .field("parser", &self.parser) + .field("runner", &self.runner) + .field("writer", &self.writer) + .finish() } +} - /// Call this to incorporate command line options into the configuration. - pub fn cli(self) -> Self { - let opts = crate::cli::make_app(); - let mut s = self; - - if let Some(re) = opts.scenario_filter { - s = s.scenario_regex(&re); - } +/// Shortcut for the [`Cucumber`] type returned by its [`Default`] impl. +pub(crate) type DefaultCucumber = Cucumber< + W, + parser::Basic, + I, + runner::Basic, + writer::Summarized>, +>; + +impl Default for DefaultCucumber +where + W: World + Debug, + I: AsRef, +{ + fn default() -> Self { + let which: runner::basic::WhichScenarioFn = |_, _, scenario| { + scenario + .tags + .iter() + .any(|tag| tag == "serial") + .then(|| ScenarioType::Serial) + .unwrap_or(ScenarioType::Concurrent) + }; + + Cucumber::custom() + .with_parser(parser::Basic::new()) + .with_runner( + runner::Basic::custom() + .which_scenario(which) + .max_concurrent_scenarios(64), + ) + .with_writer(writer::Basic::new().normalized().summarized()) + } +} - if opts.nocapture { - s = s.enable_capture(false); - } +impl DefaultCucumber +where + W: World + Debug, + I: AsRef, +{ + /// Creates a default [`Cucumber`] executor. + /// + /// * [`Parser`] — [`parser::Basic`] + /// + /// * [`Runner`] — [`runner::Basic`] + /// * [`ScenarioType`] — [`Concurrent`] by default, [`Serial`] if + /// `@serial` [tag] is present on a [`Scenario`]; + /// * Allowed to run up to 64 [`Concurrent`] [`Scenario`]s. + /// + /// * [`Writer`] — [`Normalized`] and [`Summarized`] [`writer::Basic`]. + /// + /// [`Concurrent`]: runner::basic::ScenarioType::Concurrent + /// [`Normalized`]: writer::Normalized + /// [`Parser`]: parser::Parser + /// [`Scenario`]: gherkin::Scenario + /// [`Serial`]: runner::basic::ScenarioType::Serial + /// [`ScenarioType`]: runner::basic::ScenarioType + /// [`Summarized`]: writer::Summarized + /// + /// [tag]: https://cucumber.io/docs/cucumber/api/#tags + #[must_use] + pub fn new() -> Self { + Cucumber::default() + } +} - if opts.debug { - s = s.debug(true); - } +impl Cucumber { + /// Sets the provided language of [`gherkin`] files. + /// + /// # Errors + /// + /// If the provided language isn't supported. + pub fn language( + mut self, + name: impl Into>, + ) -> Result { + self.parser = self.parser.language(name)?; + Ok(self) + } +} - s +impl Cucumber, Wr> { + /// If `max` is [`Some`] number of concurrently executed [`Scenario`]s will + /// be limited. + /// + /// [`Scenario`]: gherkin::Scenario + #[must_use] + pub fn max_concurrent_scenarios( + mut self, + max: impl Into>, + ) -> Self { + self.runner = self.runner.max_concurrent_scenarios(max); + self } - /// Set the default language to assume for each .feature file. - pub fn language(mut self, language: &str) -> Self { - if gherkin::is_language_supported(language) { - self.language = Some(language.to_string()); - } else { - eprintln!( - "ERROR: Provided language '{}' not supported; ignoring.", - language - ); + /// Function determining whether a [`Scenario`] is [`Concurrent`] or + /// a [`Serial`] one. + /// + /// [`Concurrent`]: ScenarioType::Concurrent + /// [`Serial`]: ScenarioType::Serial + /// [`Scenario`]: gherkin::Scenario + #[must_use] + pub fn which_scenario( + self, + func: Which, + ) -> Cucumber, Wr> + where + Which: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> ScenarioType + + 'static, + { + let Self { + parser, + runner, + writer, + .. + } = self; + Cucumber { + parser, + runner: runner.which_scenario(func), + writer, + _world: PhantomData, + _parser_input: PhantomData, } - - self } - pub fn before(mut self, criteria: Criteria, handler: LifecycleFn) -> Self { - self.before.push((criteria, handler)); + /// Replaces [`Collection`] of [`Step`]s. + /// + /// [`Collection`]: step::Collection + /// [`Step`]: step::Step + #[must_use] + pub fn steps(mut self, steps: step::Collection) -> Self { + self.runner = self.runner.steps(steps); self } - pub fn after(mut self, criteria: Criteria, handler: LifecycleFn) -> Self { - self.after.push((criteria, handler)); + /// Inserts [Given] [`Step`]. + /// + /// [Given]: https://cucumber.io/docs/gherkin/reference/#given + #[must_use] + pub fn given(mut self, regex: Regex, step: Step) -> Self { + self.runner = self.runner.given(regex, step); self } - /// Enable printing stdout and stderr for every step, regardless of error state. - pub fn debug(mut self, value: bool) -> Self { - self.event_handler = Box::new(crate::output::BasicOutput::new(value)); - self.debug = value; + /// Inserts [When] [`Step`]. + /// + /// [When]: https://cucumber.io/docs/gherkin/reference/#When + #[must_use] + pub fn when(mut self, regex: Regex, step: Step) -> Self { + self.runner = self.runner.when(regex, step); self } - pub fn context(mut self, context: Context) -> Self { - self.context = context; + /// Inserts [Then] [`Step`]. + /// + /// [Then]: https://cucumber.io/docs/gherkin/reference/#then + #[must_use] + pub fn then(mut self, regex: Regex, step: Step) -> Self { + self.runner = self.runner.then(regex, step); self } +} - /// Run and report number of errors if any - pub async fn run(mut self) -> crate::runner::RunResult { - let runner = crate::runner::Runner::new( - Rc::new(self.context), - self.steps.steps, - Rc::new(self.features), - self.step_timeout, - self.enable_capture, - self.scenario_filter, - self.before, - self.after, - ); - let mut stream = runner.run(); - - while let Some(event) = stream.next().await { - self.event_handler.handle_event(&event); - - if let crate::event::CucumberEvent::Finished(result) = event { - return result; - } - } - - unreachable!("CucumberEvent::Finished must be fired") +impl Cucumber +where + W: World, + P: Parser, + R: Runner, + Wr: for<'val> ArbitraryWriter<'val, W, String> + FailureWriter, +{ + /// Runs [`Cucumber`]. + /// + /// [`Feature`]s sourced by [`Parser`] are fed to [`Runner`], which produces + /// events handled by [`Writer`]. + /// + /// # Panics + /// + /// If encountered errors while parsing [`Feature`]s or at least one + /// [`Step`] [`Failed`]. + /// + /// [`Failed`]: crate::event::Step::Failed + /// [`Feature`]: gherkin::Feature + /// [`Step`]: gherkin::Step + pub async fn run_and_exit(self, input: I) { + self.filter_run_and_exit(input, |_, _, _| true).await; } - /// Convenience function to run all tests and exit with error code 1 on failure. - pub async fn run_and_exit(self) { - let code = if self.run().await.failed() { 1 } else { 0 }; - std::process::exit(code); + /// Runs [`Cucumber`] with [`Scenario`]s filter. + /// + /// [`Feature`]s sourced by [`Parser`] are filtered, then fed to [`Runner`], + /// which produces events handled by [`Writer`]. + /// + /// # Panics + /// + /// If encountered errors while parsing [`Feature`]s or at least one + /// [`Step`] [`Failed`]. + /// + /// [`Failed`]: crate::event::Step::Failed + /// [`Feature`]: gherkin::Feature + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: crate::Step + pub async fn filter_run_and_exit(self, input: I, filter: Filter) + where + Filter: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool + + 'static, + { + let writer = self.filter_run(input, filter).await; + if writer.execution_has_failed() { + let failed_steps = writer.failed_steps(); + let parsing_errors = writer.parsing_errors(); + panic!( + "{} step{} failed, {} parsing error{}", + failed_steps, + (failed_steps != 1).then(|| "s").unwrap_or_default(), + parsing_errors, + (parsing_errors != 1).then(|| "s").unwrap_or_default(), + ); + } } } diff --git a/src/event.rs b/src/event.rs index 8af72f71..5921b156 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,139 +1,274 @@ -// Copyright (c) 2018-2020 Brendan Molloy +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Key occurrences in the lifecycle of a Cucumber execution. + +//! Key occurrences in a lifecycle of [Cucumber] execution. //! -//! The top-level enum here is `CucumberEvent`. +//! The top-level enum here is [`Cucumber`]. //! -//! Each event enum contains variants indicating -//! what stage of execution Cucumber is at and, -//! variants with detailed content about the precise -//! sub-event - -pub use super::ExampleValues; -use std::{fmt::Display, rc::Rc}; - -/// The stringified content of stdout and stderr -/// captured during Step execution. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CapturedOutput { - pub out: String, - pub err: String, -} +//! Each event enum contains variants indicating what stage of execution +//! [`Runner`] is at, and variants with detailed content about the precise +//! sub-event. +//! +//! [`Runner`]: crate::Runner +//! [Cucumber]: https://cucumber.io + +use std::{any::Any, sync::Arc}; + +/// Alias for a [`catch_unwind()`] error. +/// +/// [`catch_unwind()`]: std::panic::catch_unwind() +pub type Info = Box; + +/// Top-level [Cucumber] run event. +/// +/// [Cucumber]: https://cucumber.io +#[derive(Debug)] +pub enum Cucumber { + /// [`Cucumber`] execution being started. + Started, -/// Panic source location information -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Location { - pub file: String, - pub line: u32, - pub column: u32, + /// [`Feature`] event. + Feature(Arc, Feature), + + /// [`Cucumber`] execution being finished. + Finished, } -impl Location { - pub fn unknown() -> Self { - Location { - file: "".into(), - line: 0, - column: 0, - } +impl Cucumber { + /// Constructs an event of a [`Feature`] being started. + /// + /// [`Feature`]: gherkin::Feature + #[must_use] + pub fn feature_started(feat: Arc) -> Self { + Self::Feature(feat, Feature::Started) } -} -impl Display for Location { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "\u{00a0}{}:{}:{}\u{00a0}", - &self.file, self.line, self.column - ) + /// Constructs an event of a [`Rule`] being started. + /// + /// [`Rule`]: gherkin::Rule + #[must_use] + pub fn rule_started( + feat: Arc, + rule: Arc, + ) -> Self { + Self::Feature(feat, Feature::Rule(rule, Rule::Started)) } -} -/// Panic content captured when a Step failed. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PanicInfo { - pub location: Location, - pub payload: String, -} + /// Constructs an event of a [`Feature`] being finished. + /// + /// [`Feature`]: gherkin::Feature + #[must_use] + pub fn feature_finished(feat: Arc) -> Self { + Self::Feature(feat, Feature::Finished) + } + + /// Constructs an event of a [`Rule`] being finished. + /// + /// [`Rule`]: gherkin::Rule + #[must_use] + pub fn rule_finished( + feat: Arc, + rule: Arc, + ) -> Self { + Self::Feature(feat, Feature::Rule(rule, Rule::Finished)) + } -impl PanicInfo { - pub fn unknown() -> Self { - PanicInfo { - location: Location::unknown(), - payload: "(No panic info was found?)".into(), + /// Constructs a [`Cucumber`] event from the given [`Scenario`] event. + #[must_use] + pub fn scenario( + feat: Arc, + rule: Option>, + scenario: Arc, + event: Scenario, + ) -> Self { + #[allow(clippy::option_if_let_else)] // use of moved value: `event` + if let Some(r) = rule { + Self::Feature( + feat, + Feature::Rule(r, Rule::Scenario(scenario, event)), + ) + } else { + Self::Feature(feat, Feature::Scenario(scenario, event)) } } } -/// Outcome of step execution, carrying along the relevant -/// `World` state. -pub(crate) enum TestEvent { - Unimplemented, - Skipped, - Success(W, CapturedOutput), - Failure(StepFailureKind), -} +/// Event specific to a particular [Feature]. +/// +/// [Feature]: https://cucumber.io/docs/gherkin/reference/#feature +#[derive(Debug)] +pub enum Feature { + /// [`Feature`] execution being started. + /// + /// [`Feature`]: gherkin::Feature + Started, -/// Event specific to a particular [Step](https://cucumber.io/docs/gherkin/reference/#step) -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum StepEvent { - Starting, - Unimplemented, - Skipped, - Passed(CapturedOutput), - Failed(StepFailureKind), + /// [`Rule`] event. + Rule(Arc, Rule), + + /// [`Scenario`] event. + Scenario(Arc, Scenario), + + /// [`Feature`] execution being finished. + /// + /// [`Feature`]: gherkin::Feature + Finished, } -/// Event specific to a particular [Scenario](https://cucumber.io/docs/gherkin/reference/#example) -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ScenarioEvent { - Starting(ExampleValues), - Background(Rc, StepEvent), - Step(Rc, StepEvent), - Skipped, - Passed, - Failed(FailureKind), +/// Event specific to a particular [Rule]. +/// +/// [Rule]: https://cucumber.io/docs/gherkin/reference/#rule +#[derive(Debug)] +pub enum Rule { + /// [`Rule`] execution being started. + /// + /// [`Rule`]: gherkin::Rule + Started, + + /// [`Scenario`] event. + Scenario(Arc, Scenario), + + /// [`Rule`] execution being finished. + /// + /// [`Rule`]: gherkin::Rule + Finished, } -/// Event specific to a particular [Rule](https://cucumber.io/docs/gherkin/reference/#rule) -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RuleEvent { - Starting, - Scenario(Rc, ScenarioEvent), +/// Event specific to a particular [Step]. +/// +/// [Step]: https://cucumber.io/docs/gherkin/reference/#step +#[derive(Debug)] +pub enum Step { + /// [`Step`] execution being started. + /// + /// [`Step`]: gherkin::Step + Started, + + /// [`Step`] being skipped. + /// + /// That means there is no [`Regex`] matching [`Step`] in a + /// [`step::Collection`]. + /// + /// [`Regex`]: regex::Regex + /// [`Step`]: gherkin::Step + /// [`step::Collection`]: crate::step::Collection Skipped, + + /// [`Step`] passed. + /// + /// [`Step`]: gherkin::Step Passed, - Failed(FailureKind), + + /// [`Step`] failed. + /// + /// [`Step`]: gherkin::Step + Failed(Option, Info), } -/// Event specific to a particular [Feature](https://cucumber.io/docs/gherkin/reference/#feature) -#[derive(Debug, Clone)] -pub enum FeatureEvent { - Starting, - Scenario(Rc, ScenarioEvent), - Rule(Rc, RuleEvent), +/// Event specific to a particular [Scenario]. +/// +/// [Scenario]: https://cucumber.io/docs/gherkin/reference/#example +#[derive(Debug)] +pub enum Scenario { + /// [`Scenario`] execution being started. + /// + /// [`Scenario`]: gherkin::Scenario + Started, + + /// [`Background`] [`Step`] event. + /// + /// [`Background`]: gherkin::Background + Background(Arc, Step), + + /// [`Step`] event. + Step(Arc, Step), + + /// [`Scenario`] execution being finished. + /// + /// [`Scenario`]: gherkin::Scenario Finished, } -/// Top-level cucumber run event. -#[derive(Debug, Clone)] -pub enum CucumberEvent { - Starting, - Feature(Rc, FeatureEvent), - Finished(crate::runner::RunResult), -} +impl Scenario { + /// Constructs an event of a [`Step`] being started. + /// + /// [`Step`]: gherkin::Step + #[must_use] + pub fn step_started(step: Arc) -> Self { + Self::Step(step, Step::Started) + } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum FailureKind { - TimedOut, - Panic, -} + /// Constructs an event of a [`Background`] [`Step`] being started. + /// + /// [`Background`]: gherkin::Background + /// [`Step`]: gherkin::Step + #[must_use] + pub fn background_step_started(step: Arc) -> Self { + Self::Background(step, Step::Started) + } + + /// Constructs an event of a passed [`Step`]. + /// + /// [`Step`]: gherkin::Step + #[must_use] + pub fn step_passed(step: Arc) -> Self { + Self::Step(step, Step::Passed) + } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum StepFailureKind { - TimedOut, - Panic(CapturedOutput, PanicInfo), + /// Constructs an event of a passed [`Background`] [`Step`]. + /// + /// [`Background`]: gherkin::Background + /// [`Step`]: gherkin::Step + #[must_use] + pub fn background_step_passed(step: Arc) -> Self { + Self::Background(step, Step::Passed) + } + + /// Constructs an event of a skipped [`Step`]. + /// + /// [`Step`]: gherkin::Step + #[must_use] + pub fn step_skipped(step: Arc) -> Self { + Self::Step(step, Step::Skipped) + } + /// Constructs an event of a skipped [`Background`] [`Step`]. + /// + /// [`Background`]: gherkin::Background + /// [`Step`]: gherkin::Step + #[must_use] + pub fn background_step_skipped(step: Arc) -> Self { + Self::Background(step, Step::Skipped) + } + + /// Constructs an event of a failed [`Step`]. + /// + /// [`Step`]: gherkin::Step + #[must_use] + pub fn step_failed( + step: Arc, + world: Option, + info: Info, + ) -> Self { + Self::Step(step, Step::Failed(world, info)) + } + + /// Constructs an event of a failed [`Background`] [`Step`]. + /// + /// [`Background`]: gherkin::Background + /// [`Step`]: gherkin::Step + #[must_use] + pub fn background_step_failed( + step: Arc, + world: Option, + info: Info, + ) -> Self { + Self::Background(step, Step::Failed(world, info)) + } } diff --git a/src/examples.rs b/src/examples.rs deleted file mode 100644 index 8a85ea3a..00000000 --- a/src/examples.rs +++ /dev/null @@ -1,66 +0,0 @@ -/// Content derived from a gherkin `Examples` table. Contains the table's keys -/// and for values drawn from a single row. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExampleValues { - pub keys: Vec, - pub values: Vec, -} - -impl ExampleValues { - /// When no examples exist a vector with one empty ExampleValues struct is returned. - pub fn from_examples(examples: &Option) -> Vec { - match examples { - Some(examples) => { - let mut rows = Vec::with_capacity(examples.table.rows.len()); - for row_index in 1..examples.table.rows.len() { - rows.push(ExampleValues::new( - &examples.table.rows.first().unwrap().to_vec(), - &examples.table.rows.get(row_index).unwrap().to_vec(), - )) - } - rows - } - None => vec![ExampleValues::empty()], - } - } - - pub fn new(keys: &Vec, values: &Vec) -> ExampleValues { - ExampleValues { - keys: keys.into_iter().map(|val| format!("<{}>", val)).collect(), - values: values.to_vec(), - } - } - - pub fn empty() -> ExampleValues { - ExampleValues { - keys: vec![], - values: vec![], - } - } - - pub fn is_empty(&self) -> bool { - self.keys.is_empty() - } - - pub fn insert_values(&self, step: &String) -> String { - let mut modified = step.to_owned(); - for index in 0..self.keys.len() { - let search = self.keys.get(index).unwrap_or(&String::new()).to_owned(); - let replace_with = self.values.get(index).unwrap_or(&String::new()).to_owned(); - modified = modified.replace(&search, &replace_with); - } - modified - } - - pub fn to_string(&self) -> String { - let mut values = Vec::with_capacity(self.keys.len()); - for index in 0..self.keys.len() { - values.push(format!( - "{} = {}", - self.keys.get(index).unwrap_or(&String::new()), - self.values.get(index).unwrap_or(&String::new()) - )); - } - values.join(", ") - } -} diff --git a/src/feature.rs b/src/feature.rs new file mode 100644 index 00000000..17b9b400 --- /dev/null +++ b/src/feature.rs @@ -0,0 +1,120 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! [`gherkin::Feature`] extension. + +use std::{iter, mem}; + +use sealed::sealed; + +/// Helper methods to operate on [`gherkin::Feature`]s. +#[sealed] +pub trait Ext: Sized { + /// Expands [`Scenario Outline`][1] [`Examples`][2]. + /// + /// So this one: + /// ```gherkin + /// Feature: Hungry + /// Scenario Outline: eating + /// Given there are cucumbers + /// When I eat cucumbers + /// Then I should have cucumbers + /// + /// Examples: + /// | start | eat | left | + /// | 12 | 5 | 7 | + /// | 20 | 5 | 15 | + /// ``` + /// + /// Will be expanded as: + /// ```gherkin + /// Feature: Hungry + /// Scenario Outline: eating + /// Given there are 12 cucumbers + /// When I eat 5 cucumbers + /// Then I should have 7 cucumbers + /// Scenario Outline: eating + /// Given there are 20 cucumbers + /// When I eat 5 cucumbers + /// Then I should have 15 cucumbers + /// + /// Examples: + /// | start | eat | left | + /// | 12 | 5 | 7 | + /// | 20 | 5 | 15 | + /// ``` + /// + /// [1]: https://cucumber.io/docs/gherkin/reference/#scenario-outline + /// [2]: https://cucumber.io/docs/gherkin/reference/#examples + #[must_use] + fn expand_examples(self) -> Self; + + /// Counts all the [`Feature`]'s [`Scenario`]s, including [`Rule`]s inside. + /// + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + #[must_use] + fn count_scenarios(&self) -> usize; +} + +#[sealed] +impl Ext for gherkin::Feature { + fn expand_examples(mut self) -> Self { + let expand = |scenarios: Vec| { + scenarios + .into_iter() + .flat_map(|scenario| { + let ((header, vals), examples) = + match scenario.examples.as_ref().and_then(|ex| { + ex.table.rows.split_first().map(|t| (t, ex)) + }) { + Some(s) => s, + None => return vec![scenario], + }; + + vals.iter() + .zip(iter::repeat_with(|| header)) + .enumerate() + .map(|(id, (vals, keys))| { + let mut modified = scenario.clone(); + + // This is done to differentiate `Hash`es of + // scenario outlines with the same examples. + modified.position = examples.position; + modified.position.line += id + 1; + + for step in &mut modified.steps { + for (key, val) in keys.iter().zip(vals) { + step.value = step + .value + .replace(&format!("<{}>", key), val); + } + } + modified + }) + .collect() + }) + .collect() + }; + + for rule in &mut self.rules { + rule.scenarios = expand(mem::take(&mut rule.scenarios)); + } + self.scenarios = expand(mem::take(&mut self.scenarios)); + + self + } + + fn count_scenarios(&self) -> usize { + self.scenarios.len() + + self.rules.iter().map(|r| r.scenarios.len()).sum::() + } +} diff --git a/src/hashable_regex.rs b/src/hashable_regex.rs deleted file mode 100644 index 19b245d2..00000000 --- a/src/hashable_regex.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::hash::{Hash, Hasher}; -use std::ops::Deref; - -use regex::Regex; - -#[derive(Debug, Clone)] -pub struct HashableRegex(pub Regex); - -impl std::fmt::Display for HashableRegex { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl PartialOrd for HashableRegex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for HashableRegex { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.as_str().cmp(other.0.as_str()) - } -} - -impl Hash for HashableRegex { - fn hash(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -impl PartialEq for HashableRegex { - fn eq(&self, other: &HashableRegex) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for HashableRegex {} - -impl Deref for HashableRegex { - type Target = Regex; - - fn deref(&self) -> &Regex { - &self.0 - } -} diff --git a/src/lib.rs b/src/lib.rs index 8710a5ad..59d44b47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ -// Copyright (c) 2018-2021 Brendan Molloy +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -6,81 +8,78 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! A library implementing the Cucumber testing framework for Rust, in Rust. - -#![recursion_limit = "512"] -#![deny(rust_2018_idioms)] +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] #![cfg_attr(docsrs, feature(doc_cfg))] -// Re-export Gherkin for the convenience of everybody -pub use gherkin; - -#[macro_use] -mod macros; - -mod cli; -mod collection; -pub mod criteria; -mod cucumber; +pub mod cucumber; pub mod event; -mod examples; -pub mod output; -mod regex; -pub(crate) mod runner; -mod steps; +pub mod feature; +pub mod parser; +pub mod runner; +pub mod step; +pub mod writer; #[cfg(feature = "macros")] -#[doc(hidden)] -pub mod private; +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub mod codegen; + +use std::error::Error as StdError; -// Re-export for convenience -pub use async_trait::async_trait; -pub use futures; +use async_trait::async_trait; -pub use cucumber::{Context, Cucumber, StepContext}; -pub use examples::ExampleValues; -pub use runner::RunResult; -pub use steps::Steps; +pub use gherkin; #[cfg(feature = "macros")] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] #[doc(inline)] -pub use self::private::WorldInit; +pub use self::codegen::WorldInit; #[cfg(feature = "macros")] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] #[doc(inline)] pub use cucumber_rust_codegen::{given, then, when, WorldInit}; -const TEST_SKIPPED: &str = "Cucumber: test skipped"; - -#[macro_export] -macro_rules! skip { - () => { - panic!("Cucumber: test skipped"); - }; -} +#[doc(inline)] +pub use self::{ + cucumber::Cucumber, + parser::Parser, + runner::{Runner, ScenarioType}, + step::Step, + writer::{ + Arbitrary as ArbitraryWriter, Ext as WriterExt, + Failure as FailureWriter, Writer, + }, +}; -/// The `World` trait represents shared user-defined state -/// for a cucumber run. +/// Represents a shared user-defined state for a [Cucumber] run. +/// It lives on per-[scenario] basis. +/// +/// [Cucumber]: https://cucumber.io +/// [scenario]: https://cucumber.io/docs/gherkin/reference/#descriptions #[async_trait(?Send)] pub trait World: Sized + 'static { - type Error: std::error::Error; + /// Error of creating a new [`World`] instance. + type Error: StdError; + /// Creates a new [`World`] instance. async fn new() -> Result; } - -/// During test runs, a `Cucumber` instance notifies its -/// associated `EventHandler` implementation about the -/// key occurrences in the test lifecycle. -/// -/// User can replace the default `EventHandler` for a `Cucumber` -/// at construction time using `Cucumber::with_handler`. -pub trait EventHandler: 'static { - fn handle_event(&mut self, event: &event::CucumberEvent); -} - -pub type PanicError = Box<(dyn std::any::Any + Send + 'static)>; -pub enum TestError { - TimedOut, - PanicError(PanicError), -} diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index c4e82cad..00000000 --- a/src/macros.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2018-2021 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -macro_rules! cprint { - ($fg:expr, $($arg:tt)*) => {{ - use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; - use std::io::Write; - let mut stdout = StandardStream::stdout(ColorChoice::Always); - let _x = stdout.set_color(ColorSpec::new().set_fg(Some($fg))); - let _x = write!(&mut stdout, $($arg)*); - let _x = stdout.reset(); - }}; - (bold $fg:expr, $($arg:tt)*) => {{ - use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; - use std::io::Write; - let mut stdout = StandardStream::stdout(ColorChoice::Always); - let _x = stdout.set_color(ColorSpec::new().set_fg(Some($fg)).set_bold(true)); - let _x = write!(&mut stdout, $($arg)*); - let _x = stdout.reset(); - }}; -} - -macro_rules! cprintln { - ($fg:expr, $fmt:expr) => (cprint!($fg, concat!($fmt, "\n"))); - ($fg:expr, $fmt:expr, $($arg:tt)*) => (cprint!($fg, concat!($fmt, "\n"), $($arg)*)); - (bold $fg:expr, $fmt:expr) => (cprint!(bold $fg, concat!($fmt, "\n"))); - (bold $fg:expr, $fmt:expr, $($arg:tt)*) => (cprint!(bold $fg, concat!($fmt, "\n"), $($arg)*)); -} - -#[macro_export] -macro_rules! t { - // Async with block and mutable world - (| mut $world:ident, $step:ident | $($input:tt)*) => { - |mut $world, $step| { - use $crate::futures::future::FutureExt as _; - std::panic::AssertUnwindSafe(async move { $($input)* }) - .catch_unwind() - .map(|r| r.map_err($crate::TestError::PanicError)) - .boxed_local() - } - }; - // Async with block and mutable world with type - (| mut $world:ident : $worldty:path, $step:ident | $($input:tt)*) => { - |mut $world: $worldty, $step| { - use $crate::futures::future::FutureExt as _; - std::panic::AssertUnwindSafe(async move { $($input)* }) - .catch_unwind() - .map(|r| r.map_err($crate::TestError::PanicError)) - .boxed_local() - } - }; - // Async with block and immutable world - (| $world:ident, $step:ident | $($input:tt)*) => { - |$world, $step| { - use $crate::futures::future::FutureExt as _; - std::panic::AssertUnwindSafe(async move { $($input)* }) - .catch_unwind() - .map(|r| r.map_err($crate::TestError::PanicError)) - .boxed_local() - } - }; - // Async with block and immutable world with type - (| $world:ident : $worldty:path, $step:ident | $($input:tt)*) => { - |$world: $worldty, $step| { - use $crate::futures::future::FutureExt as _; - std::panic::AssertUnwindSafe(async move { $($input)* }) - .catch_unwind() - .map(|r| r.map_err($crate::TestError::PanicError)) - .boxed_local() - } - }; -} diff --git a/src/output/debug.rs b/src/output/debug.rs deleted file mode 100644 index f938ed98..00000000 --- a/src/output/debug.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std; -use std::path::Path; - -use gherkin; - -use crate::OutputVisitor; -use crate::TestResult; - -pub struct DebugOutput; - -impl OutputVisitor for DebugOutput { - fn new() -> Self - where - Self: Sized, - { - DebugOutput - } - - fn visit_start(&mut self) { - println!("visit_start"); - } - - fn visit_feature(&mut self, feature: &gherkin::Feature, path: &Path) { - println!("visit_feature {} {}", feature.name, path.display()); - } - - fn visit_feature_end(&mut self, feature: &gherkin::Feature) { - println!("visit_feature_end {}", feature.name); - } - - fn visit_feature_error(&mut self, path: &Path, error: &gherkin::Error) { - println!("visit_feature_error {} {}", path.display(), error); - } - - fn visit_rule(&mut self, rule: &gherkin::Rule) { - println!("visit_rule {}", rule.name); - } - - fn visit_rule_end(&mut self, rule: &gherkin::Rule) { - println!("visit_rule_end {}", rule.name); - } - - fn visit_scenario(&mut self, _rule: Option<&gherkin::Rule>, scenario: &crate::Scenario) { - println!("visit_scenario {}", scenario.name); - } - - fn visit_scenario_end(&mut self, _rule: Option<&gherkin::Rule>, scenario: &crate::Scenario) { - println!("visit_scenario_end {}", scenario.name); - } - - fn visit_scenario_skipped( - &mut self, - _rule: Option<&gherkin::Rule>, - scenario: &crate::Scenario, - ) { - println!("visit_scenario_skipped {}", scenario.name); - } - - fn visit_step( - &mut self, - _rule: Option<&gherkin::Rule>, - _scenario: &crate::Scenario, - step: &crate::Step, - ) { - println!("visit_step {} {}", step.raw_type, step.value); - } - - fn visit_step_result( - &mut self, - _rule: Option<&gherkin::Rule>, - _scenario: &crate::Scenario, - step: &crate::Step, - result: &TestResult, - ) { - println!( - "visit_step_result {} {} - {:?}", - step.raw_type, step.value, result - ); - } - - fn visit_finish(&mut self) { - println!("visit_finish"); - } - - fn visit_step_resolved<'a, W: crate::World>( - &mut self, - _step: &crate::Step, - test: &crate::TestCaseType<'a, W>, - ) { - println!("visit_step_resolved {:?}", test); - } -} diff --git a/src/output/default.rs b/src/output/default.rs deleted file mode 100644 index 6fc94eb0..00000000 --- a/src/output/default.rs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::io::Write; -use std::path::PathBuf; -use std::rc::Rc; - -use crate::event::{CapturedOutput, StepFailureKind}; -use crate::runner::{RunResult, Stats}; -use crate::{ - event::{CucumberEvent, RuleEvent, ScenarioEvent, StepEvent}, - EventHandler, -}; -use gherkin::{Feature, LineCol, Rule, Scenario, Step}; - -pub struct BasicOutput { - debug: bool, - step_started: bool, - pending_feature_print_info: Option<(String, String)>, - printed_feature_start: bool, -} - -impl Default for BasicOutput { - fn default() -> BasicOutput { - BasicOutput { - debug: false, - step_started: false, - pending_feature_print_info: None, - printed_feature_start: false, - } - } -} - -fn wrap_with_comment(s: &str, c: &str, indent: &str) -> String { - let tw = textwrap::termwidth(); - let w = tw - indent.chars().count(); - let mut cs: Vec = textwrap::wrap_iter(s, w) - .map(|x| format!("{}{}", indent, &x.trim())) - .collect(); - // Fit the comment onto the last line - let comment_space = tw.saturating_sub(c.chars().count()).saturating_sub(1); - let last_count = cs.last().unwrap().chars().count(); - if last_count > comment_space { - cs.push(format!("{: <1$}", "", comment_space)) - } else { - cs.last_mut() - .unwrap() - .push_str(&format!("{: <1$}", "", comment_space - last_count)); - } - cs.join("\n") -} - -impl BasicOutput { - pub fn new(debug: bool) -> Self { - Self { - debug, - ..Default::default() - } - } - - fn relpath(&self, target: Option<&std::path::PathBuf>) -> String { - let target = match target { - Some(v) => v, - None => return "".into(), - }; - let target = target.canonicalize().expect("invalid target path"); - pathdiff::diff_paths( - &target, - &std::env::current_dir().expect("invalid current directory"), - ) - .expect("invalid target path") - .to_string_lossy() - .to_string() - } - - fn print_step_extras(&mut self, step: &gherkin::Step) { - let indent = " "; - if let Some(ref table) = &step.table { - // Find largest sized item per column - let mut max_size: Vec = vec![0; table.row_width()]; - - for row in &table.rows { - for (n, field) in row.iter().enumerate() { - if field.len() > max_size[n] { - max_size[n] = field.len(); - } - } - } - - let formatted_row_fields: Vec> = (&table.rows) - .iter() - .map(|row| { - row.iter() - .enumerate() - .map(|(n, field)| { - if field.parse::().is_ok() { - format!(" {: >1$} ", field, max_size[n]) - } else { - format!(" {: <1$} ", field, max_size[n]) - } - }) - .collect() - }) - .collect(); - - let border_color = termcolor::Color::Magenta; - - for row in formatted_row_fields { - print!("{}", indent); - self.write("|", border_color, false); - for field in row { - print!("{}", field); - self.write("|", border_color, false); - } - println!(); - } - }; - - if let Some(ref docstring) = &step.docstring { - self.writeln( - &format!("{}\"\"\"", indent), - termcolor::Color::Magenta, - true, - ); - println!("{}", textwrap::indent(docstring, indent).trim_end()); - self.writeln( - &format!("{}\"\"\"", indent), - termcolor::Color::Magenta, - true, - ); - } - } - - fn write(&mut self, s: &str, c: termcolor::Color, bold: bool) { - if bold { - cprint!(bold c, "{}", s); - } else { - cprint!(c, "{}", s); - } - } - - fn writeln(&mut self, s: &str, c: termcolor::Color, bold: bool) { - if bold { - cprintln!(bold c, "{}", s); - } else { - cprintln!(c, "{}", s); - } - } - - fn writeln_cmt(&mut self, s: &str, cmt: &str, indent: &str, c: termcolor::Color, bold: bool) { - if bold { - cprint!(bold c, "{}", wrap_with_comment(s, cmt, indent)); - } else { - cprint!(c, "{}", wrap_with_comment(s, cmt, indent)); - } - cprintln!(termcolor::Color::White, " {}", cmt); - } - - fn delete_last_line(&self) { - let mut out = std::io::stdout(); - let cursor_up = "\x1b[1A"; - let erase_line = "\x1b[2K"; - let _x = write!(&mut out, "{}{}", cursor_up, erase_line); - } - - fn file_line_col(&self, file: Option<&PathBuf>, position: LineCol) -> String { - // the U+00A0 ensures control/cmd clicking doesn't underline weird. - match file { - Some(v) => format!( - "{}:{}:{}\u{00a0}", - self.relpath(Some(v)), - position.line, - position.col - ), - None => format!(":{}:{}\u{00a0}", position.line, position.col), - } - } - - fn print_captured(&mut self, output: &CapturedOutput, color: termcolor::Color) { - if !output.out.is_empty() { - self.writeln( - &format!( - "{:—<1$}", - "———————— Captured stdout: ", - textwrap::termwidth() - ), - color, - true, - ); - - self.writeln( - &textwrap::indent( - &textwrap::fill(&output.out, textwrap::termwidth().saturating_sub(4)), - " ", - ) - .trim_end(), - color, - false, - ); - } - - if !output.err.is_empty() { - self.writeln( - &format!( - "{:—<1$}", - "———————— Captured stderr: ", - textwrap::termwidth() - ), - color, - true, - ); - - self.writeln( - &textwrap::indent( - &textwrap::fill(&output.err, textwrap::termwidth().saturating_sub(4)), - " ", - ) - .trim_end(), - color, - false, - ); - } - - if !output.err.is_empty() || !output.out.is_empty() { - self.writeln(&format!("{:—<1$}", "", textwrap::termwidth()), color, true); - } - } - - fn handle_step( - &mut self, - feature: &Rc, - rule: Option<&Rc>, - _scenario: &Rc, - step: &Rc, - event: &StepEvent, - is_bg: bool, - ) { - let cmt = self.file_line_col(feature.path.as_ref(), step.position); - let msg = if is_bg { - format!("⛓️ {}", &step) - } else { - step.to_string() - }; - let indent = if rule.is_some() { " " } else { " " }; - - if self.step_started { - self.delete_last_line(); - self.step_started = false; - } - - match event { - StepEvent::Starting => { - self.writeln_cmt( - &format!("{}", msg), - &cmt, - indent, - termcolor::Color::White, - false, - ); - self.print_step_extras(&*step); - self.step_started = true; - } - StepEvent::Unimplemented => { - self.writeln_cmt( - &format!("- {}", msg), - &cmt, - indent, - termcolor::Color::Cyan, - false, - ); - self.print_step_extras(&*step); - self.write(&format!("{} ⚡ ", indent), termcolor::Color::Yellow, false); - println!("Not yet implemented (skipped)"); - } - StepEvent::Skipped => { - self.writeln_cmt( - &format!("- {}", msg), - &cmt, - indent, - termcolor::Color::Cyan, - false, - ); - self.print_step_extras(&*step); - } - StepEvent::Passed(output) => { - self.writeln_cmt( - &format!("✔ {}", msg), - &cmt, - indent, - termcolor::Color::Green, - false, - ); - self.print_step_extras(&*step); - if self.debug { - self.print_captured(output, termcolor::Color::Cyan); - } - } - StepEvent::Failed(StepFailureKind::Panic(output, panic_info)) => { - self.writeln_cmt( - &format!("✘ {}", msg), - &cmt, - indent, - termcolor::Color::Red, - false, - ); - self.print_step_extras(&*step); - self.writeln_cmt( - &format!( - "{:—<1$}", - "[!] Step failed: ", - textwrap::termwidth() - .saturating_sub(panic_info.location.to_string().chars().count()) - .saturating_sub(6), - ), - &panic_info.location.to_string(), - "———— ", - termcolor::Color::Red, - true, - ); - self.writeln( - &textwrap::indent( - &textwrap::fill( - &panic_info.payload, - textwrap::termwidth().saturating_sub(4), - ), - " ", - ) - .trim_end(), - termcolor::Color::Red, - false, - ); - self.print_captured(output, termcolor::Color::Red); - } - StepEvent::Failed(StepFailureKind::TimedOut) => { - self.writeln_cmt( - &format!("✘ {}", msg), - &cmt, - indent, - termcolor::Color::Red, - false, - ); - self.print_step_extras(&*step); - self.writeln_cmt( - &format!( - "{:—<1$}", - "[!] Step timed out", - textwrap::termwidth().saturating_sub(6), - ), - "", - "———— ", - termcolor::Color::Red, - true, - ); - } - } - } - - fn handle_scenario( - &mut self, - feature: &Rc, - rule: Option<&Rc>, - scenario: &Rc, - event: &ScenarioEvent, - ) { - match event { - ScenarioEvent::Starting(example_values) => { - let cmt = self.file_line_col(feature.path.as_ref(), scenario.position); - let text = if example_values.is_empty() { - format!("{}: {} ", &scenario.keyword, &scenario.name) - } else { - format!( - "{}: {}\n => {}", - &scenario.keyword, - &scenario.name, - example_values.to_string(), - ) - }; - let indent = if rule.is_some() { " " } else { " " }; - self.writeln_cmt(&text, &cmt, indent, termcolor::Color::White, true); - } - ScenarioEvent::Background(step, event) => { - self.handle_step(feature, rule, scenario, step, event, true) - } - ScenarioEvent::Step(step, event) => { - self.handle_step(feature, rule, scenario, step, event, false) - } - _ => {} - } - } - - fn handle_rule(&mut self, feature: &Rc, rule: &Rc, event: &RuleEvent) { - if let RuleEvent::Scenario(scenario, evt) = event { - self.handle_scenario(feature, Some(rule), scenario, evt) - } else if *event == RuleEvent::Starting { - let cmt = self.file_line_col(feature.path.as_ref(), rule.position); - self.writeln_cmt( - &format!("{}: {}", &rule.keyword, &rule.name), - &cmt, - " ", - termcolor::Color::White, - true, - ); - } - } - - fn print_counter(&self, name: &str, stats: &Stats) { - use termcolor::Color::*; - - cprint!(bold White, "{} {} (", stats.total, name); - - if stats.failed > 0 { - cprint!(bold Red, "{} failed", stats.failed); - } - - if stats.skipped > 0 { - if stats.failed > 0 { - cprint!(bold White, ", "); - } - cprint!(bold Cyan, "{} skipped", stats.skipped); - } - - if stats.failed > 0 || stats.skipped > 0 { - cprint!(bold White, ", "); - } - - cprint!(bold Green, "{} passed", stats.passed); - cprintln!(bold White, ")"); - } - - fn print_finish(&self, result: &RunResult) { - use termcolor::Color::*; - - cprintln!(bold Blue, "[Summary]"); - cprintln!(bold White, "{} features", result.features.total); - - self.print_counter("scenarios", &result.scenarios); - if result.rules.total > 0 { - self.print_counter("rules", &result.rules); - } - self.print_counter("steps", &result.steps); - - let t = result.elapsed; - println!( - "\nFinished in {}.{} seconds.", - t.as_secs(), - t.subsec_millis() - ); - } -} - -impl EventHandler for BasicOutput { - fn handle_event(&mut self, event: &CucumberEvent) { - match event { - CucumberEvent::Starting => { - cprintln!(bold termcolor::Color::Blue, "[Cucumber v{}]", env!("CARGO_PKG_VERSION")) - } - CucumberEvent::Finished(ref r) => self.print_finish(r), - CucumberEvent::Feature(feature, event) => match event { - crate::event::FeatureEvent::Starting => { - let msg = format!("{}: {}", &feature.keyword, &feature.name); - let cmt = self.file_line_col(feature.path.as_ref(), feature.position); - self.pending_feature_print_info = Some((msg, cmt)); - self.printed_feature_start = false; - } - crate::event::FeatureEvent::Scenario(scenario, event) => { - if let Some((msg, cmt)) = self.pending_feature_print_info.take() { - self.writeln_cmt(&msg, &cmt, "", termcolor::Color::White, true); - println!(); - self.printed_feature_start = true; - } - self.handle_scenario(feature, None, scenario, event) - } - crate::event::FeatureEvent::Rule(rule, event) => { - self.handle_rule(feature, rule, event) - } - crate::event::FeatureEvent::Finished => { - if self.printed_feature_start { - println!(); - } - } - }, - } - } -} diff --git a/src/output/mod.rs b/src/output/mod.rs deleted file mode 100644 index a3e35b8d..00000000 --- a/src/output/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -mod default; - -pub use default::BasicOutput; diff --git a/src/panic_trap.rs b/src/panic_trap.rs deleted file mode 100644 index a3dece52..00000000 --- a/src/panic_trap.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::io::Read; -use std::ops::Deref; -use std::panic; -use std::sync::{Arc, Mutex}; - -use shh::{stderr, stdout}; - -#[derive(Debug, Clone)] -pub struct PanicDetails { - pub payload: String, - pub location: String, -} - -impl PanicDetails { - fn from_panic_info(info: &panic::PanicInfo) -> PanicDetails { - let payload = if let Some(s) = info.payload().downcast_ref::() { - s.clone() - } else if let Some(s) = info.payload().downcast_ref::<&str>() { - s.deref().to_owned() - } else { - "Opaque panic payload".to_owned() - }; - - let location = info - .location() - .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) - .unwrap_or_else(|| "Unknown panic location".to_owned()); - - PanicDetails { payload, location } - } -} - -pub struct PanicTrap { - pub result: Result, - pub stdout: Vec, - pub stderr: Vec, -} - -impl PanicTrap { - pub fn run T>(quiet: bool, f: F) -> PanicTrap { - if quiet { - PanicTrap::run_quietly(f) - } else { - PanicTrap::run_loudly(f) - } - } - - fn run_quietly T>(f: F) -> PanicTrap { - let mut stdout = stdout().expect("Failed to capture stdout"); - let mut stderr = stderr().expect("Failed to capture stderr"); - - let mut trap = PanicTrap::run_loudly(f); - - stdout.read_to_end(&mut trap.stdout).unwrap(); - stderr.read_to_end(&mut trap.stderr).unwrap(); - - trap - } - - fn run_loudly T>(f: F) -> PanicTrap { - let last_panic = Arc::new(Mutex::new(None)); - - panic::set_hook({ - let last_panic = last_panic.clone(); - - Box::new(move |info| { - *last_panic.lock().expect("Last panic mutex poisoned") = - Some(PanicDetails::from_panic_info(info)); - }) - }); - - let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); - - let _ = panic::take_hook(); - - PanicTrap { - result: result.map_err(|_| { - last_panic - .lock() - .expect("Last panic mutex poisoned") - .take() - .expect("Panic occurred but no panic details were set") - }), - stdout: Vec::new(), - stderr: Vec::new(), - } - } -} diff --git a/src/parser/basic.rs b/src/parser/basic.rs new file mode 100644 index 00000000..fc8ff1bb --- /dev/null +++ b/src/parser/basic.rs @@ -0,0 +1,123 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Default [`Parser`] implementation. + +use std::{ + borrow::Cow, + path::{Path, PathBuf}, + vec, +}; + +use derive_more::{Display, Error}; +use futures::stream; +use gherkin::GherkinEnv; +use globwalk::GlobWalkerBuilder; + +use super::{Parser, Result as ParseResult}; + +/// Default [`Parser`]. +/// +/// As there is no async runtime-agnostic way to interact with IO, this +/// [`Parser`] is blocking. +#[derive(Clone, Debug, Default)] +pub struct Basic { + /// Optional custom language of [`gherkin`] keywords. + /// + /// Default is English. + language: Option>, +} + +impl> Parser for Basic { + type Output = stream::Iter>>; + + fn parse(self, path: I) -> Self::Output { + let features = || { + let path = path.as_ref(); + let path = match path.canonicalize().or_else(|_| { + let mut buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + buf.push( + path.strip_prefix("/") + .or_else(|_| path.strip_prefix("./")) + .unwrap_or(path), + ); + buf.as_path().canonicalize() + }) { + Ok(p) => p, + Err(err) => { + return vec![Err(gherkin::ParseFileError::Reading { + path: path.to_path_buf(), + source: err, + })]; + } + }; + + if path.is_file() { + let env = self + .language + .as_ref() + .and_then(|l| GherkinEnv::new(l).ok()) + .unwrap_or_default(); + vec![gherkin::Feature::parse_path(path, env)] + } else { + let walker = GlobWalkerBuilder::new(path, "*.feature") + .case_insensitive(true) + .build() + .unwrap(); + walker + .filter_map(Result::ok) + .map(|entry| { + let env = self + .language + .as_ref() + .and_then(|l| GherkinEnv::new(l).ok()) + .unwrap_or_default(); + gherkin::Feature::parse_path(entry.path(), env) + }) + .collect::>() + } + }; + + stream::iter(features().into_iter()) + } +} + +impl Basic { + /// Creates a new [`Basic`] [`Parser`]. + #[must_use] + pub fn new() -> Self { + Self { language: None } + } + + /// Sets the provided language to parse [`gherkin`] files with instead of + /// the default one (English). + /// + /// # Errors + /// + /// If the provided language isn't supported. + pub fn language( + mut self, + name: impl Into>, + ) -> Result { + let name = name.into(); + if !gherkin::is_language_supported(&name) { + return Err(UnsupportedLanguageError(name)); + } + self.language = Some(name); + Ok(self) + } +} + +/// Error of [`gherkin`] not supporting keywords in some language. +#[derive(Debug, Display, Error)] +#[display(fmt = "Language {} isn't supported", _0)] +pub struct UnsupportedLanguageError( + #[error(not(source))] pub Cow<'static, str>, +); diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 00000000..4d4cfbbd --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Tools for parsing [Gherkin] files. +//! +//! [Gherkin]: https://cucumber.io/docs/gherkin/reference + +pub mod basic; + +use futures::Stream; + +#[doc(inline)] +pub use self::basic::Basic; + +/// Source of parsed [`Feature`]s. +/// +/// [`Feature`]: gherkin::Feature +pub trait Parser { + /// Output [`Stream`] of parsed [`Feature`]s. + /// + /// [`Feature`]: gherkin::Feature + type Output: Stream> + 'static; + + /// Parses the given `input` into a [`Stream`] of [`Feature`]s. + /// + /// [`Feature`]: gherkin::Feature + fn parse(self, input: I) -> Self::Output; +} + +/// Result of parsing [Gherkin] files. +/// +/// [Gherkin]: https://cucumber.io/docs/gherkin/reference +pub type Result = std::result::Result; diff --git a/src/private.rs b/src/private.rs deleted file mode 100644 index 17594ded..00000000 --- a/src/private.rs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2018-2021 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Helper type-level glue for [`cucumber_rust_codegen`] crate. - -pub use inventory::{self, collect, submit}; - -use crate::{cucumber::StepContext, runner::TestFuture, Cucumber, Steps, World}; - -/// [`World`] extension with auto-wiring capabilities. -pub trait WorldInit: - WorldInventory -where - G1: Step + inventory::Collect, - G2: StepRegex + inventory::Collect, - G3: StepAsync + inventory::Collect, - G4: StepRegexAsync + inventory::Collect, - W1: Step + inventory::Collect, - W2: StepRegex + inventory::Collect, - W3: StepAsync + inventory::Collect, - W4: StepRegexAsync + inventory::Collect, - T1: Step + inventory::Collect, - T2: StepRegex + inventory::Collect, - T3: StepAsync + inventory::Collect, - T4: StepRegexAsync + inventory::Collect, -{ - /// Returns runner for tests with auto-wired steps marked by [`given`], [`when`] and [`then`] - /// attributes. - /// - /// [`given`]: crate::given - /// [`then`]: crate::then - /// [`when`]: crate::when - #[must_use] - fn init(features: &[&str]) -> Cucumber { - Cucumber::new().features(features).steps({ - let mut builder: Steps = Steps::new(); - - let (simple, regex, async_, async_regex) = Self::cucumber_given(); - for e in simple { - let _ = builder.given(e.inner().0, e.inner().1); - } - for e in regex { - let _ = builder.given_regex(e.inner().0, e.inner().1); - } - for e in async_ { - let _ = builder.given_async(e.inner().0, e.inner().1); - } - for e in async_regex { - let _ = builder.given_regex_async(e.inner().0, e.inner().1); - } - - let (simple, regex, async_, async_regex) = Self::cucumber_when(); - for e in simple { - let _ = builder.when(e.inner().0, e.inner().1); - } - for e in regex { - let _ = builder.when_regex(e.inner().0, e.inner().1); - } - for e in async_ { - let _ = builder.when_async(e.inner().0, e.inner().1); - } - for e in async_regex { - let _ = builder.when_regex_async(e.inner().0, e.inner().1); - } - - let (simple, regex, async_, async_regex) = Self::cucumber_then(); - for e in simple { - let _ = builder.then(e.inner().0, e.inner().1); - } - for e in regex { - let _ = builder.then_regex(e.inner().0, e.inner().1); - } - for e in async_ { - let _ = builder.then_async(e.inner().0, e.inner().1); - } - for e in async_regex { - let _ = builder.then_regex_async(e.inner().0, e.inner().1); - } - - builder - }) - } -} - -impl - WorldInit for E -where - G1: Step + inventory::Collect, - G2: StepRegex + inventory::Collect, - G3: StepAsync + inventory::Collect, - G4: StepRegexAsync + inventory::Collect, - W1: Step + inventory::Collect, - W2: StepRegex + inventory::Collect, - W3: StepAsync + inventory::Collect, - W4: StepRegexAsync + inventory::Collect, - T1: Step + inventory::Collect, - T2: StepRegex + inventory::Collect, - T3: StepAsync + inventory::Collect, - T4: StepRegexAsync + inventory::Collect, - E: WorldInventory, -{ -} - -/// [`World`] extension allowing to register steps in [`inventory`]. -pub trait WorldInventory: World -where - G1: Step + inventory::Collect, - G2: StepRegex + inventory::Collect, - G3: StepAsync + inventory::Collect, - G4: StepRegexAsync + inventory::Collect, - W1: Step + inventory::Collect, - W2: StepRegex + inventory::Collect, - W3: StepAsync + inventory::Collect, - W4: StepRegexAsync + inventory::Collect, - T1: Step + inventory::Collect, - T2: StepRegex + inventory::Collect, - T3: StepAsync + inventory::Collect, - T4: StepRegexAsync + inventory::Collect, -{ - #[must_use] - fn cucumber_given() -> ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) { - ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) - } - - fn new_given(name: &'static str, fun: CucumberFn) -> G1 { - G1::new(name, fun) - } - - fn new_given_regex(name: &'static str, fun: CucumberRegexFn) -> G2 { - G2::new(name, fun) - } - - fn new_given_async(name: &'static str, fun: CucumberAsyncFn) -> G3 { - G3::new(name, fun) - } - - fn new_given_regex_async(name: &'static str, fun: CucumberAsyncRegexFn) -> G4 { - G4::new(name, fun) - } - - #[must_use] - fn cucumber_when() -> ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) { - ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) - } - - fn new_when(name: &'static str, fun: CucumberFn) -> W1 { - W1::new(name, fun) - } - - fn new_when_regex(name: &'static str, fun: CucumberRegexFn) -> W2 { - W2::new(name, fun) - } - - fn new_when_async(name: &'static str, fun: CucumberAsyncFn) -> W3 { - W3::new(name, fun) - } - - fn new_when_regex_async(name: &'static str, fun: CucumberAsyncRegexFn) -> W4 { - W4::new(name, fun) - } - - #[must_use] - fn cucumber_then() -> ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) { - ( - inventory::iter, - inventory::iter, - inventory::iter, - inventory::iter, - ) - } - - fn new_then(name: &'static str, fun: CucumberFn) -> T1 { - T1::new(name, fun) - } - - fn new_then_regex(name: &'static str, fun: CucumberRegexFn) -> T2 { - T2::new(name, fun) - } - - fn new_then_async(name: &'static str, fun: CucumberAsyncFn) -> T3 { - T3::new(name, fun) - } - - fn new_then_regex_async(name: &'static str, fun: CucumberAsyncRegexFn) -> T4 { - T4::new(name, fun) - } -} - -pub trait Step { - fn new(_: &'static str, _: CucumberFn) -> Self; - fn inner(&self) -> (&'static str, CucumberFn); -} - -pub trait StepRegex { - fn new(_: &'static str, _: CucumberRegexFn) -> Self; - fn inner(&self) -> (&'static str, CucumberRegexFn); -} - -pub trait StepAsync { - fn new(_: &'static str, _: CucumberAsyncFn) -> Self; - fn inner(&self) -> (&'static str, CucumberAsyncFn); -} - -pub trait StepRegexAsync { - fn new(_: &'static str, _: CucumberAsyncRegexFn) -> Self; - fn inner(&self) -> (&'static str, CucumberAsyncRegexFn); -} - -pub type CucumberFn = fn(W, StepContext) -> W; - -pub type CucumberRegexFn = fn(W, StepContext) -> W; - -pub type CucumberAsyncFn = fn(W, StepContext) -> TestFuture; - -pub type CucumberAsyncRegexFn = fn(W, StepContext) -> TestFuture; diff --git a/src/regex.rs b/src/regex.rs deleted file mode 100644 index 959054cf..00000000 --- a/src/regex.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2018-2020 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::hash::{Hash, Hasher}; -use std::ops::Deref; - -use regex::Regex; - -#[derive(Debug, Clone)] -pub struct HashableRegex(pub Regex); - -impl Hash for HashableRegex { - fn hash(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -impl PartialEq for HashableRegex { - fn eq(&self, other: &HashableRegex) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for HashableRegex {} - -impl Deref for HashableRegex { - type Target = Regex; - - fn deref(&self) -> &Regex { - &self.0 - } -} - -impl PartialOrd for HashableRegex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for HashableRegex { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.as_str().cmp(other.0.as_str()) - } -} diff --git a/src/runner.rs b/src/runner.rs deleted file mode 100644 index 980ea388..00000000 --- a/src/runner.rs +++ /dev/null @@ -1,716 +0,0 @@ -// Copyright (c) 2018-2021 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::any::Any; -use std::panic; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::{Arc, TryLockError}; - -use async_stream::stream; -use futures::{Future, FutureExt, Stream, StreamExt, TryFutureExt}; -use regex::Regex; - -use crate::{ - collection::StepsCollection, - criteria::Criteria, - cucumber::{Context, LifecycleContext, StepContext}, -}; -use crate::{cucumber::LifecycleFn, event::*}; -use crate::{TestError, World, TEST_SKIPPED}; - -use super::ExampleValues; -use std::time::{Duration, Instant}; - -pub(crate) type TestFuture = Pin>>>; - -impl From W> for TestFunction { - fn from(f: fn(W, StepContext) -> W) -> Self { - TestFunction::BasicSync(f) - } -} - -impl From TestFuture> for TestFunction { - fn from(f: fn(W, StepContext) -> TestFuture) -> Self { - TestFunction::BasicAsync(f) - } -} - -impl From W> for StepFn { - fn from(f: fn(W, StepContext) -> W) -> Self { - StepFn::Sync(f) - } -} - -impl From TestFuture> for StepFn { - fn from(f: fn(W, StepContext) -> TestFuture) -> Self { - StepFn::Async(f) - } -} - -#[derive(Clone, Copy)] -pub enum StepFn { - Sync(fn(W, StepContext) -> W), - Async(fn(W, StepContext) -> TestFuture), -} - -impl From<&StepFn> for TestFunction { - fn from(step: &StepFn) -> Self { - match step { - StepFn::Sync(x) => TestFunction::BasicSync(*x), - StepFn::Async(x) => TestFunction::BasicAsync(*x), - } - } -} - -pub enum TestFunction { - BasicSync(fn(W, StepContext) -> W), - BasicAsync(fn(W, StepContext) -> TestFuture), - RegexSync(fn(W, StepContext) -> W, Vec), - RegexAsync(fn(W, StepContext) -> TestFuture, Vec), -} - -fn coerce_error(err: &(dyn Any + Send + 'static)) -> String { - if let Some(string) = err.downcast_ref::() { - string.to_string() - } else if let Some(string) = err.downcast_ref::<&str>() { - (*string).to_string() - } else { - "(Could not resolve panic payload)".into() - } -} - -/// Stats for various event results -#[derive(Debug, Default, Clone)] -pub struct Stats { - /// total events seen - pub total: u32, - /// events skipped - pub skipped: u32, - /// events that passed - pub passed: u32, - /// events that failed - pub failed: u32, - /// events that timed out - pub timed_out: u32, -} - -impl Stats { - /// Indicates this has failing states (aka failed or timed_out) - pub fn failed(&self) -> bool { - self.failed > 0 || self.timed_out > 0 - } -} - -/// The result of the Cucumber run -#[derive(Debug, Clone)] -pub struct RunResult { - /// the time when the run was started - pub started: std::time::Instant, - /// the time the run took - pub elapsed: std::time::Duration, - /// Stats of features of this run - pub features: Stats, - /// Stats of rules of this run - pub rules: Stats, - /// Stats of scenarios of this run - pub scenarios: Stats, - /// Stats of scenarios of this run - pub steps: Stats, -} - -impl RunResult { - /// Indicates this has failing states (aka failed or timed_out) - pub fn failed(&self) -> bool { - self.features.failed() || self.scenarios.failed() - } -} - -#[derive(Debug, Clone)] -struct StatsCollector { - started: std::time::Instant, - features: Stats, - rules: Stats, - scenarios: Stats, - steps: Stats, -} - -impl StatsCollector { - fn new() -> Self { - StatsCollector { - started: std::time::Instant::now(), - features: Default::default(), - rules: Default::default(), - scenarios: Default::default(), - steps: Default::default(), - } - } - - fn handle_rule_event(&mut self, event: &RuleEvent) { - match event { - RuleEvent::Starting => { - self.rules.total += 1; - } - RuleEvent::Scenario(_, ref event) => self.handle_scenario_event(event), - RuleEvent::Skipped => { - self.rules.skipped += 1; - } - RuleEvent::Passed => { - self.rules.passed += 1; - } - RuleEvent::Failed(FailureKind::Panic) => { - self.rules.failed += 1; - } - RuleEvent::Failed(FailureKind::TimedOut) => { - self.rules.timed_out += 1; - } - } - } - - fn handle_scenario_event(&mut self, event: &ScenarioEvent) { - match event { - ScenarioEvent::Starting(_) => { - self.scenarios.total += 1; - } - ScenarioEvent::Background(_, ref event) => self.handle_step_event(event), - ScenarioEvent::Step(_, ref event) => self.handle_step_event(event), - ScenarioEvent::Skipped => { - self.scenarios.skipped += 1; - } - ScenarioEvent::Passed => { - self.scenarios.passed += 1; - } - ScenarioEvent::Failed(FailureKind::Panic) => { - self.scenarios.failed += 1; - } - ScenarioEvent::Failed(FailureKind::TimedOut) => { - self.scenarios.timed_out += 1; - } - } - } - - fn handle_step_event(&mut self, event: &StepEvent) { - self.steps.total += 1; - match event { - StepEvent::Starting => { - // we don't have to count this - } - StepEvent::Unimplemented => { - self.steps.skipped += 1; - } - StepEvent::Skipped => { - self.steps.skipped += 1; - } - StepEvent::Passed(_) => { - self.steps.passed += 1; - } - StepEvent::Failed(StepFailureKind::Panic(_, _)) => { - self.steps.failed += 1; - } - StepEvent::Failed(StepFailureKind::TimedOut) => { - self.steps.timed_out += 1; - } - } - } - - fn handle_feature_event(&mut self, event: &FeatureEvent) { - match event { - FeatureEvent::Starting => { - self.features.total += 1; - } - FeatureEvent::Scenario(_, ref event) => self.handle_scenario_event(event), - FeatureEvent::Rule(_, ref event) => self.handle_rule_event(event), - _ => {} - } - } - - fn collect(self) -> RunResult { - let StatsCollector { - started, - features, - rules, - scenarios, - steps, - } = self; - - RunResult { - elapsed: started.elapsed(), - started, - features, - rules, - scenarios, - steps, - } - } -} - -pub(crate) struct Runner { - context: Rc, - functions: StepsCollection, - features: Rc>, - step_timeout: Option, - enable_capture: bool, - scenario_filter: Option, - before: Vec<(Criteria, LifecycleFn)>, - after: Vec<(Criteria, LifecycleFn)>, -} - -impl Runner { - #[inline] - pub fn new( - context: Rc, - functions: StepsCollection, - features: Rc>, - step_timeout: Option, - enable_capture: bool, - scenario_filter: Option, - before: Vec<(Criteria, LifecycleFn)>, - after: Vec<(Criteria, LifecycleFn)>, - ) -> Rc> { - Rc::new(Runner { - context, - functions, - features, - step_timeout, - enable_capture, - scenario_filter, - before, - after, - }) - } - - async fn run_step(self: Rc, step: Rc, world: W) -> TestEvent { - use std::io::prelude::*; - - let func = match self.functions.resolve(&step) { - Some(v) => v, - None => return TestEvent::Unimplemented, - }; - - let mut maybe_capture_handles = if self.enable_capture { - Some((shh::stdout().unwrap(), shh::stderr().unwrap())) - } else { - None - }; - - // This ugly mess here catches the panics from async calls. - let panic_info = Arc::new(std::sync::Mutex::new(None)); - let panic_info0 = Arc::clone(&panic_info); - let step_timeout0 = self.step_timeout; - panic::set_hook(Box::new(move |pi| { - let panic_info = Some(PanicInfo { - location: pi - .location() - .map(|l| Location { - file: l.file().to_string(), - line: l.line(), - column: l.column(), - }) - .unwrap_or_else(Location::unknown), - payload: coerce_error(pi.payload()), - }); - if let Some(step_timeout) = step_timeout0 { - let start_point = Instant::now(); - loop { - match panic_info0.try_lock() { - Ok(mut guard) => { - *guard = panic_info; - return; - } - Err(TryLockError::WouldBlock) => { - if start_point.elapsed() < step_timeout { - continue; - } else { - return; - } - } - Err(TryLockError::Poisoned(_)) => { - return; - } - } - } - } else { - *panic_info0.lock().unwrap() = panic_info; - } - })); - - let context = Rc::clone(&self.context); - - let step_future = match func { - TestFunction::BasicAsync(f) => (f)(world, StepContext::new(context, step, vec![])), - TestFunction::RegexAsync(f, r) => (f)(world, StepContext::new(context, step, r)), - - TestFunction::BasicSync(test_fn) => std::panic::AssertUnwindSafe(async move { - (test_fn)(world, StepContext::new(context, step, vec![])) - }) - .catch_unwind() - .map_err(TestError::PanicError) - .boxed_local(), - - TestFunction::RegexSync(test_fn, matches) => std::panic::AssertUnwindSafe(async move { - (test_fn)(world, StepContext::new(context, step, matches)) - }) - .catch_unwind() - .map_err(TestError::PanicError) - .boxed_local(), - }; - - let result = if let Some(step_timeout) = self.step_timeout { - let timeout = Box::pin(async { - futures_timer::Delay::new(step_timeout).await; - Err(TestError::TimedOut) - }); - futures::future::select(timeout, step_future) - .await - .factor_first() - .0 - } else { - step_future.await - }; - - let mut out = String::new(); - let mut err = String::new(); - // Note the use of `take` to move the handles into this branch so that they are - // appropriately dropped following - if let Some((mut stdout, mut stderr)) = maybe_capture_handles.take() { - stdout.read_to_string(&mut out).unwrap_or_else(|_| { - out = "Error retrieving stdout".to_string(); - 0 - }); - stderr.read_to_string(&mut err).unwrap_or_else(|_| { - err = "Error retrieving stderr".to_string(); - 0 - }); - } - - let output = CapturedOutput { out, err }; - match result { - Ok(w) => TestEvent::Success(w, output), - Err(TestError::TimedOut) => TestEvent::Failure(StepFailureKind::TimedOut), - Err(TestError::PanicError(e)) => { - let e = coerce_error(&e); - if &*e == TEST_SKIPPED { - return TestEvent::Skipped; - } - - let pi = if let Some(step_timeout) = self.step_timeout { - let start_point = Instant::now(); - loop { - match panic_info.try_lock() { - Ok(mut guard) => { - break guard.take().unwrap_or_else(PanicInfo::unknown); - } - Err(TryLockError::WouldBlock) => { - if start_point.elapsed() < step_timeout { - futures_timer::Delay::new(Duration::from_micros(10)).await; - continue; - } else { - break PanicInfo::unknown(); - } - } - Err(TryLockError::Poisoned(_)) => break PanicInfo::unknown(), - } - } - } else { - let mut guard = panic_info.lock().unwrap(); - guard.take().unwrap_or_else(PanicInfo::unknown) - }; - TestEvent::Failure(StepFailureKind::Panic(output, pi)) - } - } - } - - fn run_feature(self: Rc, feature: Rc) -> FeatureStream { - Box::pin(stream! { - yield FeatureEvent::Starting; - - let context = LifecycleContext { - context: self.context.clone(), - feature: Rc::clone(&feature), - rule: None, - scenario: None, - }; - - for (criteria, handler) in self.before.iter() { - if !criteria.context().is_feature() { - continue; - } - - if criteria.eval(&*feature, None, None) { - (handler)(context.clone()).await; - } - } - - for scenario in feature.scenarios.iter() { - // If regex filter fails, skip the scenario - if let Some(ref regex) = self.scenario_filter { - if !regex.is_match(&scenario.name) { - continue; - } - } - - let examples = ExampleValues::from_examples(&scenario.examples); - for example_values in examples { - let this = Rc::clone(&self); - let scenario = Rc::new(scenario.clone()); - - let mut stream = this.run_scenario(Rc::clone(&scenario), None, Rc::clone(&feature), example_values); - - while let Some(event) = stream.next().await { - yield FeatureEvent::Scenario(Rc::clone(&scenario), event); - } - } - } - - for rule in feature.rules.iter() { - let this = Rc::clone(&self); - let rule = Rc::new(rule.clone()); - - let mut stream = this.run_rule(Rc::clone(&rule), Rc::clone(&feature)); - - while let Some(event) = stream.next().await { - yield FeatureEvent::Rule(Rc::clone(&rule), event); - } - } - - for (criteria, handler) in self.after.iter() { - if !criteria.context().is_feature() { - continue; - } - - if criteria.eval(&*feature, None, None) { - (handler)(context.clone()).await; - } - } - - yield FeatureEvent::Finished; - }) - } - - fn run_rule( - self: Rc, - rule: Rc, - feature: Rc, - ) -> RuleStream { - Box::pin(stream! { - yield RuleEvent::Starting; - - let context = LifecycleContext { - context: self.context.clone(), - feature: Rc::clone(&feature), - rule: Some(Rc::clone(&rule)), - scenario: None, - }; - - for (criteria, handler) in self.before.iter() { - if !criteria.context().is_rule() { - continue; - } - - if criteria.eval(&*feature, Some(&*rule), None) { - (handler)(context.clone()).await; - } - } - - let mut return_event = None; - - for scenario in rule.scenarios.iter() { - let this = Rc::clone(&self); - let scenario = Rc::new(scenario.clone()); - - let mut stream = this.run_scenario(Rc::clone(&scenario), Some(Rc::clone(&rule)), Rc::clone(&feature), ExampleValues::empty()); - - while let Some(event) = stream.next().await { - match event { - ScenarioEvent::Failed(FailureKind::Panic) => { return_event = Some(RuleEvent::Failed(FailureKind::Panic)); }, - ScenarioEvent::Failed(FailureKind::TimedOut) => { return_event = Some(RuleEvent::Failed(FailureKind::TimedOut)); }, - ScenarioEvent::Passed if return_event.is_none() => { return_event = Some(RuleEvent::Passed); }, - ScenarioEvent::Skipped if return_event == Some(RuleEvent::Passed) => { return_event = Some(RuleEvent::Skipped); } - _ => {} - } - yield RuleEvent::Scenario(Rc::clone(&scenario), event); - } - } - - for (criteria, handler) in self.after.iter() { - if !criteria.context().is_rule() { - continue; - } - - if criteria.eval(&*feature, Some(&*rule), None) { - (handler)(context.clone()).await; - } - } - - yield return_event.unwrap_or(RuleEvent::Skipped); - }) - } - - fn run_scenario( - self: Rc, - scenario: Rc, - rule: Option>, - feature: Rc, - example: super::ExampleValues, - ) -> ScenarioStream { - Box::pin(stream! { - yield ScenarioEvent::Starting(example.clone()); - - let context = LifecycleContext { - context: self.context.clone(), - feature: Rc::clone(&feature), - rule: rule.clone(), - scenario: Some(Rc::clone(&scenario)), - }; - - for (criteria, handler) in self.before.iter() { - if !criteria.context().is_scenario() { - continue; - } - - if criteria.eval(&*feature, rule.as_ref().map(|x| &**x), Some(&*scenario)) { - (handler)(context.clone()).await; - } - } - - let mut world = Some(W::new().await.unwrap()); - - let mut is_success = true; - - if let Some(steps) = feature.background.as_ref().map(|x| &x.steps) { - for step in steps.iter() { - let this = Rc::clone(&self); - let step = Rc::new(step.clone()); - - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Starting); - - let result = this.run_step(Rc::clone(&step), world.take().unwrap()).await; - - match result { - TestEvent::Success(w, output) => { - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Passed(output)); - // Pass world result for current step to next step. - world = Some(w); - } - TestEvent::Failure(StepFailureKind::Panic(output, e)) => { - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Failed(StepFailureKind::Panic(output, e))); - yield ScenarioEvent::Failed(FailureKind::Panic); - is_success = false; - break; - }, - TestEvent::Failure(StepFailureKind::TimedOut) => { - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Failed(StepFailureKind::TimedOut)); - yield ScenarioEvent::Failed(FailureKind::TimedOut); - is_success = false; - break; - } - TestEvent::Skipped => { - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Skipped); - yield ScenarioEvent::Skipped; - is_success = false; - break; - } - TestEvent::Unimplemented => { - yield ScenarioEvent::Background(Rc::clone(&step), StepEvent::Unimplemented); - yield ScenarioEvent::Skipped; - is_success = false; - break; - } - } - } - } - - if is_success { - for step in scenario.steps.iter() { - let this = Rc::clone(&self); - - let mut step = step.clone(); - if !example.is_empty() { - step.value = example.insert_values(&step.value); - } - let step = Rc::new(step); - - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Starting); - - let result = this.run_step(Rc::clone(&step), world.take().unwrap()).await; - - match result { - TestEvent::Success(w, output) => { - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Passed(output)); - // Pass world result for current step to next step. - world = Some(w); - } - TestEvent::Failure(StepFailureKind::Panic(output, e)) => { - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Failed(StepFailureKind::Panic(output, e))); - yield ScenarioEvent::Failed(FailureKind::Panic); - is_success = false; - break; - }, - TestEvent::Failure(StepFailureKind::TimedOut) => { - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Failed(StepFailureKind::TimedOut)); - yield ScenarioEvent::Failed(FailureKind::TimedOut); - is_success = false; - break; - } - TestEvent::Skipped => { - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Skipped); - yield ScenarioEvent::Skipped; - is_success = false; - break; - } - TestEvent::Unimplemented => { - yield ScenarioEvent::Step(Rc::clone(&step), StepEvent::Unimplemented); - yield ScenarioEvent::Skipped; - is_success = false; - break; - } - } - } - } - - for (criteria, handler) in self.after.iter() { - if !criteria.context().is_scenario() { - continue; - } - - if criteria.eval(&*feature, rule.as_ref().map(|x| &**x), Some(&*scenario)) { - (handler)(context.clone()).await; - } - } - - if is_success { - yield ScenarioEvent::Passed; - } - }) - } - - pub fn run(self: Rc) -> CucumberStream { - Box::pin(stream! { - let mut stats = StatsCollector::new(); - yield CucumberEvent::Starting; - - let features = self.features.iter().cloned().map(Rc::new).collect::>(); - for feature in features.into_iter() { - let this = Rc::clone(&self); - let mut stream = this.run_feature(Rc::clone(&feature)); - - while let Some(event) = stream.next().await { - stats.handle_feature_event(&event); - yield CucumberEvent::Feature(Rc::clone(&feature), event); - } - } - - yield CucumberEvent::Finished(stats.collect()); - }) - } -} - -type CucumberStream = Pin>>; -type FeatureStream = Pin>>; -type RuleStream = Pin>>; -type ScenarioStream = Pin>>; diff --git a/src/runner/basic.rs b/src/runner/basic.rs new file mode 100644 index 00000000..54432724 --- /dev/null +++ b/src/runner/basic.rs @@ -0,0 +1,825 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Default [`Runner`] implementation. + +use std::{ + cmp, + collections::HashMap, + fmt, mem, + panic::{self, AssertUnwindSafe}, + path::PathBuf, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, + }, +}; + +use futures::{ + channel::mpsc, + future::{self, Either}, + lock::Mutex, + pin_mut, + stream::{self, LocalBoxStream}, + FutureExt as _, Stream, StreamExt as _, TryFutureExt as _, + TryStreamExt as _, +}; +use itertools::Itertools as _; +use regex::Regex; + +use crate::{ + event::{self, Info}, + feature::Ext as _, + parser, step, Runner, Step, World, +}; + +/// Type determining whether [`Scenario`]s should run concurrently or +/// sequentially. +/// +/// [`Scenario`]: gherkin::Scenario +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum ScenarioType { + /// Run [`Scenario`]s sequentially (one-by-one). + /// + /// [`Scenario`]: gherkin::Scenario + Serial, + + /// Run [`Scenario`]s concurrently. + /// + /// [`Scenario`]: gherkin::Scenario + Concurrent, +} + +/// Default [`Runner`] implementation which follows [_order guarantees_][1] from +/// the [`Runner`] trait docs. +/// +/// Executes [`Scenario`]s concurrently based on the custom function, which +/// returns [`ScenarioType`]. Also, can limit maximum number of concurrent +/// [`Scenario`]s. +/// +/// [1]: Runner#order-guarantees +/// [`Scenario`]: gherkin::Scenario +pub struct Basic { + /// Optional number of concurrently executed [`Scenario`]s. + /// + /// [`Scenario`]: gherkin::Scenario + max_concurrent_scenarios: Option, + + /// [`Collection`] of functions to match [`Step`]s. + /// + /// [`Collection`]: step::Collection + steps: step::Collection, + + /// Function determining whether a [`Scenario`] is [`Concurrent`] or + /// a [`Serial`] one. + /// + /// [`Concurrent`]: ScenarioType::Concurrent + /// [`Serial`]: ScenarioType::Serial + /// [`Scenario`]: gherkin::Scenario + which_scenario: F, +} + +/// Alias for [`fn`] used to determine whether a [`Scenario`] is [`Concurrent`] +/// or a [`Serial`] one. +/// +/// [`Concurrent`]: ScenarioType::Concurrent +/// [`Serial`]: ScenarioType::Serial +/// [`Scenario`]: gherkin::Scenario +pub type WhichScenarioFn = fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, +) -> ScenarioType; + +// Implemented manually to omit redundant trait bounds on `World` and to omit +// outputting `F`. +impl fmt::Debug for Basic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Basic") + .field("max_concurrent_scenarios", &self.max_concurrent_scenarios) + .field("steps", &self.steps) + .finish_non_exhaustive() + } +} + +impl Basic { + /// Creates a new empty [`Runner`]. + #[must_use] + pub fn custom() -> Self { + Self { + max_concurrent_scenarios: None, + steps: step::Collection::new(), + which_scenario: (), + } + } +} + +impl Basic { + /// If `max` is [`Some`], then number of concurrently executed [`Scenario`]s + /// will be limited. + /// + /// [`Scenario`]: gherkin::Scenario + #[must_use] + pub fn max_concurrent_scenarios( + mut self, + max: impl Into>, + ) -> Self { + self.max_concurrent_scenarios = max.into(); + self + } + + /// Function determining whether a [`Scenario`] is [`Concurrent`] or + /// a [`Serial`] one. + /// + /// [`Concurrent`]: ScenarioType::Concurrent + /// [`Serial`]: ScenarioType::Serial + /// [`Scenario`]: gherkin::Scenario + #[must_use] + pub fn which_scenario(self, func: Which) -> Basic + where + Which: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> ScenarioType + + 'static, + { + let Self { + max_concurrent_scenarios, + steps, + .. + } = self; + Basic { + max_concurrent_scenarios, + steps, + which_scenario: func, + } + } + + /// Sets the given [`Collection`] of [`Step`]s to this [`Runner`]. + /// + /// [`Collection`]: step::Collection + #[must_use] + pub fn steps(mut self, steps: step::Collection) -> Self { + self.steps = steps; + self + } + + /// Adds a [Given] [`Step`] matching the given `regex`. + /// + /// [Given]: https://cucumber.io/docs/gherkin/reference/#given + #[must_use] + pub fn given(mut self, regex: Regex, step: Step) -> Self { + self.steps = mem::take(&mut self.steps).given(regex, step); + self + } + + /// Adds a [When] [`Step`] matching the given `regex`. + /// + /// [When]: https://cucumber.io/docs/gherkin/reference/#given + #[must_use] + pub fn when(mut self, regex: Regex, step: Step) -> Self { + self.steps = mem::take(&mut self.steps).when(regex, step); + self + } + + /// Adds a [Then] [`Step`] matching the given `regex`. + /// + /// [Then]: https://cucumber.io/docs/gherkin/reference/#then + #[must_use] + pub fn then(mut self, regex: Regex, step: Step) -> Self { + self.steps = mem::take(&mut self.steps).then(regex, step); + self + } +} + +impl Runner for Basic +where + W: World, + Which: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> ScenarioType + + 'static, +{ + type EventStream = + LocalBoxStream<'static, parser::Result>>; + + fn run(self, features: S) -> Self::EventStream + where + S: Stream> + 'static, + { + let Self { + max_concurrent_scenarios, + steps, + which_scenario, + } = self; + + let buffer = Features::default(); + let (sender, receiver) = mpsc::unbounded(); + + let insert = insert_features( + buffer.clone(), + features, + which_scenario, + sender.clone(), + ); + let execute = execute(buffer, max_concurrent_scenarios, steps, sender); + + stream::select( + receiver.map(Either::Left), + future::join(insert, execute) + .into_stream() + .map(Either::Right), + ) + .filter_map(|r| async { + match r { + Either::Left(ev) => Some(ev), + Either::Right(_) => None, + } + }) + .boxed_local() + } +} + +/// Stores [`Feature`]s for later use by [`execute()`]. +/// +/// [`Feature`]: gherkin::Feature +async fn insert_features( + into: Features, + features: S, + which_scenario: F, + sender: mpsc::UnboundedSender>>, +) where + S: Stream> + 'static, + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> ScenarioType + + 'static, +{ + pin_mut!(features); + while let Some(feat) = features.next().await { + match feat { + Ok(f) => into.insert(f, &which_scenario).await, + Err(e) => sender.unbounded_send(Err(e)).unwrap(), + } + } + + into.finish(); +} + +/// Retrieves [`Feature`]s and executes them. +/// +/// [`Feature`]: gherkin::Feature +async fn execute( + features: Features, + max_concurrent_scenarios: Option, + collection: step::Collection, + sender: mpsc::UnboundedSender>>, +) { + // Those panic hook shenanigans are done to avoid console messages like + // "thread 'main' panicked at ..." + // + // 1. We obtain the current panic hook and replace it with an empty one. + // 2. We run tests, which can panic. In that case we pass all panic info + // down the line to the Writer, which will print it at a right time. + // 3. We return original panic hook, because suppressing all panics doesn't + // sound like a very good idea. + let hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + + let mut executor = Executor::new(collection, sender); + + executor.send(event::Cucumber::Started); + + loop { + let runnable = features.get(max_concurrent_scenarios).await; + if runnable.is_empty() { + if features.is_finished() { + break; + } + continue; + } + + let started = executor.start_scenarios(&runnable); + executor.send_all(started); + + drop( + runnable + .into_iter() + .map(|(f, r, s)| executor.run_scenario(f, r, s)) + .collect::>() + .await, + ); + + executor.cleanup_finished_rules_and_features(); + } + + executor.send(event::Cucumber::Finished); + + panic::set_hook(hook); +} + +/// Stores currently ran [`Feature`]s and notifies about their state of +/// completion. +/// +/// [`Feature`]: gherkin::Feature. +struct Executor { + /// Number of finished [`Scenario`]s of [`Feature`]. + /// + /// [`Feature`]: gherkin::Feature + /// [`Scenario`]: gherkin::Scenario + features_scenarios_count: HashMap, AtomicUsize>, + + /// Number of finished [`Scenario`]s of [`Rule`]. + /// + /// We also store path to `.feature` file so [`Rule`]s with same names and + /// spans in different files will have different hashes. + /// + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + rule_scenarios_count: + HashMap<(Option, Arc), AtomicUsize>, + + /// [`Step`]s [`Collection`]. + /// + /// [`Collection`]: step::Collection + /// [`Step`]: step::Step + collection: step::Collection, + + /// Sender for notifying state of [`Feature`]s completion. + /// + /// [`Feature`]: gherkin::Feature + sender: mpsc::UnboundedSender>>, +} + +impl Executor { + /// Creates a new [`Executor`]. + fn new( + collection: step::Collection, + sender: mpsc::UnboundedSender>>, + ) -> Self { + Self { + features_scenarios_count: HashMap::new(), + rule_scenarios_count: HashMap::new(), + collection, + sender, + } + } + + /// Runs a [`Scenario`]. + /// + /// # Events + /// + /// - Emits all [`Scenario`] events. + /// - If [`Scenario`] was last for particular [`Rule`] or [`Feature`], also + /// emits finishing events for them. + /// + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + async fn run_scenario( + &self, + feature: Arc, + rule: Option>, + scenario: Arc, + ) { + self.send(event::Cucumber::scenario( + feature.clone(), + rule.clone(), + scenario.clone(), + event::Scenario::Started, + )); + + let ok = |e: fn(Arc) -> event::Scenario| { + let (f, r, s) = (&feature, &rule, &scenario); + move |step| { + let (f, r, s) = (f.clone(), r.clone(), s.clone()); + event::Cucumber::scenario(f, r, s, e(step)) + } + }; + let err = |e: fn(Arc, _, Info) -> event::Scenario| { + let (f, r, s) = (&feature, &rule, &scenario); + move |step, world, info| { + let (f, r, s) = (f.clone(), r.clone(), s.clone()); + event::Cucumber::scenario(f, r, s, e(step, world, info)) + } + }; + + let res = async { + let feature_background = feature + .background + .as_ref() + .map(|b| b.steps.iter().map(|s| Arc::new(s.clone()))) + .into_iter() + .flatten(); + + let feature_background = stream::iter(feature_background) + .map(Ok) + .try_fold(None, |world, bg_step| { + self.run_step( + world, + bg_step, + ok(event::Scenario::background_step_started), + ok(event::Scenario::background_step_passed), + ok(event::Scenario::background_step_skipped), + err(event::Scenario::background_step_failed), + ) + .map_ok(Some) + }) + .await?; + + let rule_background = rule + .as_ref() + .map(|rule| { + rule.background + .as_ref() + .map(|b| b.steps.iter().map(|s| Arc::new(s.clone()))) + .into_iter() + .flatten() + }) + .into_iter() + .flatten(); + + let rule_background = stream::iter(rule_background) + .map(Ok) + .try_fold(feature_background, |world, bg_step| { + self.run_step( + world, + bg_step, + ok(event::Scenario::background_step_started), + ok(event::Scenario::background_step_passed), + ok(event::Scenario::background_step_skipped), + err(event::Scenario::background_step_failed), + ) + .map_ok(Some) + }) + .await?; + + stream::iter(scenario.steps.iter().map(|s| Arc::new(s.clone()))) + .map(Ok) + .try_fold(rule_background, |world, step| { + self.run_step( + world, + step, + ok(event::Scenario::step_started), + ok(event::Scenario::step_passed), + ok(event::Scenario::step_skipped), + err(event::Scenario::step_failed), + ) + .map_ok(Some) + }) + .await + }; + + drop(res.await); + + self.send(event::Cucumber::scenario( + feature.clone(), + rule.clone(), + scenario.clone(), + event::Scenario::Finished, + )); + + if let Some(rule) = rule { + if let Some(finished) = + self.rule_scenario_finished(feature.clone(), rule) + { + self.send(finished); + } + } + + if let Some(finished) = self.feature_scenario_finished(feature) { + self.send(finished); + } + } + + /// Runs a [`Step`]. + /// + /// # Events + /// + /// - Emits all [`Step`] events. + /// + /// [`Step`]: gherkin::Step + async fn run_step( + &self, + mut world: Option, + step: Arc, + started: impl FnOnce(Arc) -> event::Cucumber, + passed: impl FnOnce(Arc) -> event::Cucumber, + skipped: impl FnOnce(Arc) -> event::Cucumber, + failed: impl FnOnce( + Arc, + Option, + Info, + ) -> event::Cucumber, + ) -> Result { + self.send(started(step.clone())); + + let run = async { + if world.is_none() { + world = + Some(W::new().await.expect("failed to initialize World")); + } + + let (step_fn, ctx) = self.collection.find(&step)?; + step_fn(world.as_mut().unwrap(), ctx).await; + Some(()) + }; + + let res = match AssertUnwindSafe(run).catch_unwind().await { + Ok(Some(())) => { + self.send(passed(step)); + Ok(world.unwrap()) + } + Ok(None) => { + self.send(skipped(step)); + Err(()) + } + Err(err) => { + self.send(failed(step, world, err)); + Err(()) + } + }; + + res + } + + /// Marks [`Rule`]'s [`Scenario`] as finished and returns [`Rule::Finished`] + /// event if no [`Scenario`]s left. + /// + /// [`Rule`]: gherkin::Rule + /// [`Rule::Finished`]: event::Rule::Finished + /// [`Scenario`]: gherkin::Scenario + fn rule_scenario_finished( + &self, + feature: Arc, + rule: Arc, + ) -> Option> { + let finished_scenarios = self + .rule_scenarios_count + .get(&(feature.path.clone(), rule.clone())) + .unwrap_or_else(|| panic!("No Rule {}", rule.name)) + .fetch_add(1, Ordering::SeqCst) + + 1; + (rule.scenarios.len() == finished_scenarios) + .then(|| event::Cucumber::rule_finished(feature, rule)) + } + + /// Marks [`Feature`]'s [`Scenario`] as finished and returns + /// [`Feature::Finished`] event if no [`Scenario`]s left. + /// + /// [`Feature`]: gherkin::Feature + /// [`Feature::Finished`]: event::Feature::Finished + /// [`Scenario`]: gherkin::Scenario + fn feature_scenario_finished( + &self, + feature: Arc, + ) -> Option> { + let finished_scenarios = self + .features_scenarios_count + .get(&feature) + .unwrap_or_else(|| panic!("No Feature {}", feature.name)) + .fetch_add(1, Ordering::SeqCst) + + 1; + let scenarios = feature.count_scenarios(); + (scenarios == finished_scenarios) + .then(|| event::Cucumber::feature_finished(feature)) + } + + /// Marks [`Scenario`]s as started and returns [`Rule::Started`] and + /// [`Feature::Started`] if given [`Scenario`] was first for particular + /// [`Rule`] or [`Feature`]. + /// + /// [`Feature`]: gherkin::Feature + /// [`Feature::Started`]: event::Feature::Started + /// [`Rule`]: gherkin::Rule + /// [`Rule::Started`]: event::Rule::Started + /// [`Scenario`]: gherkin::Scenario + fn start_scenarios( + &mut self, + runnable: impl AsRef< + [( + Arc, + Option>, + Arc, + )], + >, + ) -> impl Iterator> { + let runnable = runnable.as_ref(); + + let mut started_features = Vec::new(); + for feature in runnable.iter().map(|(f, ..)| f.clone()).dedup() { + let _ = self + .features_scenarios_count + .entry(feature.clone()) + .or_insert_with(|| { + started_features.push(feature); + 0.into() + }); + } + + let mut started_rules = Vec::new(); + for (feature, rule) in runnable + .iter() + .filter_map(|(f, r, _)| r.clone().map(|r| (f.clone(), r))) + .dedup() + { + let _ = self + .rule_scenarios_count + .entry((feature.path.clone(), rule.clone())) + .or_insert_with(|| { + started_rules.push((feature, rule)); + 0.into() + }); + } + + started_features + .into_iter() + .map(event::Cucumber::feature_started) + .chain( + started_rules + .into_iter() + .map(|(f, r)| event::Cucumber::rule_started(f, r)), + ) + } + + /// Removes all finished [`Rule`]s and [`Feature`]s as all their events are + /// emitted already. + /// + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + fn cleanup_finished_rules_and_features(&mut self) { + self.features_scenarios_count = self + .features_scenarios_count + .drain() + .filter(|(f, count)| { + f.count_scenarios() != count.load(Ordering::SeqCst) + }) + .collect(); + + self.rule_scenarios_count = self + .rule_scenarios_count + .drain() + .filter(|((_, r), count)| { + r.scenarios.len() != count.load(Ordering::SeqCst) + }) + .collect(); + } + + /// Notifies with the given [`Cucumber`] event. + /// + /// [`Cucumber`]: event::Cucumber + fn send(&self, event: event::Cucumber) { + self.sender.unbounded_send(Ok(event)).unwrap(); + } + + /// Notifies with the given [`Cucumber`] events. + /// + /// [`Cucumber`]: event::Cucumber + fn send_all(&self, events: impl Iterator>) { + for event in events { + self.send(event); + } + } +} + +/// [`Scenario`]s storage. +/// +/// [`Scenario`]: gherkin::Scenario +type Scenarios = HashMap< + ScenarioType, + Vec<( + Arc, + Option>, + Arc, + )>, +>; + +/// Storage sorted by [`ScenarioType`] [`Feature`]'s [`Scenario`]s. +/// +/// [`Feature`]: gherkin::Feature +/// [`Scenario`]: gherkin::Scenario +#[derive(Clone, Default)] +struct Features { + /// Storage itself. + scenarios: Arc>, + + /// Indicates whether all parsed [`Feature`]s are sorted and stored. + finished: Arc, +} + +impl Features { + /// Splits [`Feature`] into [`Scenario`]s, sorts by [`ScenarioType`] and + /// stores them. + /// + /// [`Feature`]: gherkin::Feature + /// [`Scenario`]: gherkin::Scenario + async fn insert( + &self, + feature: gherkin::Feature, + which_scenario: &Which, + ) where + Which: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> ScenarioType + + 'static, + { + let f = feature.expand_examples(); + + let local = f + .scenarios + .iter() + .map(|s| (&f, None, s)) + .chain(f.rules.iter().flat_map(|r| { + r.scenarios + .iter() + .map(|s| (&f, Some(r), s)) + .collect::>() + })) + .map(|(f, r, s)| { + ( + Arc::new(f.clone()), + r.map(|r| Arc::new(r.clone())), + Arc::new(s.clone()), + ) + }) + .into_group_map_by(|(f, r, s)| { + which_scenario(f, r.as_ref().map(AsRef::as_ref), s) + }); + + let mut scenarios = self.scenarios.lock().await; + if local.get(&ScenarioType::Serial).is_none() { + // If there are no Serial Scenarios we just extending already + // existing Concurrent Scenarios. + for (which, values) in local { + scenarios.entry(which).or_default().extend(values); + } + } else { + // If there are Serial Scenarios we insert all Serial and Concurrent + // Scenarios in front. + // This is done to execute them closely to one another, so the + // output wouldn't hang on executing other Concurrent Scenarios. + for (which, mut values) in local { + let old = mem::take(scenarios.entry(which).or_default()); + values.extend(old); + scenarios.entry(which).or_default().extend(values); + } + } + } + + /// Returns [`Scenario`]s which are ready to run. + /// + /// [`Scenario`]: gherkin::Scenario + async fn get( + &self, + max_concurrent_scenarios: Option, + ) -> Vec<( + Arc, + Option>, + Arc, + )> { + let mut scenarios = self.scenarios.lock().await; + scenarios + .get_mut(&ScenarioType::Serial) + .and_then(|s| s.pop().map(|s| vec![s])) + .or_else(|| { + scenarios.get_mut(&ScenarioType::Concurrent).and_then(|s| { + (!s.is_empty()).then(|| { + let end = cmp::min( + s.len(), + max_concurrent_scenarios.unwrap_or(s.len()), + ); + s.drain(0..end).collect() + }) + }) + }) + .unwrap_or_default() + } + + /// Marks that there will be no more [`Feature`]s to execute. + /// + /// [`Feature`]: gherkin::Feature + fn finish(&self) { + self.finished.store(true, Ordering::SeqCst); + } + + /// Indicates whether there are more [`Feature`]s to execute. + /// + /// [`Feature`]: gherkin::Feature + fn is_finished(&self) -> bool { + self.finished.load(Ordering::SeqCst) + } +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs new file mode 100644 index 00000000..870acb3f --- /dev/null +++ b/src/runner/mod.rs @@ -0,0 +1,73 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Tools for executing [`Step`]s. +//! +//! [`Step`]: crate::Step +//! +//! [Gherkin]: https://cucumber.io/docs/gherkin/reference/ + +pub mod basic; + +use futures::Stream; + +use crate::{event, parser}; + +#[doc(inline)] +pub use self::basic::{Basic, ScenarioType}; + +/// Executor of [`Parser`] output producing [`Cucumber`] events for [`Writer`]. +/// +/// # Order guarantees +/// +/// Implementors are expected to source events in a [happened-before] order. For +/// example [`event::Scenario::Started`] for a single [`Scenario`] should +/// predate any other events of this [`Scenario`], while +/// [`event::Scenario::Finished`] should be the last one. [`Step`] events of +/// this [`Scenario`] should be emitted in order of declaration in `.feature` +/// file. But as [`Scenario`]s can be executed concurrently, events from one +/// [`Scenario`] can be interrupted by events of a different one (which are also +/// following the [happened-before] order). Those rules are applied also to +/// [`Rule`]s and [`Feature`]s. If you want to avoid those interruptions for +/// some [`Scenario`], it should be resolved as [`ScenarioType::Serial`] by the +/// [`Runner`]. +/// +/// All those rules are considered in a [`Basic`] reference [`Runner`] +/// implementation. +/// +/// Note, that those rules are recommended in case you are using a +/// [`writer::Normalized`]. Strictly speaking, no one is stopping you from +/// implementing [`Runner`] which sources events completely out-of-order or even +/// skips some of them. For example, this can be useful if you care only about +/// failed [`Step`]s. +/// +/// [`Cucumber`]: event::Cucumber +/// [`Feature`]: gherkin::Feature +/// [`Parser`]: crate::Parser +/// [`Rule`]: gherkin::Rule +/// [`Scenario`]: gherkin::Scenario +/// [`Step`]: gherkin::Step +/// [`Writer`]: crate::Writer +/// [`writer::Normalized`]: crate::writer::Normalized +/// +/// [happened-before]: https://en.wikipedia.org/wiki/Happened-before +pub trait Runner { + /// Output events [`Stream`]. + type EventStream: Stream>>; + + /// Executes the given [`Stream`] of [`Feature`]s transforming it into + /// a [`Stream`] of executed [`Cucumber`] events. + /// + /// [`Cucumber`]: event::Cucumber + /// [`Feature`]: gherkin::Feature + fn run(self, features: S) -> Self::EventStream + where + S: Stream> + 'static; +} diff --git a/src/step.rs b/src/step.rs new file mode 100644 index 00000000..a1c64de9 --- /dev/null +++ b/src/step.rs @@ -0,0 +1,194 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Definitions for a [`Collection`] which is used to store [`Step`] [`Fn`]s and +//! corresponding [`Regex`] patterns. +//! +//! [`Step`]: gherkin::Step + +use std::{ + collections::HashMap, + fmt, + hash::{Hash, Hasher}, + ops::Deref, +}; + +use futures::future::LocalBoxFuture; +use gherkin::StepType; +use regex::Regex; + +/// Alias for a [`gherkin::Step`] function that returns a [`LocalBoxFuture`]. +pub type Step = + for<'a> fn(&'a mut World, Context) -> LocalBoxFuture<'a, ()>; + +/// Collection of [`Step`]s. +/// +/// Every [`Step`] should be matched by exactly 1 [`Regex`]. Otherwise there are +/// no guarantees that [`Step`]s will be matched deterministically from run to +/// run. +pub struct Collection { + given: HashMap>, + when: HashMap>, + then: HashMap>, +} + +impl fmt::Debug for Collection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Collection") + .field( + "given", + &self + .given + .iter() + .map(|(re, step)| (re, format!("{:p}", step))) + .collect::>(), + ) + .field( + "when", + &self + .when + .iter() + .map(|(re, step)| (re, format!("{:p}", step))) + .collect::>(), + ) + .field( + "then", + &self + .then + .iter() + .map(|(re, step)| (re, format!("{:p}", step))) + .collect::>(), + ) + .finish() + } +} + +impl Default for Collection { + fn default() -> Self { + Self { + given: HashMap::new(), + when: HashMap::new(), + then: HashMap::new(), + } + } +} + +impl Collection { + /// Creates a new empty [`Collection`]. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Adds a [Given] [`Step`] matching the given `regex`. + /// + /// [Given]: https://cucumber.io/docs/gherkin/reference/#given + #[must_use] + pub fn given(mut self, regex: Regex, step: Step) -> Self { + let _ = self.given.insert(regex.into(), step); + self + } + + /// Adds a [When] [`Step`] matching the given `regex`. + /// + /// [When]: https://cucumber.io/docs/gherkin/reference/#when + #[must_use] + pub fn when(mut self, regex: Regex, step: Step) -> Self { + let _ = self.when.insert(regex.into(), step); + self + } + + /// Adds a [Then] [`Step`] matching the given `regex`. + /// + /// [Then]: https://cucumber.io/docs/gherkin/reference/#then + #[must_use] + pub fn then(mut self, regex: Regex, step: Step) -> Self { + let _ = self.then.insert(regex.into(), step); + self + } + + /// Returns a [`Step`] function matching the given [`gherkin::Step`], + /// if any. + #[must_use] + pub fn find( + &self, + step: &gherkin::Step, + ) -> Option<(&Step, Context)> { + let collection = match step.ty { + StepType::Given => &self.given, + StepType::When => &self.when, + StepType::Then => &self.then, + }; + + let (captures, step_fn) = + collection.iter().find_map(|(re, step_fn)| { + re.captures(&step.value).map(|c| (c, step_fn)) + })?; + + let matches = captures + .iter() + .map(|c| c.map(|c| c.as_str().to_owned()).unwrap_or_default()) + .collect(); + + Some(( + step_fn, + Context { + step: step.clone(), + matches, + }, + )) + } +} + +/// Context for a [`Step`] function execution. +#[derive(Debug)] +pub struct Context { + /// [`Step`] matched to a [`Step`] function. + /// + /// [`Step`]: gherkin::Step + pub step: gherkin::Step, + + /// [`Regex`] matches of a [`Step::value`]. + /// + /// [`Step::value`]: gherkin::Step::value + pub matches: Vec, +} + +/// [`Regex`] wrapper to store inside a [`LinkedHashMap`]. +#[derive(Clone, Debug)] +struct HashableRegex(Regex); + +impl From for HashableRegex { + fn from(re: Regex) -> Self { + Self(re) + } +} + +impl Hash for HashableRegex { + fn hash(&self, state: &mut H) { + self.0.as_str().hash(state); + } +} + +impl PartialEq for HashableRegex { + fn eq(&self, other: &HashableRegex) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Eq for HashableRegex {} + +impl Deref for HashableRegex { + type Target = Regex; + + fn deref(&self) -> &Regex { + &self.0 + } +} diff --git a/src/steps.rs b/src/steps.rs deleted file mode 100644 index bdb8308f..00000000 --- a/src/steps.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) 2018-2021 Brendan Molloy -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use cute_custom_default::CustomDefault; -use gherkin::StepType; - -use crate::runner::StepFn; -use crate::{collection::StepsCollection, runner::TestFuture}; -use crate::{cucumber::StepContext, World}; - -#[derive(CustomDefault)] -pub struct Steps { - pub(crate) steps: StepsCollection, -} - -impl Steps { - pub fn new() -> Steps { - Steps { - steps: StepsCollection::default(), - } - } - - fn insert_async(&mut self, ty: StepType, name: &'static str, test_fn: StepFn) -> &mut Self { - self.steps.insert_basic(ty, name, test_fn.into()); - self - } - - fn insert_sync( - &mut self, - ty: StepType, - name: &'static str, - test_fn: fn(W, StepContext) -> W, - ) -> &mut Self { - self.steps.insert_basic(ty, name, test_fn.into()); - self - } - - fn insert_regex_async( - &mut self, - ty: StepType, - name: &'static str, - test_fn: StepFn, - ) -> &mut Self { - let regex = regex::Regex::new(name) - .unwrap_or_else(|_| panic!("`{}` is not a valid regular expression", name)); - self.steps.insert_regex(ty, regex, test_fn); - self - } - - fn insert_regex_sync( - &mut self, - ty: StepType, - name: &'static str, - test_fn: fn(W, StepContext) -> W, - ) -> &mut Self { - let regex = regex::Regex::new(name) - .unwrap_or_else(|_| panic!("`{}` is not a valid regular expression", name)); - self.steps.insert_regex(ty, regex, test_fn.into()); - self - } - - pub fn given_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_async(StepType::Given, name, test_fn.into()) - } - - pub fn when_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_async(StepType::When, name, test_fn.into()) - } - - pub fn then_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_async(StepType::Then, name, test_fn.into()) - } - - pub fn given(&mut self, name: &'static str, test_fn: fn(W, StepContext) -> W) -> &mut Self { - self.insert_sync(StepType::Given, name, test_fn) - } - - pub fn when(&mut self, name: &'static str, test_fn: fn(W, StepContext) -> W) -> &mut Self { - self.insert_sync(StepType::When, name, test_fn) - } - - pub fn then(&mut self, name: &'static str, test_fn: fn(W, StepContext) -> W) -> &mut Self { - self.insert_sync(StepType::Then, name, test_fn) - } - - pub fn given_regex_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_regex_async(StepType::Given, name, test_fn.into()) - } - - pub fn when_regex_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_regex_async(StepType::When, name, test_fn.into()) - } - - pub fn then_regex_async( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> TestFuture, - ) -> &mut Self { - self.insert_regex_async(StepType::Then, name, test_fn.into()) - } - - pub fn given_regex( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> W, - ) -> &mut Self { - self.insert_regex_sync(StepType::Given, name, test_fn) - } - - pub fn when_regex( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> W, - ) -> &mut Self { - self.insert_regex_sync(StepType::When, name, test_fn) - } - - pub fn then_regex( - &mut self, - name: &'static str, - test_fn: fn(W, StepContext) -> W, - ) -> &mut Self { - self.insert_regex_sync(StepType::Then, name, test_fn) - } - - pub(crate) fn append(&mut self, other: Steps) { - self.steps.append(other.steps); - } -} diff --git a/src/writer/basic.rs b/src/writer/basic.rs new file mode 100644 index 00000000..2932ae2d --- /dev/null +++ b/src/writer/basic.rs @@ -0,0 +1,458 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Default [`Writer`] implementation. + +use std::{fmt::Debug, ops::Deref}; + +use async_trait::async_trait; +use console::Term; +use itertools::Itertools as _; + +use crate::{ + event::{self, Info}, + parser, + writer::term::Styles, + ArbitraryWriter, World, Writer, +}; + +/// Default [`Writer`] implementation outputting to [`Term`]inal (STDOUT by +/// default). +/// +/// Pretty-prints with colors if terminal was successfully detected, otherwise +/// has simple output. Useful for running tests with CI tools. +#[derive(Debug)] +pub struct Basic { + /// Terminal to write the output into. + terminal: Term, + + /// [`Styles`] for terminal output. + styles: Styles, +} + +#[async_trait(?Send)] +impl Writer for Basic { + #[allow(clippy::unused_async)] + async fn handle_event(&mut self, ev: parser::Result>) { + use event::{Cucumber, Feature, Rule}; + + match ev { + Err(err) => self.parsing_failed(&err), + Ok(Cucumber::Started | Cucumber::Finished) => {} + Ok(Cucumber::Feature(f, ev)) => match ev { + Feature::Started => self.feature_started(&f), + Feature::Scenario(sc, ev) => { + self.scenario(&f, &sc, &ev, 0); + } + Feature::Rule(r, ev) => match ev { + Rule::Started => { + self.rule_started(&r); + } + Rule::Scenario(sc, ev) => { + self.scenario(&f, &sc, &ev, 2); + } + Rule::Finished => {} + }, + Feature::Finished => {} + }, + } + } +} + +#[async_trait(?Send)] +impl<'val, W, Val> ArbitraryWriter<'val, W, Val> for Basic +where + W: World + Debug, + Val: AsRef + 'val, +{ + #[allow(clippy::unused_async)] + async fn write(&mut self, val: Val) + where + 'val: 'async_trait, + { + self.write_line(val.as_ref()).unwrap(); + } +} + +impl Default for Basic { + fn default() -> Self { + Self { + terminal: Term::stdout(), + styles: Styles::new(), + } + } +} + +impl Deref for Basic { + type Target = Term; + + fn deref(&self) -> &Self::Target { + &self.terminal + } +} + +impl Basic { + /// Creates a new [`Basic`] [`Writer`]. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Clears last `n` lines if terminal is present. + fn clear_last_lines_if_term_present(&self, n: usize) { + if self.styles.is_present { + self.clear_last_lines(n).unwrap(); + } + } + + /// Outputs [error] encountered while parsing some [`Feature`]. + /// + /// [error]: event::Cucumber::ParsingError + /// [`Feature`]: gherkin::Feature + fn parsing_failed(&self, err: &gherkin::ParseFileError) { + self.write_line(&self.styles.err(format!("Failed to parse: {}", err))) + .unwrap(); + } + + /// Outputs [started] [`Feature`] to STDOUT. + /// + /// [started]: event::Feature::Started + /// [`Feature`]: [`gherkin::Feature`] + fn feature_started(&self, feature: &gherkin::Feature) { + self.write_line(&self.styles.ok(format!("Feature: {}", feature.name))) + .unwrap(); + } + + /// Outputs [started] [`Rule`] to STDOUT. + /// + /// [started]: event::Rule::Started + /// [`Rule`]: [`gherkin::Rule`] + fn rule_started(&self, rule: &gherkin::Rule) { + self.write_line(&self.styles.ok(format!(" Rule: {}", rule.name))) + .unwrap(); + } + + /// Outputs [`Scenario`] [started]/[background]/[step] event to STDOUT. + /// + /// [background]: event::Background + /// [started]: event::Scenario::Started + /// [step]: event::Step + fn scenario( + &self, + feat: &gherkin::Feature, + scenario: &gherkin::Scenario, + ev: &event::Scenario, + ident: usize, + ) { + use event::Scenario; + + let offset = ident + 2; + match ev { + Scenario::Started => { + self.scenario_started(scenario, offset); + } + Scenario::Background(bg, ev) => { + self.background(feat, bg, ev, offset); + } + Scenario::Step(st, ev) => { + self.step(feat, st, ev, offset); + } + Scenario::Finished => {} + } + } + + /// Outputs [started] [`Scenario`] to STDOUT. + /// + /// [started]: event::Scenario::Started + /// [`Scenario`]: [`gherkin::Scenario`] + fn scenario_started(&self, scenario: &gherkin::Scenario, ident: usize) { + self.write_line(&self.styles.ok(format!( + "{}Scenario: {}", + " ".repeat(ident), + scenario.name, + ))) + .unwrap(); + } + + /// Outputs [`Step`] [started]/[passed]/[skipped]/[failed] event to STDOUT. + /// + /// [failed]: event::Step::Failed + /// [passed]: event::Step::Passed + /// [skipped]: event::Step::Skipped + /// [started]: event::Step::Started + /// [`Step`]: [`gherkin::Step`] + fn step( + &self, + feat: &gherkin::Feature, + step: &gherkin::Step, + ev: &event::Step, + ident: usize, + ) { + use event::Step; + + let offset = ident + 4; + match ev { + Step::Started => { + self.step_started(step, offset); + } + Step::Passed => { + self.step_passed(step, offset); + } + Step::Skipped => { + self.step_skipped(step, offset); + } + Step::Failed(world, info) => { + self.step_failed(feat, step, world.as_ref(), info, offset); + } + } + } + + /// Outputs [started] [`Step`] to STDOUT. + /// + /// This [`Step`] is printed only if terminal is present and gets + /// overwritten by later [passed]/[skipped]/[failed] events. + /// + /// [started]: event::Step::Started + /// [passed]: event::Step::Passed + /// [skipped]: event::Step::Skipped + /// [started]: event::Step::Started + /// [`Step`]: [`gherkin::Step`] + fn step_started(&self, step: &gherkin::Step, ident: usize) { + if self.styles.is_present { + self.write_line(&format!( + "{}{} {}", + " ".repeat(ident), + step.keyword, + step.value, + )) + .unwrap(); + } + } + + /// Outputs [passed] [`Step`] to STDOUT. + /// + /// [passed]: event::Step::Passed + /// [`Step`]: [`gherkin::Step`] + fn step_passed(&self, step: &gherkin::Step, ident: usize) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.ok(format!( + // ✔ + "{}\u{2714} {} {}", + " ".repeat(ident - 3), + step.keyword, + step.value, + ))) + .unwrap(); + } + + /// Outputs [skipped] [`Step`] to STDOUT. + /// + /// [skipped]: event::Step::Skipped + /// [`Step`]: [`gherkin::Step`] + fn step_skipped(&self, step: &gherkin::Step, ident: usize) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.skipped(format!( + "{}? {} {} (skipped)", + " ".repeat(ident - 3), + step.keyword, + step.value, + ))) + .unwrap(); + } + + /// Outputs [failed] [`Step`] to STDOUT. + /// + /// [failed]: event::Step::Failed + /// [`Step`]: [`gherkin::Step`] + fn step_failed( + &self, + feat: &gherkin::Feature, + step: &gherkin::Step, + world: Option<&W>, + info: &Info, + ident: usize, + ) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.err(format!( + // ✘ + "{ident}\u{2718} {} {}\n\ + {ident} Step failed: {}:{}:{}\n\ + {ident} Captured output: {}\ + {}", + step.keyword, + step.value, + feat.path + .as_ref() + .and_then(|p| p.to_str()) + .unwrap_or(&feat.name), + step.position.line, + step.position.col, + coerce_error(info), + format_world(world, ident), + ident = " ".repeat(ident - 3), + ))) + .unwrap(); + } + + /// Outputs [`Background`] [`Step`] [started]/[passed]/[skipped]/[failed] + /// event to STDOUT. + /// + /// [failed]: event::Step::Failed + /// [passed]: event::Step::Passed + /// [skipped]: event::Step::Skipped + /// [started]: event::Step::Started + /// [`Background`]: [`gherkin::Background`] + /// [`Step`]: [`gherkin::Step`] + fn background( + &self, + feat: &gherkin::Feature, + bg: &gherkin::Step, + ev: &event::Step, + ident: usize, + ) { + use event::Step; + + let offset = ident + 4; + match ev { + Step::Started => { + self.bg_step_started(bg, offset); + } + Step::Passed => { + self.bg_step_passed(bg, offset); + } + Step::Skipped => { + self.bg_step_skipped(bg, offset); + } + Step::Failed(world, info) => { + self.bg_step_failed(feat, bg, world.as_ref(), info, offset); + } + } + } + + /// Outputs [started] [`Background`] [`Step`] to STDOUT. + /// + /// This [`Step`] is printed only if terminal is present and gets + /// overwritten by later [passed]/[skipped]/[failed] events. + /// + /// [started]: event::Step::Started + /// [passed]: event::Step::Passed + /// [skipped]: event::Step::Skipped + /// [started]: event::Step::Started + /// [`Background`]: [`gherkin::Background`] + /// [`Step`]: [`gherkin::Step`] + fn bg_step_started(&self, step: &gherkin::Step, ident: usize) { + if self.styles.is_present { + self.write_line(&format!( + "{}{}{} {}", + " ".repeat(ident - 2), + "> ", + step.keyword, + step.value, + )) + .unwrap(); + } + } + + /// Outputs [passed] [`Background`] [`Step`] to STDOUT. + /// + /// [passed]: event::Step::Passed + /// [`Background`]: [`gherkin::Background`] + /// [`Step`]: [`gherkin::Step`] + fn bg_step_passed(&self, step: &gherkin::Step, ident: usize) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.ok(format!( + // ✔ + "{}\u{2714}> {} {}", + " ".repeat(ident - 3), + step.keyword, + step.value, + ))) + .unwrap(); + } + + /// Outputs [skipped] [`Background`] [`Step`] to STDOUT. + /// + /// [skipped]: event::Step::Skipped + /// [`Background`]: [`gherkin::Background`] + /// [`Step`]: [`gherkin::Step`] + fn bg_step_skipped(&self, step: &gherkin::Step, ident: usize) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.skipped(format!( + "{}?> {} {} (skipped)", + " ".repeat(ident - 3), + step.keyword, + step.value, + ))) + .unwrap(); + } + + /// Outputs [failed] [`Background`] [`Step`] to STDOUT. + /// + /// [failed]: event::Step::Failed + /// [`Background`]: [`gherkin::Background`] + /// [`Step`]: [`gherkin::Step`] + fn bg_step_failed( + &self, + feat: &gherkin::Feature, + step: &gherkin::Step, + world: Option<&W>, + info: &Info, + ident: usize, + ) { + self.clear_last_lines_if_term_present(1); + self.write_line(&self.styles.err(format!( + // ✘ + "{ident}\u{2718}> {} {}\n\ + {ident} Background step failed: {}:{}:{}\n\ + {ident} Captured output: {}\ + {}", + step.keyword, + step.value, + feat.path + .as_ref() + .and_then(|p| p.to_str()) + .unwrap_or(&feat.name), + step.position.line, + step.position.col, + coerce_error(info), + format_world(world, ident), + ident = " ".repeat(ident - 3), + ))) + .unwrap(); + } +} + +/// Tries to coerce [`catch_unwind()`] output to [`String`]. +/// +/// [`catch_unwind()`]: std::panic::catch_unwind() +#[must_use] +fn coerce_error(err: &Info) -> String { + if let Some(string) = err.downcast_ref::() { + string.clone() + } else if let Some(&string) = err.downcast_ref::<&str>() { + string.to_owned() + } else { + "(Could not resolve panic payload)".to_owned() + } +} + +/// Formats the given [`World`] using [`Debug`], then adds `ident`s to each line +/// to prettify the output. +fn format_world(world: Option<&W>, ident: usize) -> String { + let world = world + .map(|world| format!("{:#?}", world)) + .unwrap_or_default() + .lines() + .map(|line| format!("{}{}", " ".repeat(ident), line)) + .join("\n"); + (!world.is_empty()) + .then(|| format!("\n{}", world)) + .unwrap_or_default() +} diff --git a/src/writer/fail_on_skipped.rs b/src/writer/fail_on_skipped.rs new file mode 100644 index 00000000..8b350e75 --- /dev/null +++ b/src/writer/fail_on_skipped.rs @@ -0,0 +1,164 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! [`Writer`]-wrapper for transforming [`Skipped`] [`Step`]s into [`Failed`]. +//! +//! [`Failed`]: event::Step::Failed +//! [`Skipped`]: event::Step::Skipped +//! [`Step`]: gherkin::Step + +use std::sync::Arc; + +use async_trait::async_trait; +use derive_more::Deref; + +use crate::{event, parser, ArbitraryWriter, FailureWriter, World, Writer}; + +/// [`Writer`]-wrapper for transforming [`Skipped`] [`Step`]s into [`Failed`]. +/// +/// [`Failed`]: event::Step::Failed +/// [`Skipped`]: event::Step::Skipped +/// [`Step`]: gherkin::Step +#[derive(Debug, Deref)] +pub struct FailOnSkipped { + /// Original [`Writer`] to pass transformed event into. + #[deref] + pub writer: W, + + /// [`Fn`] to determine whether [`Skipped`] test should be considered as + /// [`Failed`] or not. + /// + /// [`Failed`]: event::Step::Failed + /// [`Skipped`]: event::Step::Skipped + should_fail: F, +} + +/// Alias for a [`fn`] used to determine whether [`Skipped`] test should be +/// considered as [`Failed`] or not. +/// +/// [`Failed`]: event::Step::Failed +/// [`Skipped`]: event::Step::Skipped +pub type SkipFn = + fn(&gherkin::Feature, Option<&gherkin::Rule>, &gherkin::Scenario) -> bool; + +#[async_trait(?Send)] +impl Writer for FailOnSkipped +where + W: World, + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool, + Wr: for<'val> ArbitraryWriter<'val, W, String>, +{ + async fn handle_event(&mut self, ev: parser::Result>) { + use event::{Cucumber, Feature, Rule, Scenario, Step}; + + let map_failed = + |f: Arc<_>, r: Option>, sc: Arc<_>, st: Arc<_>| { + let event = if (self.should_fail)(&f, r.as_deref(), &sc) { + Step::Failed(None, Box::new("not allowed to skip")) + } else { + Step::Skipped + }; + + Ok(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, + }; + + self.writer.handle_event(ev).await; + } +} + +#[async_trait(?Send)] +impl<'val, W, Wr, Val, F> ArbitraryWriter<'val, W, Val> for FailOnSkipped +where + W: World, + Self: Writer, + Wr: ArbitraryWriter<'val, W, Val>, + Val: 'val, +{ + async fn write(&mut self, val: Val) + where + 'val: 'async_trait, + { + self.writer.write(val).await; + } +} + +impl FailureWriter for FailOnSkipped +where + Wr: FailureWriter, + Self: Writer, +{ + fn failed_steps(&self) -> usize { + self.writer.failed_steps() + } + + fn parsing_errors(&self) -> usize { + self.writer.parsing_errors() + } +} + +impl From for FailOnSkipped { + fn from(writer: Writer) -> Self { + Self { + writer, + should_fail: |_, _, sc| { + !sc.tags.iter().any(|tag| tag == "allow_skipped") + }, + } + } +} + +impl FailOnSkipped { + /// Wraps the given [`Writer`] in a new [`FailOnSkipped`] one. + #[must_use] + pub fn new(writer: Writer) -> Self { + Self::from(writer) + } + + /// Wraps the given [`Writer`] in a new [`FailOnSkipped`] one with the given + /// `predicate` indicating when a [`Skipped`] [`Step`] is considered + /// [`Failed`]. + /// + /// [`Failed`]: event::Step::Failed + /// [`Skipped`]: event::Step::Skipped + /// [`Step`]: gherkin::Step + #[must_use] + pub fn with

(writer: Writer, predicate: P) -> FailOnSkipped + where + P: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool, + { + FailOnSkipped { + writer, + should_fail: predicate, + } + } +} diff --git a/src/writer/mod.rs b/src/writer/mod.rs new file mode 100644 index 00000000..54bccd2a --- /dev/null +++ b/src/writer/mod.rs @@ -0,0 +1,155 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Tools for outputting [`Cucumber`] events. +//! +//! [`Cucumber`]: crate::event::Cucumber + +pub mod basic; +pub mod fail_on_skipped; +pub mod normalized; +pub mod summarized; +pub mod term; + +use async_trait::async_trait; +use sealed::sealed; + +use crate::{event, parser, World}; + +#[doc(inline)] +pub use self::{ + basic::Basic, fail_on_skipped::FailOnSkipped, normalized::Normalized, + summarized::Summarized, +}; + +/// Writer of [`Cucumber`] events to some output. +/// +/// As [`Cucumber::run()`] returns [`Writer`], it can hold some state inside for +/// inspection after execution. See [`Summarized`] and +/// [`Cucumber::run_and_exit()`] for examples. +/// +/// [`Cucumber`]: crate::event::Cucumber +/// [`Cucumber::run()`]: crate::Cucumber::run +/// [`Cucumber::run_and_exit()`]: crate::Cucumber::run_and_exit +#[async_trait(?Send)] +pub trait Writer { + /// Handles the given [`Cucumber`] event. + /// + /// [`Cucumber`]: crate::event::Cucumber + async fn handle_event( + &mut self, + ev: parser::Result>, + ); +} + +/// [`Writer`] that also can output an arbitrary `Value` in addition to +/// regular [`Cucumber`] events. +/// +/// [`Cucumber`]: crate::event::Cucumber +#[async_trait(?Send)] +pub trait Arbitrary<'val, World, Value: 'val>: Writer { + /// Writes `val` to the [`Writer`]'s output. + async fn write(&mut self, val: Value) + where + 'val: 'async_trait; +} + +/// [`Writer`] tracking a number of [`Failed`] [`Step`]s and parsing errors. +/// +/// [`Failed`]: event::Step::Failed +/// [`Step`]: gherkin::Step +pub trait Failure: Writer { + /// Indicates whether there were failures/errors during execution. + #[must_use] + fn execution_has_failed(&self) -> bool { + self.failed_steps() > 0 || self.parsing_errors() > 0 + } + + /// Returns number of [`Failed`] [`Step`]s. + /// + /// [`Failed`]: event::Step::Failed + /// [`Step`]: gherkin::Step + #[must_use] + fn failed_steps(&self) -> usize; + + /// Returns number of parsing errors. + #[must_use] + fn parsing_errors(&self) -> usize; +} + +/// Extension of [`Writer`] allowing its normalization and summarization. +#[sealed] +pub trait Ext: Writer + Sized { + /// Wraps this [`Writer`] into a [`Normalized`] version. + /// + /// See [`Normalized`] for more information. + fn normalized(self) -> Normalized; + + /// Wraps this [`Writer`] to print a summary at the end of an output. + /// + /// See [`Summarized`] for more information. + fn summarized(self) -> Summarized; + + /// Wraps this [`Writer`] to fail on [`Skipped`] [`Step`]s if their + /// [`Scenario`] isn't marked with `@allow_skipped` tag. + /// + /// See [`FailOnSkipped`] for more information. + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Skipped`]: event::Step::Skipped + /// [`Step`]: gherkin::Step + fn fail_on_skipped(self) -> FailOnSkipped; + + /// Wraps this [`Writer`] to fail on [`Skipped`] [`Step`]s if the given + /// `with` predicate returns `true`. + /// + /// See [`FailOnSkipped`] for more information. + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Skipped`]: event::Step::Skipped + /// [`Step`]: gherkin::Step + fn fail_on_skipped_with(self, with: F) -> FailOnSkipped + where + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool; +} + +#[sealed] +impl Ext for T +where + W: World, + T: Writer + Sized, +{ + fn normalized(self) -> Normalized { + Normalized::new(self) + } + + fn summarized(self) -> Summarized { + Summarized::from(self) + } + + fn fail_on_skipped(self) -> FailOnSkipped { + FailOnSkipped::from(self) + } + + fn fail_on_skipped_with(self, f: F) -> FailOnSkipped + where + F: Fn( + &gherkin::Feature, + Option<&gherkin::Rule>, + &gherkin::Scenario, + ) -> bool, + { + FailOnSkipped::with(self, f) + } +} diff --git a/src/writer/normalized.rs b/src/writer/normalized.rs new file mode 100644 index 00000000..aee78601 --- /dev/null +++ b/src/writer/normalized.rs @@ -0,0 +1,579 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! [`Writer`]-wrapper for outputting events in a normalized readable order. + +use std::{hash::Hash, sync::Arc}; + +use async_trait::async_trait; +use derive_more::Deref; +use either::Either; +use linked_hash_map::LinkedHashMap; + +use crate::{event, parser, ArbitraryWriter, FailureWriter, World, Writer}; + +/// Wrapper for a [`Writer`] implementation for outputting events corresponding +/// to _order guarantees_ from the [`Runner`] in a normalized readable order. +/// +/// Doesn't output anything by itself, but rather is used as a combinator for +/// rearranging events and feeding them to the underlying [`Writer`]. +/// +/// If some [`Feature`]([`Rule`]/[`Scenario`]/[`Step`]) has started to be +/// written into an output, then it will be written uninterruptedly until its +/// end, even if some other [`Feature`]s have finished their execution. It makes +/// much easier to understand what is really happening in the running +/// [`Feature`] while don't impose any restrictions on the running order. +/// +/// [`Feature`]: gherkin::Feature +/// [`Rule`]: gherkin::Rule +/// [`Runner`]: crate::Runner +/// [`Scenario`]: gherkin::Scenario +/// [`Step`]: gherkin::Step +#[derive(Debug, Deref)] +pub struct Normalized { + /// Original [`Writer`] to normalize output of. + #[deref] + pub writer: Writer, + + /// Normalization queue of happened events. + queue: CucumberQueue, +} + +impl Normalized { + /// Creates a new [`Normalized`] wrapper, which will rearrange [`event`]s + /// and feed them to the given [`Writer`]. + #[must_use] + pub fn new(writer: Writer) -> Self { + Self { + writer, + queue: CucumberQueue::new(), + } + } +} + +#[async_trait(?Send)] +impl> Writer for Normalized { + async fn handle_event( + &mut self, + ev: parser::Result>, + ) { + use event::{Cucumber, Feature, Rule}; + + match ev { + res @ (Err(_) | Ok(Cucumber::Started)) => { + self.writer.handle_event(res).await; + } + Ok(Cucumber::Finished) => self.queue.finished(), + Ok(Cucumber::Feature(f, ev)) => match ev { + Feature::Started => self.queue.new_feature(f), + Feature::Scenario(s, ev) => { + self.queue.insert_scenario_event(&f, None, s, ev); + } + Feature::Finished => self.queue.feature_finished(&f), + Feature::Rule(r, ev) => match ev { + Rule::Started => self.queue.new_rule(&f, r), + Rule::Scenario(s, ev) => { + self.queue.insert_scenario_event(&f, Some(r), s, ev); + } + Rule::Finished => self.queue.rule_finished(&f, r), + }, + }, + } + + while let Some(feature_to_remove) = + self.queue.emit((), &mut self.writer).await + { + self.queue.remove(&feature_to_remove); + } + + if self.queue.is_finished() { + self.writer.handle_event(Ok(Cucumber::Finished)).await; + } + } +} + +#[async_trait(?Send)] +impl<'val, W, Wr, Val> ArbitraryWriter<'val, W, Val> for Normalized +where + Wr: ArbitraryWriter<'val, W, Val>, + Val: 'val, +{ + async fn write(&mut self, val: Val) + where + 'val: 'async_trait, + { + self.writer.write(val).await; + } +} + +impl FailureWriter for Normalized +where + Wr: FailureWriter, + Self: Writer, +{ + fn failed_steps(&self) -> usize { + self.writer.failed_steps() + } + + fn parsing_errors(&self) -> usize { + self.writer.parsing_errors() + } +} + +/// Normalization queue for incoming events. +/// +/// We use [`LinkedHashMap`] everywhere throughout this module to ensure FIFO +/// queue for our events. This means by calling [`next()`] we reliably get the +/// currently outputting item's events. We're doing that until it yields an +/// event that corresponds to the item being finished, after which we remove the +/// current item, as all its events have been printed out and we should do it +/// all over again with a [`next()`] item. +/// +/// [`next()`]: std::iter::Iterator::next() +#[derive(Debug)] +struct Queue { + started_emitted: bool, + queue: LinkedHashMap, + finished: bool, +} + +impl Queue { + /// Creates a new empty normalization [`Queue`]. + fn new() -> Self { + Self { + started_emitted: false, + queue: LinkedHashMap::new(), + finished: false, + } + } + + /// Marks that [`Queue`]'s started event has been emitted. + fn started_emitted(&mut self) { + self.started_emitted = true; + } + + /// Checks whether [`Queue`]'s started event has been emitted. + fn is_started_emitted(&self) -> bool { + self.started_emitted + } + + /// Marks this [`Queue`] as finished. + fn finished(&mut self) { + self.finished = true; + } + + /// Checks whether this [`Queue`] has been finished. + fn is_finished(&self) -> bool { + self.finished + } + + /// Removes the given `key` from this [`Queue`]. + fn remove(&mut self, key: &K) { + drop(self.queue.remove(key)); + } +} + +/// [`Queue`] which can remember its current item ([`Feature`], [`Rule`], +/// [`Scenario`] or [`Step`]) and pass events connected to it to the provided +/// [`Writer`]. +/// +/// [`Feature`]: gherkin::Feature +/// [`Rule`]: gherkin::Rule +/// [`Scenario`]: gherkin::Scenario +/// [`Step`]: gherkin::Step +#[async_trait(?Send)] +trait Emitter { + /// Currently outputted key and value from this [`Queue`]. + type Current; + + /// Currently outputted item ([`Feature`], [`Rule`], [`Scenario`] or + /// [`Step`]). If returned from [`Self::emit()`], means that all events + /// associated with that item were passed to the underlying [`Writer`], so + /// should be removed from the [`Queue`]. + /// + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + type Emitted; + + /// Path to the [`Self::Emitted`] item. For [`Feature`] its `()`, as it's + /// top-level item. For [`Scenario`] it's + /// `(`[`Feature`]`, `[`Option`]`<`[`Rule`]`>)`, because [`Scenario`] + /// definitely has parent [`Feature`] and optionally can have parent + /// [`Rule`]. + /// + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + type EmittedPath; + + /// Currently outputted key and value from this [`Queue`]. + fn current_item(self) -> Option; + + /// Passes events of the current item ([`Feature`], [`Rule`], [`Scenario`] + /// or [`Step`]) to the provided [`Writer`]. + /// + /// If this method returns [`Some`], then all events of the current item + /// were passed to the provided [`Writer`] and that means it should be + /// [`remove`]d. + /// + /// [`remove`]: Queue::remove() + /// [`Feature`]: gherkin::Feature + /// [`Rule`]: gherkin::Rule + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + async fn emit>( + self, + path: Self::EmittedPath, + writer: &mut W, + ) -> Option; +} + +/// [`Queue`] of all incoming events. +type CucumberQueue = Queue, FeatureQueue>; + +impl CucumberQueue { + /// Inserts a new [`Feature`] on [`event::Feature::Started`]. + /// + /// [`Feature`]: gherkin::Feature + fn new_feature(&mut self, feat: Arc) { + drop(self.queue.insert(feat, FeatureQueue::new())); + } + + /// Marks a [`Feature`] as finished on [`event::Feature::Finished`]. + /// + /// We don't emit it by the way, as there may be other in-progress + /// [`Feature`]s holding the output. + /// + /// [`Feature`]: gherkin::Feature + fn feature_finished(&mut self, feat: &gherkin::Feature) { + self.queue + .get_mut(feat) + .unwrap_or_else(|| panic!("No Feature {}", feat.name)) + .finished(); + } + + /// Inserts a new [`Rule`] on [`event::Rule::Started`]. + /// + /// [`Rule`]: gherkin::Feature + fn new_rule(&mut self, feat: &gherkin::Feature, rule: Arc) { + self.queue + .get_mut(feat) + .unwrap_or_else(|| panic!("No Feature {}", feat.name)) + .new_rule(rule); + } + + /// Marks a [`Rule`] as finished on [`event::Rule::Finished`]. + /// + /// We don't emit it by the way, as there may be other in-progress [`Rule`]s + /// holding the output. + /// + /// [`Rule`]: gherkin::Feature + fn rule_finished( + &mut self, + feat: &gherkin::Feature, + rule: Arc, + ) { + self.queue + .get_mut(feat) + .unwrap_or_else(|| panic!("No Feature {}", feat.name)) + .rule_finished(rule); + } + + /// Inserts a new [`event::Scenario::Started`]. + fn insert_scenario_event( + &mut self, + feat: &gherkin::Feature, + rule: Option>, + scenario: Arc, + event: event::Scenario, + ) { + self.queue + .get_mut(feat) + .unwrap_or_else(|| panic!("No Feature {}", feat.name)) + .insert_scenario_event(rule, scenario, event); + } +} + +#[async_trait(?Send)] +impl<'me, World> Emitter for &'me mut CucumberQueue { + type Current = (Arc, &'me mut FeatureQueue); + type Emitted = Arc; + type EmittedPath = (); + + fn current_item(self) -> Option { + self.queue.iter_mut().next().map(|(f, ev)| (f.clone(), ev)) + } + + async fn emit>( + self, + _: (), + writer: &mut W, + ) -> Option { + if let Some((f, events)) = self.current_item() { + if !events.is_started_emitted() { + writer + .handle_event(Ok(event::Cucumber::feature_started( + f.clone(), + ))) + .await; + events.started_emitted(); + } + + while let Some(scenario_or_rule_to_remove) = + events.emit(f.clone(), writer).await + { + events.remove(&scenario_or_rule_to_remove); + } + + if events.is_finished() { + writer + .handle_event(Ok(event::Cucumber::feature_finished( + f.clone(), + ))) + .await; + return Some(f.clone()); + } + } + None + } +} + +/// Either a [`Rule`] or a [`Scenario`]. +/// +/// [`Rule`]: gherkin::Rule +/// [`Scenario`]: gherkin::Scenario +type RuleOrScenario = Either, Arc>; + +/// Either a [`Rule`]'s or a [`Scenario`]'s [`Queue`]. +/// +/// [`Rule`]: gherkin::Rule +/// [`Scenario`]: gherkin::Scenario +type RuleOrScenarioQueue = + Either, ScenariosQueue>; + +/// Either a [`Rule`]'s or a [`Scenario`]'s [`Queue`] with the corresponding +/// [`Rule`] or [`Scenario`] which is currently being outputted. +/// +/// [`Rule`]: gherkin::Rule +/// [`Scenario`]: gherkin::Scenario +type NextRuleOrScenario<'events, World> = Either< + (Arc, &'events mut RulesQueue), + (Arc, &'events mut ScenariosQueue), +>; + +/// [`Queue`] of all events of a single [`Feature`]. +/// +/// [`Feature`]: gherkin::Feature +type FeatureQueue = Queue>; + +impl FeatureQueue { + /// Inserts a new [`Rule`]. + /// + /// [`Rule`]: gherkin::Rule + fn new_rule(&mut self, rule: Arc) { + drop( + self.queue + .insert(Either::Left(rule), Either::Left(RulesQueue::new())), + ); + } + + /// Marks a [`Rule`] as finished on [`event::Rule::Finished`]. + /// + /// [`Rule`]: gherkin::Rule + fn rule_finished(&mut self, rule: Arc) { + match self.queue.get_mut(&Either::Left(rule)).unwrap() { + Either::Left(ev) => { + ev.finished(); + } + Either::Right(_) => unreachable!(), + } + } + + /// Inserts a new [`Scenario`] event. + /// + /// [`Scenario`]: gherkin::Scenario + fn insert_scenario_event( + &mut self, + rule: Option>, + scenario: Arc, + ev: event::Scenario, + ) { + if let Some(rule) = rule { + match self + .queue + .get_mut(&Either::Left(rule.clone())) + .unwrap_or_else(|| panic!("No Rule {}", rule.name)) + { + Either::Left(rules) => rules + .queue + .entry(scenario) + .or_insert_with(ScenariosQueue::new) + .0 + .push(ev), + Either::Right(_) => unreachable!(), + } + } else { + match self + .queue + .entry(Either::Right(scenario)) + .or_insert_with(|| Either::Right(ScenariosQueue::new())) + { + Either::Right(events) => events.0.push(ev), + Either::Left(_) => unreachable!(), + } + } + } +} + +#[async_trait(?Send)] +impl<'me, World> Emitter for &'me mut FeatureQueue { + type Current = NextRuleOrScenario<'me, World>; + type Emitted = RuleOrScenario; + type EmittedPath = Arc; + + fn current_item(self) -> Option { + Some(match self.queue.iter_mut().next()? { + (Either::Left(rule), Either::Left(events)) => { + Either::Left((rule.clone(), events)) + } + (Either::Right(scenario), Either::Right(events)) => { + Either::Right((scenario.clone(), events)) + } + _ => unreachable!(), + }) + } + + async fn emit>( + self, + feature: Self::EmittedPath, + writer: &mut W, + ) -> Option { + match self.current_item()? { + Either::Left((rule, events)) => { + events.emit((feature, rule), writer).await.map(Either::Left) + } + Either::Right((scenario, events)) => events + .emit((feature, None, scenario), writer) + .await + .map(Either::Right), + } + } +} + +/// [`Queue`] of all events of a single [`Rule`]. +/// +/// [`Rule`]: gherkin::Rule +type RulesQueue = Queue, ScenariosQueue>; + +#[async_trait(?Send)] +impl<'me, World> Emitter for &'me mut RulesQueue { + type Current = (Arc, &'me mut ScenariosQueue); + type Emitted = Arc; + type EmittedPath = (Arc, Arc); + + fn current_item(self) -> Option { + self.queue + .iter_mut() + .next() + .map(|(sc, ev)| (sc.clone(), ev)) + } + + async fn emit>( + self, + (feature, rule): Self::EmittedPath, + writer: &mut W, + ) -> Option { + if !self.is_started_emitted() { + writer + .handle_event(Ok(event::Cucumber::rule_started( + feature.clone(), + rule.clone(), + ))) + .await; + self.started_emitted(); + } + + while let Some((scenario, events)) = self.current_item() { + if let Some(should_be_removed) = events + .emit((feature.clone(), Some(rule.clone()), scenario), writer) + .await + { + self.remove(&should_be_removed); + } else { + break; + } + } + + if self.is_finished() { + writer + .handle_event(Ok(event::Cucumber::rule_finished( + feature, + rule.clone(), + ))) + .await; + return Some(rule); + } + + None + } +} + +/// [`Queue`] of all events of a single [`Scenario`]. +/// +/// [`Scenario`]: gherkin::Scenario +#[derive(Debug)] +struct ScenariosQueue(Vec>); + +impl ScenariosQueue { + /// Creates a new [`ScenariosQueue`]. + fn new() -> Self { + Self(Vec::new()) + } +} + +#[async_trait(?Send)] +impl<'me, World> Emitter for &'me mut ScenariosQueue { + type Current = event::Scenario; + type Emitted = Arc; + type EmittedPath = ( + Arc, + Option>, + Arc, + ); + + fn current_item(self) -> Option { + (!self.0.is_empty()).then(|| self.0.remove(0)) + } + + async fn emit>( + self, + (feature, rule, scenario): Self::EmittedPath, + writer: &mut W, + ) -> Option { + while let Some(ev) = self.current_item() { + let should_be_removed = matches!(ev, event::Scenario::Finished); + + let ev = event::Cucumber::scenario( + feature.clone(), + rule.clone(), + scenario.clone(), + ev, + ); + writer.handle_event(Ok(ev)).await; + + if should_be_removed { + return Some(scenario.clone()); + } + } + None + } +} diff --git a/src/writer/summarized.rs b/src/writer/summarized.rs new file mode 100644 index 00000000..7912d7ce --- /dev/null +++ b/src/writer/summarized.rs @@ -0,0 +1,347 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! [`Writer`]-wrapper for collecting a summary of execution. + +use std::{array, borrow::Cow, collections::HashSet, sync::Arc}; + +use async_trait::async_trait; +use derive_more::Deref; +use itertools::Itertools as _; + +use crate::{ + event, parser, writer::term::Styles, ArbitraryWriter, FailureWriter, World, + Writer, +}; + +/// Execution statistics. +/// +/// [`Step`]: gherkin::Step +#[derive(Clone, Copy, Debug)] +pub struct Stats { + /// Number of passed [`Step`]s (or [`Scenario`]s). + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + pub passed: usize, + + /// Number of skipped [`Step`]s (or [`Scenario`]s). + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + pub skipped: usize, + + /// Number of failed [`Step`]s (or [`Scenario`]s). + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + pub failed: usize, +} + +impl Stats { + /// Returns total number of [`Step`]s (or [`Scenario`]s), these [`Stats`] + /// have been collected for. + /// + /// [`Scenario`]: gherkin::Scenario + /// [`Step`]: gherkin::Step + #[must_use] + pub fn total(&self) -> usize { + self.passed + self.skipped + self.failed + } +} + +/// Wrapper for a [`Writer`] for outputting an execution summary (number of +/// executed features, scenarios, steps and parsing errors). +/// +/// __Note:__ The underlying [`Writer`] is expected to be an [`ArbitraryWriter`] +/// with `Value` accepting [`String`]. If your underlying [`ArbitraryWriter`] +/// operates with something like JSON (or any other type), you should implement +/// a [`Writer`] on [`Summarized`] by yourself, to provide the required summary +/// format. +#[derive(Debug, Deref)] +pub struct Summarized { + /// Original [`Writer`] to summarize output of. + #[deref] + pub writer: Writer, + + /// Number of started [`Feature`]s. + /// + /// [`Feature`]: gherkin::Feature + pub features: usize, + + /// Number of started [`Rule`]s. + /// + /// [`Rule`]: gherkin::Rule + pub rules: usize, + + /// [`Scenario`]s [`Stats`]. + /// + /// [`Scenario`]: gherkin::Scenario + pub scenarios: Stats, + + /// [`Step`]s [`Stats`]. + /// + /// [`Step`]: gherkin::Step + pub steps: Stats, + + /// Number of [`Parser`] errors. + /// + /// [`Parser`]: crate::Parser + pub parsing_errors: usize, + + /// Handled [`Scenario`]s to collect [`Stats`]. + /// + /// [`Scenario`]: gherkin::Scenario + handled_scenarios: HashSet>, +} + +/// Alias for [`fn`] used to determine should [`Skipped`] test considered as +/// [`Failed`] or not. +/// +/// [`Failed`]: event::Step::Failed +/// [`Skipped`]: event::Step::Skipped +pub type SkipFn = + fn(&gherkin::Feature, Option<&gherkin::Rule>, &gherkin::Scenario) -> bool; + +#[async_trait(?Send)] +impl Writer for Summarized +where + W: World, + Wr: for<'val> ArbitraryWriter<'val, W, String>, +{ + async fn handle_event(&mut self, ev: parser::Result>) { + use event::{Cucumber, Feature, Rule}; + + let mut finished = false; + match &ev { + Err(_) => self.parsing_errors += 1, + Ok(Cucumber::Feature(_, ev)) => match ev { + Feature::Started => self.features += 1, + Feature::Rule(_, Rule::Started) => { + self.rules += 1; + } + Feature::Rule(_, Rule::Scenario(sc, ev)) + | Feature::Scenario(sc, ev) => { + self.handle_scenario(sc, ev); + } + Feature::Finished | Feature::Rule(..) => {} + }, + Ok(Cucumber::Finished) => finished = true, + Ok(Cucumber::Started) => {} + }; + + self.writer.handle_event(ev).await; + + if finished { + self.writer.write(Styles::new().summary(self)).await; + } + } +} + +#[async_trait(?Send)] +impl<'val, W, Wr, Val> ArbitraryWriter<'val, W, Val> for Summarized +where + W: World, + Self: Writer, + Wr: ArbitraryWriter<'val, W, Val>, + Val: 'val, +{ + async fn write(&mut self, val: Val) + where + 'val: 'async_trait, + { + self.writer.write(val).await; + } +} + +impl FailureWriter for Summarized +where + W: World, + Self: Writer, +{ + fn failed_steps(&self) -> usize { + self.steps.failed + } + + fn parsing_errors(&self) -> usize { + self.parsing_errors + } +} + +impl From for Summarized { + fn from(writer: Writer) -> Self { + Self { + writer, + features: 0, + rules: 0, + scenarios: Stats { + passed: 0, + skipped: 0, + failed: 0, + }, + steps: Stats { + passed: 0, + skipped: 0, + failed: 0, + }, + parsing_errors: 0, + handled_scenarios: HashSet::new(), + } + } +} + +impl Summarized { + /// Keeps track of [`Step`]'s [`Stats`]. + /// + /// [`Step`]: gherkin::Step + fn handle_step( + &mut self, + scenario: &Arc, + ev: &event::Step, + ) { + use event::Step; + + match ev { + Step::Started => {} + Step::Passed => self.steps.passed += 1, + Step::Skipped => { + self.steps.skipped += 1; + self.scenarios.skipped += 1; + let _ = self.handled_scenarios.insert(scenario.clone()); + } + Step::Failed(..) => { + self.steps.failed += 1; + self.scenarios.failed += 1; + let _ = self.handled_scenarios.insert(scenario.clone()); + } + } + } + + /// Keeps track of [`Scenario`]'s [`Stats`]. + /// + /// [`Scenario`]: gherkin::Scenario + fn handle_scenario( + &mut self, + scenario: &Arc, + ev: &event::Scenario, + ) { + use event::Scenario; + + match ev { + Scenario::Started => {} + Scenario::Background(_, ev) | Scenario::Step(_, ev) => { + self.handle_step(scenario, ev); + } + Scenario::Finished => { + if !self.handled_scenarios.remove(scenario) { + self.scenarios.passed += 1; + } + } + } + } +} + +impl Summarized { + /// Wraps the given [`Writer`] into a new [`Summarized`] one. + #[must_use] + pub fn new(writer: Writer) -> Self { + Self::from(writer) + } +} + +impl Styles { + /// Generates a formatted summary [`String`]. + #[must_use] + pub fn summary(&self, summary: &Summarized) -> String { + let features = self.maybe_plural("feature", summary.features); + + let rules = (summary.rules > 0) + .then(|| format!("{}\n", self.maybe_plural("rule", summary.rules))) + .unwrap_or_default(); + + let scenarios = + self.maybe_plural("scenario", summary.scenarios.total()); + let scenarios_stats = self.format_stats(summary.scenarios); + + let steps = self.maybe_plural("step", summary.steps.total()); + let steps_stats = self.format_stats(summary.steps); + + let parsing_errors = (summary.parsing_errors > 0) + .then(|| { + self.err( + self.maybe_plural("parsing error", summary.parsing_errors), + ) + }) + .unwrap_or_default(); + + format!( + "{}\n{}\n{}{}{}\n{}{}\n{}", + self.bold(self.header("[Summary]")), + features, + rules, + scenarios, + scenarios_stats, + steps, + steps_stats, + parsing_errors, + ) + .trim_end_matches('\n') + .to_string() + } + + /// Formats [`Stats`] for a terminal output. + #[must_use] + pub fn format_stats(&self, stats: Stats) -> Cow<'static, str> { + let formatted = array::IntoIter::new([ + (stats.passed > 0) + .then(|| self.bold(self.ok(format!("{} passed", stats.passed)))) + .unwrap_or_default(), + (stats.skipped > 0) + .then(|| { + self.bold( + self.skipped(format!("{} skipped", stats.skipped)), + ) + }) + .unwrap_or_default(), + (stats.failed > 0) + .then(|| { + self.bold(self.err(format!("{} failed", stats.failed))) + }) + .unwrap_or_default(), + ]) + .filter(|s| !s.is_empty()) + .join(&self.bold(", ")); + + (!formatted.is_empty()) + .then(|| { + self.bold(format!( + " {}{}{}", + self.bold("("), + formatted, + self.bold(")") + )) + }) + .unwrap_or_default() + } + + /// Adds `s` to `singular` if the given `num` is not `1`. + fn maybe_plural( + &self, + singular: impl Into>, + num: usize, + ) -> Cow<'static, str> { + self.bold(format!( + "{} {}{}", + num, + singular.into(), + (num != 1).then(|| "s").unwrap_or_default(), + )) + } +} diff --git a/src/writer/term.rs b/src/writer/term.rs new file mode 100644 index 00000000..7720f2ac --- /dev/null +++ b/src/writer/term.rs @@ -0,0 +1,126 @@ +// Copyright (c) 2018-2021 Brendan Molloy , +// Ilya Solovyiov , +// Kai Ren +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Tools for terminal output. + +use std::borrow::Cow; + +use console::Style; + +/// [`Style`]s for terminal output. +#[derive(Debug)] +pub struct Styles { + /// [`Style`] for rendering successful events. + pub ok: Style, + + /// [`Style`] for rendering skipped events. + pub skipped: Style, + + /// [`Style`] for rendering errors and failed events. + pub err: Style, + + /// [`Style`] for rendering header. + pub header: Style, + + /// [`Style`] for rendering __bold__. + pub bold: Style, + + /// Indicates whether the terminal was detected. + pub is_present: bool, +} + +impl Default for Styles { + fn default() -> Self { + Self { + ok: Style::new().green(), + skipped: Style::new().cyan(), + err: Style::new().red(), + header: Style::new().blue(), + bold: Style::new().bold(), + is_present: atty::is(atty::Stream::Stdout) + && console::colors_enabled(), + } + } +} + +impl Styles { + /// Creates new [`Styles`]. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// If terminal is present colors `input` with [`Styles::ok`] color or + /// leaves "as is" otherwise. + #[must_use] + pub fn ok(&self, input: impl Into>) -> Cow<'static, str> { + if self.is_present { + self.ok.apply_to(input.into()).to_string().into() + } else { + input.into() + } + } + + /// If terminal is present colors `input` with [`Styles::skipped`] color or + /// leaves "as is" otherwise. + #[must_use] + pub fn skipped( + &self, + input: impl Into>, + ) -> Cow<'static, str> { + if self.is_present { + self.skipped.apply_to(input.into()).to_string().into() + } else { + input.into() + } + } + + /// If terminal is present colors `input` with [`Styles::err`] color or + /// leaves "as is" otherwise. + #[must_use] + pub fn err( + &self, + input: impl Into>, + ) -> Cow<'static, str> { + if self.is_present { + self.err.apply_to(input.into()).to_string().into() + } else { + input.into() + } + } + + /// If terminal is present colors `input` with [`Styles::header`] color or + /// leaves "as is" otherwise. + #[must_use] + pub fn header( + &self, + input: impl Into>, + ) -> Cow<'static, str> { + if self.is_present { + self.header.apply_to(input.into()).to_string().into() + } else { + input.into() + } + } + + /// If terminal is present makes `input` __bold__ or leaves "as is" + /// otherwise. + #[must_use] + pub fn bold( + &self, + input: impl Into>, + ) -> Cow<'static, str> { + if self.is_present { + self.bold.apply_to(input.into()).to_string().into() + } else { + input.into() + } + } +} diff --git a/tests/cucumber_builder.rs b/tests/cucumber_builder.rs deleted file mode 100644 index ed186a36..00000000 --- a/tests/cucumber_builder.rs +++ /dev/null @@ -1,129 +0,0 @@ -extern crate cucumber_rust as cucumber; - -use cucumber::{async_trait, criteria, World}; -use futures::FutureExt; -use regex::Regex; -use std::{cell::RefCell, convert::Infallible}; - -pub struct MyWorld { - // You can use this struct for mutable context in scenarios. - foo: String, - bar: usize, - some_value: RefCell, -} - -impl MyWorld { - async fn test_async_fn(&mut self) { - *self.some_value.borrow_mut() = 123u8; - self.bar = 123; - } -} - -#[async_trait(?Send)] -impl World for MyWorld { - type Error = Infallible; - - async fn new() -> Result { - Ok(Self { - foo: "wat".into(), - bar: 0, - some_value: RefCell::new(0), - }) - } -} - -mod example_steps { - use super::SomeString; - use cucumber::{t, Steps, World}; - - pub fn steps() -> Steps { - let mut builder: Steps = Steps::new(); - - builder - .given_async( - "a thing", - t!(|mut world: crate::MyWorld, ctx| { - println!("{}", ctx.get::<&'static str>().unwrap()); - println!("{}", ctx.get::().unwrap()); - println!("{}", ctx.get::().unwrap().0); - println!("This is on stdout"); - eprintln!("This is on stderr"); - world.foo = "elho".into(); - world.test_async_fn().await; - world - }), - ) - .when_regex_async( - "something goes (.*)", - t!(|world, _ctx| crate::MyWorld::new().await.unwrap()), - ) - .given( - "I am trying out Cucumber", - |mut world: crate::MyWorld, _ctx| { - world.foo = "Some string".to_string(); - world - }, - ) - .when("I consider what I am doing", |mut world, _ctx| { - let new_string = format!("{}.", &world.foo); - world.foo = new_string; - world - }) - .then("I am interested in ATDD", |world, _ctx| { - assert_eq!(world.foo, "Some string."); - world - }) - .then_regex(r"^we can (.*) rules with regex$", |world, ctx| { - // And access them as an array - assert_eq!(ctx.matches[1], "implement"); - world - }) - .given_regex(r"a number (\d+)", |mut world, ctx| { - world.foo = ctx.matches[1].to_owned(); - world - }) - .then_regex(r"twice that number should be (\d+)", |world, ctx| { - let to_check = world.foo.parse::().unwrap(); - let expected = ctx.matches[1].parse::().unwrap(); - assert_eq!(to_check * 2, expected); - world - }); - - builder - } -} - -struct SomeString(&'static str); - -#[tokio::main] -async fn main() { - // Do any setup you need to do before running the Cucumber runner. - // e.g. setup_some_db_thing()?; - - cucumber::Cucumber::::new() - .features(&["./features/basic"]) - .steps(example_steps::steps()) - .context( - cucumber::Context::new() - .add("This is a string from the context.") - .add(42u32) - .add(SomeString("the newtype pattern helps here")), - ) - .before(criteria::scenario(Regex::new(".*").unwrap()), |_| { - async move { - println!("S:AHHHH"); - () - } - .boxed() - }) - .after(criteria::scenario(Regex::new(".*").unwrap()), |_| { - async move { - println!("E:AHHHH"); - } - .boxed() - }) - .debug(true) - .cli() - .run_and_exit() - .await -} diff --git a/tests/features/book/concurrent.feature b/tests/features/book/concurrent.feature new file mode 100644 index 00000000..10cdc768 --- /dev/null +++ b/tests/features/book/concurrent.feature @@ -0,0 +1,11 @@ +Feature: Animal feature + + Scenario: If we feed a hungry cat it will no longer be hungry + Given a hungry cat + When I feed the cat + Then the cat is not hungry + + Scenario: If we feed a satiated cat it will not become hungry + Given a satiated cat + When I feed the cat + Then the cat is not hungry \ No newline at end of file diff --git a/tests/features/book/serial.feature b/tests/features/book/serial.feature new file mode 100644 index 00000000..034a68eb --- /dev/null +++ b/tests/features/book/serial.feature @@ -0,0 +1,13 @@ +Feature: Animal feature + + @serial + Scenario: If we feed a hungry cat it will no longer be hungry + Given a hungry cat + When I feed the cat + Then the cat is not hungry + + @serial + Scenario: If we feed a satiated cat it will not become hungry + Given a satiated cat + When I feed the cat + Then the cat is not hungry diff --git a/tests/features/output/background_rule.feature b/tests/features/output/background_rule.feature new file mode 100644 index 00000000..1a5d6368 --- /dev/null +++ b/tests/features/output/background_rule.feature @@ -0,0 +1,11 @@ +Feature: output + + Background: + Given foo is 0 + + Rule: output + + Scenario: output + Given foo is 1 + When foo is 2 + Then foo is 3 diff --git a/tests/features/output/background_rule.feature.out b/tests/features/output/background_rule.feature.out new file mode 100644 index 00000000..de4642ba --- /dev/null +++ b/tests/features/output/background_rule.feature.out @@ -0,0 +1,16 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/background_rule_background.feature b/tests/features/output/background_rule_background.feature new file mode 100644 index 00000000..ee40c3d2 --- /dev/null +++ b/tests/features/output/background_rule_background.feature @@ -0,0 +1,14 @@ +Feature: output + + Background: + Given foo is 0 + + Rule: output + + Background: + Given foo is 1 + + Scenario: output + Given foo is 2 + When foo is 3 + Then foo is 4 diff --git a/tests/features/output/background_rule_background.feature.out b/tests/features/output/background_rule_background.feature.out new file mode 100644 index 00000000..6910a9a7 --- /dev/null +++ b/tests/features/output/background_rule_background.feature.out @@ -0,0 +1,18 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: None, tags: [], position: LineCol { line: 11, col: 5 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/background_rule_background_outline.feature b/tests/features/output/background_rule_background_outline.feature new file mode 100644 index 00000000..7e822cea --- /dev/null +++ b/tests/features/output/background_rule_background_outline.feature @@ -0,0 +1,18 @@ +Feature: Outline + + Background: + Given foo is 0 + + Rule: outline + + Background: + Given foo is 1 + + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 2 | 3 | 4 | diff --git a/tests/features/output/background_rule_background_outline.feature.out b/tests/features/output/background_rule_background_outline.feature.out new file mode 100644 index 00000000..0504f6bc --- /dev/null +++ b/tests/features/output/background_rule_background_outline.feature.out @@ -0,0 +1,18 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], position: LineCol { line: 8, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 12, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 13, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 4", docstring: None, table: None, position: LineCol { line: 14, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["2", "3", "4"]], position: LineCol { line: 17, col: 9 } }, tags: [], position: LineCol { line: 16, col: 7 } }), tags: [], position: LineCol { line: 17, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/background_rule_outline.feature b/tests/features/output/background_rule_outline.feature new file mode 100644 index 00000000..70cb057e --- /dev/null +++ b/tests/features/output/background_rule_outline.feature @@ -0,0 +1,15 @@ +Feature: Outline + + Background: + Given foo is 0 + + Rule: outline + + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 1 | 2 | 3 | diff --git a/tests/features/output/background_rule_outline.feature.out b/tests/features/output/background_rule_outline.feature.out new file mode 100644 index 00000000..fc9029a0 --- /dev/null +++ b/tests/features/output/background_rule_outline.feature.out @@ -0,0 +1,16 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 14, col: 9 } }, tags: [], position: LineCol { line: 13, col: 7 } }), tags: [], position: LineCol { line: 14, col: 7 } }], tags: [], position: LineCol { line: 6, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/background_scenario.feature b/tests/features/output/background_scenario.feature new file mode 100644 index 00000000..f758faba --- /dev/null +++ b/tests/features/output/background_scenario.feature @@ -0,0 +1,9 @@ +Feature: output + + Background: + Given foo is 0 + + Scenario: output + Given foo is 1 + When foo is 2 + Then foo is 3 diff --git a/tests/features/output/background_scenario.feature.out b/tests/features/output/background_scenario.feature.out new file mode 100644 index 00000000..3a021e35 --- /dev/null +++ b/tests/features/output/background_scenario.feature.out @@ -0,0 +1,14 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }], examples: None, tags: [], position: LineCol { line: 6, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/background_scenario_outline.feature b/tests/features/output/background_scenario_outline.feature new file mode 100644 index 00000000..fbff210b --- /dev/null +++ b/tests/features/output/background_scenario_outline.feature @@ -0,0 +1,13 @@ +Feature: Outline + + Background: + Given foo is 0 + + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 1 | 2 | 3 | diff --git a/tests/features/output/background_scenario_outline.feature.out b/tests/features/output/background_scenario_outline.feature.out new file mode 100644 index 00000000..d15494c3 --- /dev/null +++ b/tests/features/output/background_scenario_outline.feature.out @@ -0,0 +1,14 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }], position: LineCol { line: 3, col: 3 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 7, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 8, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 9, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 12, col: 7 } }, tags: [], position: LineCol { line: 11, col: 5 } }), tags: [], position: LineCol { line: 12, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/non_default_lang.feature b/tests/features/output/non_default_lang.feature new file mode 100644 index 00000000..9ac6209e --- /dev/null +++ b/tests/features/output/non_default_lang.feature @@ -0,0 +1,7 @@ +# language: no +Egenskap: output + Eksempel: output + Gitt foo is 0 + Når foo is 1 + Så foo is 2 + diff --git a/tests/features/output/non_default_lang.feature.out b/tests/features/output/non_default_lang.feature.out new file mode 100644 index 00000000..c9869d67 --- /dev/null +++ b/tests/features/output/non_default_lang.feature.out @@ -0,0 +1,12 @@ +Started +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Started)) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Started))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Passed))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }, Started))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Step(Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }, Passed))) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Scenario(Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }, Finished)) +Feature(Feature { keyword: "Egenskap", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Eksempel", name: "output", steps: [Step { keyword: "Gitt", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Når", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Så", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: None, tags: [], position: LineCol { line: 3, col: 3 } }], rules: [], tags: [], position: LineCol { line: 2, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/parsing_failed.feature b/tests/features/output/parsing_failed.feature new file mode 100644 index 00000000..4610d09f --- /dev/null +++ b/tests/features/output/parsing_failed.feature @@ -0,0 +1,2 @@ +Feature: + Given invalid file diff --git a/tests/features/output/parsing_failed.feature.out b/tests/features/output/parsing_failed.feature.out new file mode 100644 index 00000000..fdcbe228 --- /dev/null +++ b/tests/features/output/parsing_failed.feature.out @@ -0,0 +1,3 @@ +ParsingError +Started +Finished diff --git a/tests/features/output/rule.feature b/tests/features/output/rule.feature new file mode 100644 index 00000000..9a8b61f8 --- /dev/null +++ b/tests/features/output/rule.feature @@ -0,0 +1,6 @@ +Feature: output + Rule: output + Scenario: output + Given foo is 0 + When foo is 1 + Then foo is 2 diff --git a/tests/features/output/rule.feature.out b/tests/features/output/rule.feature.out new file mode 100644 index 00000000..07cb26e2 --- /dev/null +++ b/tests/features/output/rule.feature.out @@ -0,0 +1,14 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: None, tags: [], position: LineCol { line: 3, col: 5 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/rule_background.feature b/tests/features/output/rule_background.feature new file mode 100644 index 00000000..9b40b336 --- /dev/null +++ b/tests/features/output/rule_background.feature @@ -0,0 +1,11 @@ +Feature: output + + Rule: output + + Background: + Given foo is 0 + + Scenario: output + Given foo is 1 + When foo is 2 + Then foo is 3 diff --git a/tests/features/output/rule_background.feature.out b/tests/features/output/rule_background.feature.out new file mode 100644 index 00000000..ce6bcce8 --- /dev/null +++ b/tests/features/output/rule_background.feature.out @@ -0,0 +1,16 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "output", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], position: LineCol { line: 5, col: 5 } }), scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 11, col: 7 } }], examples: None, tags: [], position: LineCol { line: 8, col: 5 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/rule_background_outline.feature b/tests/features/output/rule_background_outline.feature new file mode 100644 index 00000000..cdbdfcc8 --- /dev/null +++ b/tests/features/output/rule_background_outline.feature @@ -0,0 +1,14 @@ +Feature: Outline + + Rule: outline + Background: + Given foo is 0 + + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 1 | 2 | 3 | diff --git a/tests/features/output/rule_background_outline.feature.out b/tests/features/output/rule_background_outline.feature.out new file mode 100644 index 00000000..ae99d309 --- /dev/null +++ b/tests/features/output/rule_background_outline.feature.out @@ -0,0 +1,16 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Background(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "outline", background: Some(Background { keyword: "Background", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }], position: LineCol { line: 4, col: 5 } }), scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 8, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 9, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 3", docstring: None, table: None, position: LineCol { line: 10, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["1", "2", "3"]], position: LineCol { line: 13, col: 9 } }, tags: [], position: LineCol { line: 12, col: 7 } }), tags: [], position: LineCol { line: 13, col: 7 } }], tags: [], position: LineCol { line: 3, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/rule_outline.feature b/tests/features/output/rule_outline.feature new file mode 100644 index 00000000..c860e718 --- /dev/null +++ b/tests/features/output/rule_outline.feature @@ -0,0 +1,10 @@ +Feature: Outline + Rule: them all + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 0 | 1 | 2 | diff --git a/tests/features/output/rule_outline.feature.out b/tests/features/output/rule_outline.feature.out new file mode 100644 index 00000000..03f402d0 --- /dev/null +++ b/tests/features/output/rule_outline.feature.out @@ -0,0 +1,14 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Started)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }, Passed)))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }, Finished))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Rule(Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [], rules: [Rule { keyword: "Rule", name: "them all", background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 7 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 7 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 7 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 9 } }, tags: [], position: LineCol { line: 8, col: 7 } }), tags: [], position: LineCol { line: 9, col: 7 } }], tags: [], position: LineCol { line: 2, col: 3 } }], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/scenario.feature b/tests/features/output/scenario.feature new file mode 100644 index 00000000..aa2e151c --- /dev/null +++ b/tests/features/output/scenario.feature @@ -0,0 +1,5 @@ +Feature: output + Scenario: output + Given foo is 0 + When foo is 1 + Then foo is 2 diff --git a/tests/features/output/scenario.feature.out b/tests/features/output/scenario.feature.out new file mode 100644 index 00000000..0d19780a --- /dev/null +++ b/tests/features/output/scenario.feature.out @@ -0,0 +1,12 @@ +Started +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Started)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "output", description: None, background: None, scenarios: [Scenario { keyword: "Scenario", name: "output", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 3, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }], examples: None, tags: [], position: LineCol { line: 2, col: 3 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/output/scenario_outline.feature b/tests/features/output/scenario_outline.feature new file mode 100644 index 00000000..8aea4d68 --- /dev/null +++ b/tests/features/output/scenario_outline.feature @@ -0,0 +1,10 @@ +Feature: Outline + + Scenario Outline: foo + Given foo is + When foo is + Then foo is + + Examples: + | bar1 | bar2 | bar3 | + | 0 | 1 | 2 | diff --git a/tests/features/output/scenario_outline.feature.out b/tests/features/output/scenario_outline.feature.out new file mode 100644 index 00000000..f4b2de7a --- /dev/null +++ b/tests/features/output/scenario_outline.feature.out @@ -0,0 +1,12 @@ +Started +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Started) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Started)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }, Started))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Step(Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }, Passed))) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Scenario(Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }, Finished)) +Feature(Feature { keyword: "Feature", name: "Outline", description: None, background: None, scenarios: [Scenario { keyword: "Scenario Outline", name: "foo", steps: [Step { keyword: "Given", ty: Given, value: "foo is 0", docstring: None, table: None, position: LineCol { line: 4, col: 5 } }, Step { keyword: "When", ty: When, value: "foo is 1", docstring: None, table: None, position: LineCol { line: 5, col: 5 } }, Step { keyword: "Then", ty: Then, value: "foo is 2", docstring: None, table: None, position: LineCol { line: 6, col: 5 } }], examples: Some(Examples { keyword: "Examples", table: Table { rows: [["bar1", "bar2", "bar3"], ["0", "1", "2"]], position: LineCol { line: 9, col: 7 } }, tags: [], position: LineCol { line: 8, col: 5 } }), tags: [], position: LineCol { line: 9, col: 5 } }], rules: [], tags: [], position: LineCol { line: 1, col: 1 }, path: None }, Finished) +Finished diff --git a/tests/features/readme/eating.feature b/tests/features/readme/eating.feature new file mode 100644 index 00000000..037f18cf --- /dev/null +++ b/tests/features/readme/eating.feature @@ -0,0 +1,6 @@ +Feature: Eating too much cucumbers may not be good for you + + Scenario: Eating a few isn't a problem + Given Alice is hungry + When she eats 3 cucumbers + Then she is full diff --git a/tests/features/wait/invalid.feature b/tests/features/wait/invalid.feature new file mode 100644 index 00000000..938283d6 --- /dev/null +++ b/tests/features/wait/invalid.feature @@ -0,0 +1,2 @@ +Feature: invalid + Given: where's scenario??? diff --git a/tests/features/wait/nested/rule.feature b/tests/features/wait/nested/rule.feature new file mode 100644 index 00000000..cd864088 --- /dev/null +++ b/tests/features/wait/nested/rule.feature @@ -0,0 +1,17 @@ +Feature: Basic + Background: + Given 1 sec + + @serial + Scenario: 1 sec + Given 1 sec + When 1 sec + Then unknown + Then 1 sec + + Rule: rule + Scenario: 2 secs + Given 2 secs + When 2 secs + Then 2 secs + Then 1 sec diff --git a/tests/features/wait/outline.feature b/tests/features/wait/outline.feature new file mode 100644 index 00000000..23606ee6 --- /dev/null +++ b/tests/features/wait/outline.feature @@ -0,0 +1,13 @@ +Feature: Outline + + Scenario Outline: wait + Given secs + When secs + Then secs + + Examples: + | wait | + | 2 | + | 1 | + | 1 | + | 5 | diff --git a/tests/features/wait/rule.feature b/tests/features/wait/rule.feature new file mode 100644 index 00000000..cd864088 --- /dev/null +++ b/tests/features/wait/rule.feature @@ -0,0 +1,17 @@ +Feature: Basic + Background: + Given 1 sec + + @serial + Scenario: 1 sec + Given 1 sec + When 1 sec + Then unknown + Then 1 sec + + Rule: rule + Scenario: 2 secs + Given 2 secs + When 2 secs + Then 2 secs + Then 1 sec diff --git a/tests/features/wait/rule_outline.feature b/tests/features/wait/rule_outline.feature new file mode 100644 index 00000000..4c9d9abc --- /dev/null +++ b/tests/features/wait/rule_outline.feature @@ -0,0 +1,14 @@ +Feature: Rule Outline + + Rule: To them all + Scenario Outline: wait + Given secs + When secs + Then secs + + Examples: + | wait | + | 2 | + | 1 | + | 1 | + | 5 | diff --git a/tests/fixtures/capture-runner/Cargo.toml b/tests/fixtures/capture-runner/Cargo.toml deleted file mode 100644 index cf13611a..00000000 --- a/tests/fixtures/capture-runner/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "capture-runner" -version = "0.1.0" -authors = ["Zack Pierce "] -edition = "2018" - -[dependencies] -async-trait = "0.1.40" -cucumber_rust = { path = "../../.." } -futures = "0.3.5" diff --git a/tests/fixtures/capture-runner/README.md b/tests/fixtures/capture-runner/README.md deleted file mode 100644 index 1a217f6d..00000000 --- a/tests/fixtures/capture-runner/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# capture-runner - -An internal-only test-helper CLI application which -executes a single cucumber scenario using the -cucumber-rust framework. - -The steps of the scenario will attempt to print -to stdout and stderr. - -The cucumber runner can be minimally configured -using command line arguments. - -## Example usage - -To run the cucumber test with enable_capture on: -```shell script -capture-runner true -``` - -To run the cucumber test with enable_capture off: -```shell script -capture-runner false -``` diff --git a/tests/fixtures/capture-runner/src/main.rs b/tests/fixtures/capture-runner/src/main.rs deleted file mode 100644 index 521f3a1e..00000000 --- a/tests/fixtures/capture-runner/src/main.rs +++ /dev/null @@ -1,105 +0,0 @@ -use async_trait::async_trait; -use cucumber_rust::{event::*, output::BasicOutput, Cucumber, EventHandler, Steps, World}; -use std::sync::{Arc, Mutex}; - -#[derive(Default)] -struct CaptureRunnerWorld; - -#[async_trait(?Send)] -impl World for CaptureRunnerWorld { - type Error = std::convert::Infallible; - - async fn new() -> Result { - Ok(CaptureRunnerWorld::default()) - } -} -/// Event handler that delegates for printing to the default event handler, -/// but also captures whether any steps failed, were skipped, timed out, -/// or were unimplemented. -#[derive(Clone, Default)] -pub struct ProblemDetectingEventHandler { - pub state: Arc>, -} - -#[derive(Default)] -pub struct ProblemDetectingEventHandlerState { - pub basic_output: BasicOutput, - pub any_problem: bool, -} - -impl EventHandler for ProblemDetectingEventHandler { - fn handle_event(&mut self, event: &CucumberEvent) { - let mut state = self.state.lock().unwrap(); - match &event { - CucumberEvent::Feature( - _, - FeatureEvent::Scenario( - _, - ScenarioEvent::Step(_, StepEvent::Failed(StepFailureKind::Panic(_, _))), - ), - ) - | CucumberEvent::Feature( - _, - FeatureEvent::Scenario( - _, - ScenarioEvent::Step(_, StepEvent::Failed(StepFailureKind::TimedOut)), - ), - ) - | CucumberEvent::Feature( - _, - FeatureEvent::Scenario(_, ScenarioEvent::Step(_, StepEvent::Skipped)), - ) - | CucumberEvent::Feature( - _, - FeatureEvent::Scenario(_, ScenarioEvent::Step(_, StepEvent::Unimplemented)), - ) => { - state.any_problem = true; - } - _ => {} - } - state.basic_output.handle_event(event); - } -} -fn main() { - let args: Vec = std::env::args().collect(); - if args.len() < 2 { - eprintln!("Requires a boolean argument [true | false] to indicate whether to enable output capture"); - std::process::exit(1); - } - let enable_capture: bool = args[1].parse().unwrap(); - let mut steps = Steps::::new(); - - steps.when( - r#"we print "everything is great" to stdout"#, - |world, _step| { - println!("everything is great"); - world - }, - ); - steps.when( - r#"we print "something went wrong" to stderr"#, - |world, _step| { - eprintln!("something went wrong"); - world - }, - ); - steps.then( - "it is up to the cucumber configuration to decide whether the content gets printed", - |world, _step| world, - ); - - let event_handler = ProblemDetectingEventHandler::default(); - let runner = Cucumber::with_handler(event_handler.clone()) - .steps(steps) - .features(&["./features/capture"]) - .enable_capture(enable_capture); - - futures::executor::block_on(runner.run()); - let handler_state = event_handler.state.lock().unwrap(); - - if handler_state.any_problem { - std::process::exit(1); - } else { - std::process::exit(0); - } -} diff --git a/tests/integration_test.rs b/tests/integration_test.rs deleted file mode 100644 index 52f220a8..00000000 --- a/tests/integration_test.rs +++ /dev/null @@ -1,197 +0,0 @@ -use async_trait::async_trait; -use cucumber_rust::{event::*, t, Cucumber, EventHandler, Steps, World}; -use serial_test::serial; -use std::path::PathBuf; -use std::process::Command; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -#[derive(Default, Clone)] -struct CustomEventHandler { - state: Arc>, -} -#[derive(Default)] -struct CustomEventHandlerState { - any_rule_failures: bool, - any_scenario_skipped: bool, - any_scenario_failures: bool, - any_step_unimplemented: bool, - any_step_failures: bool, - any_step_success: bool, - any_step_timeouts: bool, -} -impl EventHandler for CustomEventHandler { - fn handle_event(&mut self, event: &CucumberEvent) { - let mut state = self.state.lock().unwrap(); - match event { - CucumberEvent::Feature( - _feature, - FeatureEvent::Rule(_rule, RuleEvent::Failed(FailureKind::Panic)), - ) => { - state.any_rule_failures = true; - } - CucumberEvent::Feature( - _feature, - FeatureEvent::Scenario(_scenario, ScenarioEvent::Failed(FailureKind::Panic)), - ) => { - state.any_scenario_failures = true; - } - CucumberEvent::Feature( - ref _feature, - FeatureEvent::Scenario(ref _scenario, ScenarioEvent::Skipped), - ) => { - state.any_scenario_skipped = true; - } - CucumberEvent::Feature( - _feature, - FeatureEvent::Scenario( - _scenario, - ScenarioEvent::Step(_step, StepEvent::Failed(StepFailureKind::Panic(_, _))), - ), - ) => { - state.any_step_failures = true; - } - CucumberEvent::Feature( - _feature, - FeatureEvent::Scenario( - _scenario, - ScenarioEvent::Step(_step, StepEvent::Failed(StepFailureKind::TimedOut)), - ), - ) => { - state.any_step_timeouts = true; - } - CucumberEvent::Feature( - _feature, - FeatureEvent::Scenario( - _scenario, - ScenarioEvent::Step(_step, StepEvent::Unimplemented), - ), - ) => { - state.any_step_unimplemented = true; - } - CucumberEvent::Feature( - _feature, - FeatureEvent::Scenario(_scenario, ScenarioEvent::Step(_step, StepEvent::Passed(_))), - ) => { - state.any_step_success = true; - } - _ => {} - } - } -} - -#[derive(Default)] -struct StatelessWorld; - -#[async_trait(?Send)] -impl World for StatelessWorld { - type Error = std::convert::Infallible; - - async fn new() -> Result { - Ok(StatelessWorld::default()) - } -} - -fn stateless_steps() -> Steps { - let mut steps = Steps::::new(); - steps.when("something", |world, _step| world); - steps.when("another thing", |world, _step| world); - steps.then("it's okay", |world, _step| world); - steps.then("it's not okay", |_world, _step| { - panic!("Intentionally panicking to fail the step") - }); - steps.then_async( - "it takes a long time", - t!(|world, _step| { - futures_timer::Delay::new(Duration::from_secs(9_000)).await; - world - }), - ); - steps -} - -#[test] -#[serial] -fn user_defined_event_handlers_are_expressible() { - let custom_handler = CustomEventHandler::default(); - - let runner = Cucumber::with_handler(custom_handler.clone()) - .steps(stateless_steps()) - .features(&["./features/integration"]) - .step_timeout(Duration::from_secs(1)); - - let results = futures::executor::block_on(runner.run()); - - assert_eq!(results.features.total, 1); - assert_eq!(results.scenarios.total, 4); - assert_eq!(results.steps.total, 14); - assert_eq!(results.steps.passed, 4); - assert_eq!(results.scenarios.failed, 1); - - let handler_state = custom_handler.state.lock().unwrap(); - assert!(!handler_state.any_rule_failures); - assert!(handler_state.any_step_failures); - assert!(handler_state.any_step_unimplemented); - assert!(handler_state.any_step_success); - assert!(handler_state.any_scenario_skipped); - assert!(handler_state.any_step_timeouts); -} - -fn nocapture_enabled() -> bool { - std::env::args_os().any(|a| { - if let Some(s) = a.to_str() { - s == "--nocapture" - } else { - false - } - }) || match std::env::var("RUST_TEST_NOCAPTURE") { - Ok(val) => &val != "0", - Err(_) => false, - } -} - -#[test] -#[serial] -fn enable_capture_false_support() { - if !nocapture_enabled() { - // This test only functions when the Rust test framework is refraining - // from swallowing all output from this process (and child processes) - // Execute with `cargo test -- --nocapture` to see the real results - return; - } - let command_output = Command::new(built_executable_path("capture-runner")) - .args(&["false"]) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .output() - .expect("Could not execute capture-runner"); - let stdout = String::from_utf8_lossy(&command_output.stdout); - let stderr = String::from_utf8_lossy(&command_output.stderr); - assert!(stdout.contains("everything is great")); - assert!(stderr.contains("something went wrong")); - assert!( - command_output.status.success(), - "capture-runner should exit successfully" - ); -} -fn get_target_dir() -> PathBuf { - let bin = std::env::current_exe().expect("exe path"); - let mut target_dir = PathBuf::from(bin.parent().expect("bin parent")); - while target_dir.file_name() != Some(std::ffi::OsStr::new("target")) { - target_dir.pop(); - } - target_dir -} - -fn built_executable_path(name: &str) -> PathBuf { - let program_path = - get_target_dir() - .join("debug") - .join(format!("{}{}", name, std::env::consts::EXE_SUFFIX)); - - program_path.canonicalize().expect(&format!( - "Cannot resolve {} at {:?}", - name, - program_path.display() - )) -} diff --git a/tests/output.rs b/tests/output.rs new file mode 100644 index 00000000..ae7492a5 --- /dev/null +++ b/tests/output.rs @@ -0,0 +1,94 @@ +use std::{borrow::Cow, convert::Infallible, fmt::Debug, sync::Arc}; + +use async_trait::async_trait; +use cucumber_rust::{ + self as cucumber, event, given, parser, then, when, WorldInit, Writer, +}; +use regex::Regex; + +#[derive(Debug, Default, WorldInit)] +struct World(usize); + +#[given(regex = r"foo is (\d+)")] +#[when(regex = r"foo is (\d+)")] +#[then(regex = r"foo is (\d+)")] +fn step(w: &mut World, num: usize) { + assert_eq!(w.0, num); + w.0 += 1; +} + +#[async_trait(?Send)] +impl cucumber::World for World { + type Error = Infallible; + + async fn new() -> Result { + Ok(World::default()) + } +} + +#[derive(Default)] +struct DebugWriter(String); + +#[async_trait(?Send)] +impl Writer for DebugWriter { + async fn handle_event( + &mut self, + ev: parser::Result>, + ) { + let ev: Cow<_> = match ev { + Err(_) => "ParsingError".into(), + Ok(event::Cucumber::Feature(f, ev)) => { + let mut f = f.as_ref().clone(); + f.path = None; + format!("{:?}", event::Cucumber::Feature(Arc::new(f), ev)) + .into() + } + Ok(ev) => format!("{:?}", ev).into(), + }; + + let re = + Regex::new(r" span: Span \{ start: (\d+), end: (\d+) },").unwrap(); + let without_span = re.replace_all(ev.as_ref(), ""); + + self.0.push_str(without_span.as_ref()); + } +} + +#[cfg(test)] +mod spec { + use std::fs; + + use cucumber_rust::{WorldInit as _, WriterExt as _}; + use globwalk::GlobWalkerBuilder; + + use super::{DebugWriter, World}; + + #[tokio::test] + async fn test() { + let walker = + GlobWalkerBuilder::new("tests/features/output", "*.feature") + .case_insensitive(true) + .build() + .unwrap(); + let files = walker + .filter_map(Result::ok) + .map(|entry| entry.file_name().to_str().unwrap().to_owned()) + .collect::>(); + + for file in files { + let out = fs::read_to_string(format!( + "tests/features/output/{}.out", + file, + )) + .unwrap_or_default() + .lines() + .collect::(); + let normalized = World::cucumber() + .with_writer(DebugWriter::default().normalized()) + .run(format!("tests/features/output/{}", file)) + .await; + + assert_eq!(normalized.0, out, "file: {}", file); + } + } +} diff --git a/tests/wait.rs b/tests/wait.rs new file mode 100644 index 00000000..42c2d340 --- /dev/null +++ b/tests/wait.rs @@ -0,0 +1,43 @@ +use std::{convert::Infallible, panic::AssertUnwindSafe, time::Duration}; + +use async_trait::async_trait; +use cucumber_rust::{self as cucumber, given, then, when, WorldInit}; +use futures::FutureExt as _; +use tokio::time; + +#[tokio::main] +async fn main() { + let res = World::run("tests/features/wait"); + + let err = AssertUnwindSafe(res) + .catch_unwind() + .await + .expect_err("should err"); + let err = err.downcast_ref::().unwrap(); + + assert_eq!(err, "2 steps failed, 1 parsing error"); +} + +#[given(regex = r"(\d+) secs?")] +#[when(regex = r"(\d+) secs?")] +#[then(regex = r"(\d+) secs?")] +async fn step(world: &mut World, secs: u64) { + time::sleep(Duration::from_secs(secs)).await; + + world.0 += 1; + if world.0 > 3 { + panic!("Too much!"); + } +} + +#[derive(Clone, Copy, Debug, WorldInit)] +struct World(usize); + +#[async_trait(?Send)] +impl cucumber::World for World { + type Error = Infallible; + + async fn new() -> Result { + Ok(World(0)) + } +}