From f48c40097d17485424f9de852218f7d13e3b7446 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 29 Mar 2024 16:14:58 +0100 Subject: [PATCH 1/4] java: Implement Query object --- .github/workflows/release-mvn.yml | 25 + .github/workflows/test-java.yml | 34 ++ .github/workflows/test-testdata.yml | 32 ++ .gitignore | 2 + CHANGELOG.md | 3 + README.md | 22 + java/.gitignore | 1 + java/pom.xml | 94 ++++ .../query/GherkinDocumentElements.java | 90 ++++ .../io/cucumber/query/NamingStrategy.java | 196 ++++++++ .../main/java/io/cucumber/query/Query.java | 334 ++++++++++++++ .../test/java/io/cucumber/query/Jackson.java | 30 ++ .../java/io/cucumber/query/QueryTest.java | 220 +++++++++ testdata/.gitignore | 1 + testdata/README.md | 12 + testdata/attachments.feature.ndjson | 104 +++++ testdata/cdata.feature.ndjson | 12 + testdata/data-tables.feature.ndjson | 15 + testdata/empty.feature.ndjson | 0 testdata/empty.feature.query-results.json | 8 + testdata/examples-tables.feature.ndjson | 68 +++ ...examples-tables.feature.query-results.json | 316 +++++++++++++ testdata/hooks.feature.ndjson | 77 ++++ testdata/markdown.feature.md.ndjson | 35 ++ testdata/minimal.feature.ndjson | 12 + testdata/minimal.feature.query-results.json | 93 ++++ testdata/package-lock.json | 434 ++++++++++++++++++ testdata/package.json | 15 + testdata/parameter-types.feature.ndjson | 13 + testdata/pending.feature.ndjson | 30 ++ testdata/retry.feature.ndjson | 59 +++ testdata/rules.feature.ndjson | 47 ++ testdata/rules.feature.query-results.json | 215 +++++++++ testdata/skipped.feature.ndjson | 33 ++ testdata/stack-traces.feature.ndjson | 12 + testdata/undefined.feature.ndjson | 29 ++ .../unknown-parameter-type.feature.ndjson | 12 + 37 files changed, 2735 insertions(+) create mode 100644 .github/workflows/release-mvn.yml create mode 100644 .github/workflows/test-java.yml create mode 100644 .github/workflows/test-testdata.yml create mode 100644 .gitignore create mode 100644 java/.gitignore create mode 100644 java/pom.xml create mode 100644 java/src/main/java/io/cucumber/query/GherkinDocumentElements.java create mode 100644 java/src/main/java/io/cucumber/query/NamingStrategy.java create mode 100644 java/src/main/java/io/cucumber/query/Query.java create mode 100644 java/src/test/java/io/cucumber/query/Jackson.java create mode 100644 java/src/test/java/io/cucumber/query/QueryTest.java create mode 100644 testdata/.gitignore create mode 100644 testdata/README.md create mode 100644 testdata/attachments.feature.ndjson create mode 100644 testdata/cdata.feature.ndjson create mode 100644 testdata/data-tables.feature.ndjson create mode 100644 testdata/empty.feature.ndjson create mode 100644 testdata/empty.feature.query-results.json create mode 100644 testdata/examples-tables.feature.ndjson create mode 100644 testdata/examples-tables.feature.query-results.json create mode 100644 testdata/hooks.feature.ndjson create mode 100644 testdata/markdown.feature.md.ndjson create mode 100644 testdata/minimal.feature.ndjson create mode 100644 testdata/minimal.feature.query-results.json create mode 100644 testdata/package-lock.json create mode 100644 testdata/package.json create mode 100644 testdata/parameter-types.feature.ndjson create mode 100644 testdata/pending.feature.ndjson create mode 100644 testdata/retry.feature.ndjson create mode 100644 testdata/rules.feature.ndjson create mode 100644 testdata/rules.feature.query-results.json create mode 100644 testdata/skipped.feature.ndjson create mode 100644 testdata/stack-traces.feature.ndjson create mode 100644 testdata/undefined.feature.ndjson create mode 100644 testdata/unknown-parameter-type.feature.ndjson diff --git a/.github/workflows/release-mvn.yml b/.github/workflows/release-mvn.yml new file mode 100644 index 00000000..2e95bc0b --- /dev/null +++ b/.github/workflows/release-mvn.yml @@ -0,0 +1,25 @@ +name: Release Maven + +on: + push: + branches: [release/*] + +jobs: + publish-mvn: + name: Publish Maven Package + runs-on: ubuntu-latest + environment: Release + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11' + cache: 'maven' + - uses: cucumber/action-publish-mvn@v2.0.0 + with: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} + nexus-username: cukebot + nexus-password: ${{ secrets.SONATYPE_PASSWORD }} + working-directory: java diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml new file mode 100644 index 00000000..5497555d --- /dev/null +++ b/.github/workflows/test-java.yml @@ -0,0 +1,34 @@ +name: test-java + +on: + push: + branches: + - main + - renovate/** + pull_request: + branches: + - main + workflow_call: + +jobs: + test-java: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + java: ["11", "17"] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: "zulu" + java-version: ${{ matrix.java }} + cache: "maven" + + - run: mvn verify + working-directory: java + diff --git a/.github/workflows/test-testdata.yml b/.github/workflows/test-testdata.yml new file mode 100644 index 00000000..e76eab97 --- /dev/null +++ b/.github/workflows/test-testdata.yml @@ -0,0 +1,32 @@ +name: test-testdata + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test-testdata: + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + cache: 'npm' + cache-dependency-path: testdata/package-lock.json + + - run: npm ci + working-directory: testdata + + - name: check repository is not dirty + run: "[[ -z $(git status --porcelain) ]]" + + - name: show diff + if: ${{ failure() }} + run: git status --porcelain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1062418c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +*.iml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d46f468..757beb3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- java: implementation of query ([#39](https://github.com/cucumber/query/pull/39)) + ## [12.0.2] - 2024-03-26 ### Fixed - Correct repo URL in `package.json` diff --git a/README.md b/README.md index fc2a0072..699cf0e7 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,25 @@ status of a step, a scenario or an entire file. | `getDocumentResults(uri: string): messages.ITestResult[]` | | | | | ✓ | | `getStepMatchArguments(uri: string, lineNumber: number): messages.IStepMatchArgument[]` | | | | | ✓ | | `getGherkinStep(gherkinStepId: string): messages.GherkinDocument.Feature.IStep` | | | | | ✓ | +| `countMostSevereTestStepResultStatus()` | | | ✓ | | | +| `countTestCasesStarted()` | | | ✓ | | | +| `findAllPickles()` | | | ✓ | | | +| `findAllPickleSteps()` | | | ✓ | | | +| `findAllTestCaseStarted()` | | | ✓ | | | +| `findAllTestSteps()` | | | ✓ | | | +| `findAllTestCaseStartedGroupedByFeature()` | | | ✓ | | | +| `findFeatureBy(TestCaseStarted)` | | | ✓ | | | +| `findMostSevereTestStepResulBy(TestCaseStarted)` | | | ✓ | | | +| `findNameOf(Pickle, NamingStrategy)` | | | ✓ | | | +| `findPickleBy(TestCaseStarted)` | | | ✓ | | | +| `findPickleStepBy(TestStep testStep)` | | | ✓ | | | +| `findStepBy(PickleStep pickleStep)` | | | ✓ | | | +| `findTestCaseBy(TestCaseStarted)` | | | ✓ | | | +| `findTestCaseDurationBy(TestCaseStarted)` | | | ✓ | | | +| `findTestCaseFinishedBy(TestCaseStarted)` | | | ✓ | | | +| `findTestRunDuration()` | | | ✓ | | | +| `findTestRunFinished()` | | | ✓ | | | +| `findTestRunStarted()` | | | ✓ | | | +| `findTestStepBy(TestStepFinished)` | | | ✓ | | | +| `findTestStepsFinishedBy(TestCaseStarted)` | | | ✓ | | | +| `findTestStepFinishedAndTestStepBy(TestCaseStarted)` | | | ✓ | | | diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/java/pom.xml b/java/pom.xml new file mode 100644 index 00000000..9f2f6ed6 --- /dev/null +++ b/java/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + + io.cucumber + cucumber-parent + 4.2.0 + + + query + 0.0.1-SNAPSHOT + jar + Cucumber Query + Query messages + https://github.com/cucumber/query + + + io.cucumber.query + 1711217894 + + + + scm:git:git://github.com/cucumber/query.git + scm:git:git@github.com:cucumber/query.git + git://github.com/cucumber/query.git + HEAD + + + + + + org.junit + junit-bom + 5.10.2 + pom + import + + + + com.fasterxml.jackson + jackson-bom + 2.17.0 + pom + import + + + + + + + io.cucumber + messages + [24.0.0,25.0.0) + + + + com.fasterxml.jackson.core + jackson-databind + test + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + test + + + + com.fasterxml.jackson.module + jackson-module-parameter-names + test + + + + org.assertj + assertj-core + 3.25.3 + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + diff --git a/java/src/main/java/io/cucumber/query/GherkinDocumentElements.java b/java/src/main/java/io/cucumber/query/GherkinDocumentElements.java new file mode 100644 index 00000000..3150e425 --- /dev/null +++ b/java/src/main/java/io/cucumber/query/GherkinDocumentElements.java @@ -0,0 +1,90 @@ +package io.cucumber.query; + +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Rule; +import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.TableRow; + +import java.util.Objects; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +/** + * A structure containing all ancestors of a given element. + *

+ * This works without any ordering because Gherkins document + * structure is simple enough to hard code. + */ +class GherkinDocumentElements { + private final GherkinDocument document; + private final Feature feature; + private final Rule rule; + private final Scenario scenario; + private final Examples examples; + private final TableRow example; + private final Integer examplesIndex; + private final Integer exampleIndex; + + GherkinDocumentElements(GherkinDocument document, Feature feature, Rule rule, Scenario scenario) { + this(document, feature, rule, scenario, null, null, null, null); + } + + GherkinDocumentElements(GherkinDocument document, Feature feature, Rule rule, Scenario scenario, Integer examplesIndex, Examples examples, Integer exampleIndex, TableRow example) { + this.document = requireNonNull(document); + this.feature = feature; + this.rule = rule; + this.scenario = scenario; + this.examplesIndex = examplesIndex; + this.examples = examples; + this.exampleIndex = exampleIndex; + this.example = example; + } + + GherkinDocument document() { + return document; + } + + Optional feature() { + return Optional.ofNullable(feature); + } + + Optional rule() { + return Optional.ofNullable(rule); + } + + Optional scenario() { + return Optional.ofNullable(scenario); + } + + Optional examples() { + return Optional.ofNullable(examples); + } + + Optional example() { + return Optional.ofNullable(example); + } + + Optional examplesIndex() { + return Optional.ofNullable(examplesIndex); + } + + Optional exampleIndex() { + return Optional.ofNullable(exampleIndex); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GherkinDocumentElements that = (GherkinDocumentElements) o; + return document.equals(that.document) && feature.equals(that.feature) && Objects.equals(rule, that.rule) && scenario.equals(that.scenario) && Objects.equals(examples, that.examples) && Objects.equals(example, that.example) && Objects.equals(examplesIndex, that.examplesIndex) && Objects.equals(exampleIndex, that.exampleIndex); + } + + @Override + public int hashCode() { + return Objects.hash(document, feature, rule, scenario, examples, example, examplesIndex, exampleIndex); + } +} diff --git a/java/src/main/java/io/cucumber/query/NamingStrategy.java b/java/src/main/java/io/cucumber/query/NamingStrategy.java new file mode 100644 index 00000000..829ddb87 --- /dev/null +++ b/java/src/main/java/io/cucumber/query/NamingStrategy.java @@ -0,0 +1,196 @@ +package io.cucumber.query; + +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.Rule; +import io.cucumber.messages.types.Scenario; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static io.cucumber.query.NamingStrategy.ExampleName.NUMBER; +import static io.cucumber.query.NamingStrategy.FeatureName.INCLUDE; + +/** + * Names {@link Pickle Pickles} in a {@link GherkinDocument}. + *

+ * Pickles in a Gherkin document have a name. But represented + * without the structure of a Gherkin document (e.g. in a flat xml report), + * these names can lose their meaning. The long naming strategy solves this + * problem by prefixing an elements name with the names of all its ancestors, + * optionally including the feature name. + *

+ * Furthermore, Pickles derived from an example can be named in two ways. + * Either by their example number (e.g. {@code Example #3.14}) or by their + * pickle name. If a parameterized pickle name is used, the latter may be + * preferable. + * + *

{@code
+ * Feature: Examples Tables
+ *   Scenario Outline: Eating <eat> cucumbers
+ *     Given there are <start> cucumbers
+ *     When I eat <eat> cucumbers
+ *     Then I should have <left> cucumbers
+ *
+ *     Examples: These are passing
+ *       | start | eat | left |
+ *       |    12 |   5 |    7 |
+ *       |    20 |   6 |   14 |
+ *
+ *     Examples: These are failing
+ *       | start | eat | left |
+ *       |    12 |  20 |    0 |
+ *       |     0 |   1 |    0 |
+ * }
+ * With the long strategy, using example numbers the pickles in this example would be named: + * + *

+ * And with the short strategy, using pickle names: + *

+ */ +public abstract class NamingStrategy { + + public enum Strategy { + /** + * Names an element in a Gherkin document by including all ancestors in the name. + */ + LONG, + + /** + * Names an element in a Gherkin document by using only its name. + */ + SHORT + } + + public enum ExampleName { + /** + * Number examples, for example {@code Example #3.14} + */ + NUMBER, + /** + * Use the name of the pickle associated with the example. + */ + PICKLE + } + + public enum FeatureName { + /** + * When using the {@link Strategy#LONG} include the feature name. + */ + INCLUDE, + /** + * When using the {@link Strategy#LONG} do not include the feature name. + */ + EXCLUDE + } + + public static Builder strategy(Strategy strategy) { + return new Builder(strategy); + } + + private NamingStrategy() { + // Not for public construction. + // Could be made a public interface if GherkinDocumentElements had a better API + } + + abstract String name(GherkinDocumentElements elements, Pickle pickle); + + + private static String exampleNumber(GherkinDocumentElements elements, Integer index) { + String examplesPrefix = elements.examplesIndex() + .map(examplesIndex -> examplesIndex + 1) + .map(examplesIndex -> examplesIndex + ".") + .orElse(""); + return "Example #" + examplesPrefix + (index + 1); + } + + private static String join(List pieces) { + return pieces.stream() + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(" - ")); + } + + private static class ShortNamingStrategy extends NamingStrategy { + private final ExampleName exampleName; + + private ShortNamingStrategy(ExampleName exampleName) { + this.exampleName = exampleName; + } + + String name(GherkinDocumentElements elements, Pickle pickle) { + return elements.exampleIndex() + .filter(index -> exampleName == NUMBER) + .map(index -> exampleNumber(elements, index)) + .orElseGet(pickle::getName); + } + + } + + private static class LongNamingStrategy extends NamingStrategy { + private final FeatureName featureName; + private final ExampleName exampleName; + + private LongNamingStrategy(FeatureName featureName, ExampleName exampleName) { + this.featureName = featureName; + this.exampleName = exampleName; + } + + String name(GherkinDocumentElements elements, Pickle pickle) { + List pieces = new ArrayList<>(); + elements.feature().map(Feature::getName) + .filter(feature -> featureName == INCLUDE) + .ifPresent(pieces::add); + elements.rule().map(Rule::getName) + .ifPresent(pieces::add); + elements.scenario().map(Scenario::getName) + .ifPresent(pieces::add); + elements.examples().map(Examples::getName) + .ifPresent(pieces::add); + elements.exampleIndex() + .map(index -> exampleName == NUMBER ? exampleNumber(elements, index) : pickle.getName()) + .ifPresent(pieces::add); + return join(pieces); + } + } + + public static class Builder { + private final Strategy strategy; + private FeatureName featureName = INCLUDE; + private ExampleName exampleName = NUMBER; + + public Builder(Strategy strategy) { + this.strategy = strategy; + } + + public Builder exampleName(ExampleName exampleName) { + this.exampleName = exampleName; + return this; + } + + public Builder featureName(FeatureName featureName) { + this.featureName = featureName; + return this; + } + + public NamingStrategy build() { + if (strategy == Strategy.SHORT) { + return new ShortNamingStrategy(exampleName); + } + return new LongNamingStrategy(featureName, exampleName); + + } + } +} diff --git a/java/src/main/java/io/cucumber/query/Query.java b/java/src/main/java/io/cucumber/query/Query.java new file mode 100644 index 00000000..6708bbee --- /dev/null +++ b/java/src/main/java/io/cucumber/query/Query.java @@ -0,0 +1,334 @@ +package io.cucumber.query; + +import io.cucumber.messages.Convertor; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.PickleStep; +import io.cucumber.messages.types.Rule; +import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.Step; +import io.cucumber.messages.types.TableRow; +import io.cucumber.messages.types.TestCase; +import io.cucumber.messages.types.TestCaseFinished; +import io.cucumber.messages.types.TestCaseStarted; +import io.cucumber.messages.types.TestRunFinished; +import io.cucumber.messages.types.TestRunStarted; +import io.cucumber.messages.types.TestStep; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import io.cucumber.messages.types.TestStepResultStatus; +import io.cucumber.messages.types.Timestamp; + +import java.time.Duration; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.BiFunction; + +import static java.util.Collections.emptyList; +import static java.util.Comparator.comparing; +import static java.util.Comparator.nullsFirst; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + +/** + * Given one Cucumber Message, find another. + *

+ * This class is effectively a simple in memory database. It can be updated in + * real time through the {@link #update(Envelope)} method. Queries can be made + * while the test run is incomplete - and this will naturally return incomplete + * results. + *

+ * It is safe to query and update this class concurrently. + * + * @see Cucumber Messages - Message Overview + */ +public final class Query { + private final Comparator testStepResultComparator = nullsFirst(comparing(o -> o.getStatus().ordinal())); + private final Deque testCaseStarted = new ConcurrentLinkedDeque<>(); + private final Map testCaseFinishedByTestCaseStartedId = new ConcurrentHashMap<>(); + private final Map> testStepsFinishedByTestCaseStartedId = new ConcurrentHashMap<>(); + private final Map pickleById = new ConcurrentHashMap<>(); + private final Map testCaseById = new ConcurrentHashMap<>(); + private final Map stepById = new ConcurrentHashMap<>(); + private final Map testStepById = new ConcurrentHashMap<>(); + private final Map pickleStepById = new ConcurrentHashMap<>(); + private final Map gherkinAstNodesById = new ConcurrentHashMap<>(); + private TestRunStarted testRunStarted; + private TestRunFinished testRunFinished; + + public Map countMostSevereTestStepResultStatus() { + return findAllTestCaseStarted().stream() + .map(this::findMostSevereTestStepResulBy) + .filter(Optional::isPresent) + .map(Optional::get) + .map(TestStepResult::getStatus) + .collect(groupingBy(identity(), LinkedHashMap::new, counting())); + } + + public int countTestCasesStarted() { + return testCaseStarted.size(); + } + + public List findAllPickles() { + return pickleById.values().stream() + .sorted(comparing(Pickle::getId)) + .collect(toList()); + } + + public List findAllPickleSteps() { + return pickleStepById.values().stream() + .sorted(comparing(PickleStep::getId)) + .collect(toList()); + } + + public List findAllTestCaseStarted() { + return testCaseStarted.stream() + .sorted(comparing(TestCaseStarted::getId)) + .collect(toList()); + } + public List findAllTestSteps() { + return testStepById.values().stream() + .sorted(comparing(TestStep::getId)) + .collect(toList()); + } + + public Map, List> findAllTestCaseStartedGroupedByFeature() { + return findAllTestCaseStarted() + .stream() + .map(testCaseStarted -> { + Optional astNodes = findGherkinAstNodesBy(testCaseStarted); + return new SimpleEntry<>(astNodes, testCaseStarted); + }) + // Sort entries by gherkin document URI for consistent ordering + .sorted(nullsFirst(comparing(entry -> entry.getKey() + .flatMap(nodes -> nodes.document().getUri()) + .orElse(null)))) + .map(entry -> { + // Unpack the now sorted entries + Optional feature = entry.getKey().flatMap(GherkinDocumentElements::feature); + TestCaseStarted testcaseStarted = entry.getValue(); + return new SimpleEntry<>(feature, testcaseStarted); + }) + // Group into a linked hashmap to preserve order + .collect(groupingBy(SimpleEntry::getKey, LinkedHashMap::new, collectingAndThen(toList(), + entries -> entries.stream().map(SimpleEntry::getValue).collect(toList())))); + } + + + public Optional findFeatureBy(TestCaseStarted testCaseStarted) { + return findGherkinAstNodesBy(testCaseStarted).flatMap(GherkinDocumentElements::feature); + } + + public Optional findMostSevereTestStepResulBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + return findTestStepsFinishedBy(testCaseStarted) + .stream() + .map(TestStepFinished::getTestStepResult) + .max(testStepResultComparator); + } + + public String findNameOf(Pickle pickle, NamingStrategy namingStrategy) { + requireNonNull(pickle); + requireNonNull(namingStrategy); + + return findGherkinAstNodesBy(pickle) + .map(gherkinDocumentElements -> namingStrategy.name(gherkinDocumentElements, pickle)) + .orElse(pickle.getName()); + } + + public Optional findPickleBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + return findTestCaseBy(testCaseStarted) + .map(TestCase::getPickleId) + .map(pickleById::get); + } + + public Optional findPickleStepBy(TestStep testStep) { + requireNonNull(testCaseStarted); + return testStep.getPickleStepId() + .map(pickleStepById::get); + } + + public Optional findStepBy(PickleStep pickleStep) { + requireNonNull(pickleStep); + String stepId = pickleStep.getAstNodeIds().get(0); + return ofNullable(stepById.get(stepId)); + } + + public Optional findTestCaseBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + return ofNullable(testCaseById.get(testCaseStarted.getTestCaseId())); + } + + public Optional findTestCaseDurationBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + Timestamp started = testCaseStarted.getTimestamp(); + return findTestCaseFinishedBy(testCaseStarted) + .map(TestCaseFinished::getTimestamp) + .map(finished -> Duration.between( + Convertor.toInstant(started), + Convertor.toInstant(finished) + )); + } + + public Optional findTestCaseFinishedBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + return ofNullable(testCaseFinishedByTestCaseStartedId.get(testCaseStarted.getId())); + } + + public Optional findTestRunDuration() { + if (testRunStarted == null || testRunFinished == null) { + return Optional.empty(); + } + Duration between = Duration.between( + Convertor.toInstant(testRunStarted.getTimestamp()), + Convertor.toInstant(testRunFinished.getTimestamp()) + ); + return Optional.of(between); + } + + public Optional findTestRunFinished() { + return ofNullable(testRunFinished); + } + + public Optional findTestRunStarted() { + return ofNullable(testRunStarted); + } + + public Optional findTestStepBy(TestStepFinished testStepFinished) { + requireNonNull(testStepFinished); + return ofNullable(testStepById.get(testStepFinished.getTestStepId())); + } + + public List findTestStepsFinishedBy(TestCaseStarted testCaseStarted) { + requireNonNull(testCaseStarted); + List testStepsFinished = testStepsFinishedByTestCaseStartedId. + getOrDefault(testCaseStarted.getId(), emptyList()); + // Concurrency + return new ArrayList<>(testStepsFinished); + } + + public List> findTestStepFinishedAndTestStepBy(TestCaseStarted testCaseStarted) { + return findTestStepsFinishedBy(testCaseStarted).stream() + .map(testStepFinished -> findTestStepBy(testStepFinished).map(testStep -> new SimpleEntry<>(testStepFinished, testStep))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toList()); + } + + public void update(Envelope envelope) { + envelope.getTestRunStarted().ifPresent(this::updateTestRunStarted); + envelope.getTestRunFinished().ifPresent(this::updateTestRunFinished); + envelope.getTestCaseStarted().ifPresent(this::updateTestCaseStarted); + envelope.getTestCaseFinished().ifPresent(this::updateTestCaseFinished); + envelope.getTestStepFinished().ifPresent(this::updateTestStepFinished); + envelope.getGherkinDocument().ifPresent(this::updateGherkinDocument); + envelope.getPickle().ifPresent(this::updatePickle); + envelope.getTestCase().ifPresent(this::updateTestCase); + } + + private Optional findGherkinAstNodesBy(Pickle pickle) { + requireNonNull(pickle); + List astNodeIds = pickle.getAstNodeIds(); + String pickleAstNodeId = astNodeIds.get(astNodeIds.size() - 1); + return Optional.ofNullable(gherkinAstNodesById.get(pickleAstNodeId)); + } + + private Optional findGherkinAstNodesBy(TestCaseStarted testCaseStarted) { + return findPickleBy(testCaseStarted) + .flatMap(this::findGherkinAstNodesBy); + } + + private void updateTestCaseStarted(TestCaseStarted testCaseStarted) { + this.testCaseStarted.add(testCaseStarted); + } + + private void updateTestCase(TestCase event) { + this.testCaseById.put(event.getId(), event); + event.getTestSteps().forEach(testStep -> testStepById.put(testStep.getId(), testStep)); + } + + private void updatePickle(Pickle event) { + this.pickleById.put(event.getId(), event); + event.getSteps().forEach(pickleStep -> pickleStepById.put(pickleStep.getId(), pickleStep)); + } + + private void updateGherkinDocument(GherkinDocument document) { + document.getFeature().ifPresent(feature -> updateFeature(document, feature)); + } + + private void updateFeature(GherkinDocument document, Feature feature) { + feature.getChildren() + .forEach(featureChild -> { + featureChild.getBackground().ifPresent(background -> updateSteps(background.getSteps())); + featureChild.getScenario().ifPresent(scenario -> updateScenario(document, feature, null, scenario)); + featureChild.getRule().ifPresent(rule -> rule.getChildren().forEach(ruleChild -> { + ruleChild.getBackground().ifPresent(background -> updateSteps(background.getSteps())); + ruleChild.getScenario().ifPresent(scenario -> updateScenario(document, feature, rule, scenario)); + })); + }); + } + + private void updateSteps(List steps) { + steps.forEach(step -> stepById.put(step.getId(), step)); + } + + private void updateTestStepFinished(TestStepFinished event) { + this.testStepsFinishedByTestCaseStartedId.compute(event.getTestCaseStartedId(), updateList(event)); + } + + private void updateTestCaseFinished(TestCaseFinished event) { + this.testCaseFinishedByTestCaseStartedId.put(event.getTestCaseStartedId(), event); + } + + private void updateTestRunFinished(TestRunFinished event) { + this.testRunFinished = event; + } + + private void updateTestRunStarted(TestRunStarted event) { + this.testRunStarted = event; + } + + private void updateScenario(GherkinDocument document, Feature feature, Rule rule, Scenario scenario) { + this.gherkinAstNodesById.put(scenario.getId(), new GherkinDocumentElements(document, feature, rule, scenario)); + updateSteps(scenario.getSteps()); + + List examples = scenario.getExamples(); + for (int examplesIndex = 0; examplesIndex < examples.size(); examplesIndex++) { + Examples currentExamples = examples.get(examplesIndex); + List tableRows = currentExamples.getTableBody(); + for (int exampleIndex = 0; exampleIndex < tableRows.size(); exampleIndex++) { + TableRow currentExample = tableRows.get(exampleIndex); + gherkinAstNodesById.put(currentExample.getId(), new GherkinDocumentElements(document, feature, rule, scenario, examplesIndex, currentExamples, exampleIndex, currentExample)); + } + } + } + + private BiFunction, List> updateList(E element) { + return (key, existing) -> { + if (existing != null) { + existing.add(element); + return existing; + } + List list = new ArrayList<>(); + list.add(element); + return list; + }; + } +} diff --git a/java/src/test/java/io/cucumber/query/Jackson.java b/java/src/test/java/io/cucumber/query/Jackson.java new file mode 100644 index 00000000..15322366 --- /dev/null +++ b/java/src/test/java/io/cucumber/query/Jackson.java @@ -0,0 +1,30 @@ +package io.cucumber.query; + +import com.fasterxml.jackson.annotation.JsonCreator.Mode; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.ConstructorDetector; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +final class Jackson { + public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new ParameterNamesModule(Mode.PROPERTIES)) + .serializationInclusion(Include.NON_EMPTY) + .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED) + .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .enable(DeserializationFeature.USE_LONG_FOR_INTS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) + .build(); + + private Jackson() { + } +} + diff --git a/java/src/test/java/io/cucumber/query/QueryTest.java b/java/src/test/java/io/cucumber/query/QueryTest.java new file mode 100644 index 00000000..019654f6 --- /dev/null +++ b/java/src/test/java/io/cucumber/query/QueryTest.java @@ -0,0 +1,220 @@ +package io.cucumber.query; + +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import io.cucumber.messages.Convertor; +import io.cucumber.messages.NdjsonToMessageIterable; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.PickleStep; +import io.cucumber.messages.types.Step; +import io.cucumber.messages.types.TestCaseFinished; +import io.cucumber.messages.types.TestCaseStarted; +import io.cucumber.messages.types.TestStep; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static com.fasterxml.jackson.core.util.DefaultIndenter.SYSTEM_LINEFEED_INSTANCE; +import static io.cucumber.query.Jackson.OBJECT_MAPPER; +import static io.cucumber.query.NamingStrategy.ExampleName.PICKLE; +import static io.cucumber.query.NamingStrategy.FeatureName.EXCLUDE; +import static io.cucumber.query.NamingStrategy.Strategy.LONG; +import static io.cucumber.query.NamingStrategy.Strategy.SHORT; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +public class QueryTest { + private static final NdjsonToMessageIterable.Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class); + + static List acceptance() { + + return Stream.of( + Paths.get("../testdata/empty.feature.ndjson"), + Paths.get("../testdata/minimal.feature.ndjson"), + Paths.get("../testdata/rules.feature.ndjson"), + Paths.get("../testdata/examples-tables.feature.ndjson") + ) + .map(TestCase::new) + .sorted(Comparator.comparing(testCase -> testCase.source)) + .collect(toList()); + } + + @ParameterizedTest + @MethodSource("acceptance") + void test(TestCase testCase) throws IOException { + ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream()); + String expected = new String(Files.readAllBytes(testCase.expected), UTF_8); + String actual = new String(bytes.toByteArray(), UTF_8); + assertThat(actual).isEqualTo(expected); + } + + + @ParameterizedTest + @MethodSource("acceptance") + @Disabled + void updateExpectedQueryResultFiles(TestCase testCase) throws IOException { + try (OutputStream out = Files.newOutputStream(testCase.expected)) { + writeQueryResults(testCase, out); + } + } + + private static T writeQueryResults(TestCase testCase, T out) throws IOException { + try (InputStream in = Files.newInputStream(testCase.source)) { + try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) { + Query query = new Query(); + for (Envelope envelope : envelopes) { + query.update(envelope); + } + Map queryResults = createQueryResults(query); + DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() + .withArrayIndenter(SYSTEM_LINEFEED_INSTANCE); + OBJECT_MAPPER.writer(prettyPrinter).writeValue(out, queryResults); + } + } + return out; + } + + private static Map createQueryResults(Query query) { + + Map results = new LinkedHashMap<>(); + + results.put("countMostSevereTestStepResultStatus", query.countMostSevereTestStepResultStatus()); + results.put("countTestCasesStarted", query.countTestCasesStarted()); + results.put("findAllPickles", query.findAllPickles().size()); + results.put("findAllPickleSteps", query.findAllPickleSteps().size()); + results.put("findAllTestCaseStarted", query.findAllTestCaseStarted().size()); + results.put("findAllTestSteps", query.findAllTestSteps().size()); + results.put("findAllTestCaseStartedGroupedByFeature", query.findAllTestCaseStartedGroupedByFeature() + .entrySet() + .stream() + .map(entry -> Arrays.asList(entry.getKey().map(Feature::getName), entry.getValue().stream() + .map(TestCaseStarted::getId) + .collect(toList())))); + results.put("findFeatureBy", query.findAllTestCaseStarted().stream() + .map(query::findFeatureBy) + .map(feature -> feature.map(Feature::getName)) + .collect(toList())); + results.put("findMostSevereTestStepResulBy", query.findAllTestCaseStarted().stream() + .map(query::findMostSevereTestStepResulBy) + .map(testStepResult -> testStepResult.map(TestStepResult::getStatus)) + .collect(toList())); + + Map names = new LinkedHashMap<>(); + names.put("long", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).build())) + .collect(toList())); + names.put("excludeFeatureName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).featureName(EXCLUDE).build())) + .collect(toList())); + names.put("longPickleName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).exampleName(PICKLE).build())) + .collect(toList())); + names.put("short", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).build())) + .collect(toList())); + names.put("shortPickleName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).exampleName(PICKLE).build())) + .collect(toList())); + results.put("findNameOf", names); + + results.put("findPickleBy", query.findAllTestCaseStarted().stream() + .map(query::findPickleBy) + .map(pickle -> pickle.map(Pickle::getName)) + .collect(toList())); + results.put("findPickleStepBy", query.findAllTestSteps().stream() + .map(query::findPickleStepBy) + .map(pickleStep -> pickleStep.map(PickleStep::getText)) + .collect(toList())); + results.put("findStepBy", query.findAllPickleSteps().stream() + .map(query::findStepBy) + .map(step -> step.map(Step::getText)) + .collect(toList())); + results.put("findTestCaseBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseBy) + .map(testCase -> testCase.map(io.cucumber.messages.types.TestCase::getId)) + .collect(toList())); + results.put("findTestCaseDurationBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseDurationBy) + .map(duration -> duration.map(Convertor::toMessage)) + .collect(toList())); + results.put("findTestCaseFinishedBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseFinishedBy) + .map(testCaseFinished -> testCaseFinished.map(TestCaseFinished::getTestCaseStartedId)) + .collect(toList())); + results.put("findTestRunDuration", query.findTestRunDuration() + .map(Convertor::toMessage)); + results.put("findTestRunFinished", query.findTestRunFinished()); + results.put("findTestRunStarted", query.findTestRunStarted()); + results.put("findTestStepBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepsFinishedBy) + .flatMap(Collection::stream) + .map(query::findTestStepBy) + .map(testStep -> testStep.map(TestStep::getId)) + .collect(toList())); + results.put("findTestStepsFinishedBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepsFinishedBy) + .map(testStepFinisheds -> testStepFinisheds.stream().map(TestStepFinished::getTestStepId).collect(toList())) + .collect(toList())); + results.put("findTestStepFinishedAndTestStepBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepFinishedAndTestStepBy) + .flatMap(Collection::stream) + .map(entry -> asList(entry.getKey().getTestStepId(), entry.getValue().getId())) + .collect(toList())); + + return results; + } + + static class TestCase { + private final Path source; + private final Path expected; + + private final String name; + + TestCase(Path source) { + this.source = source; + String fileName = source.getFileName().toString(); + this.name = fileName.substring(0, fileName.lastIndexOf(".ndjson")); + this.expected = source.getParent().resolve(name + ".query-results.json"); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestCase testCase = (TestCase) o; + return source.equals(testCase.source); + } + + @Override + public int hashCode() { + return Objects.hash(source); + } + } + +} diff --git a/testdata/.gitignore b/testdata/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/testdata/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/testdata/README.md b/testdata/README.md new file mode 100644 index 00000000..ed981627 --- /dev/null +++ b/testdata/README.md @@ -0,0 +1,12 @@ +# Acceptance test data + +Query uses the examples from the [cucumber compatibility kit](https://github.com/cucumber/compatibility-kit) +for acceptance testing. These examples consist of `.ndjson` files created by +the [`fake-cucumber` reference implementation](https://github.com/cucumber/fake-cucumber). + +* The `.njdon` files are copied in by running `npm install`. +* The expected `.xml` files are created by running the + `QueryTest#updateExpectedXmlReportFiles` test. + +We ensure the `.ndjson` files stay up to date by running `npm install` in CI +and verifying nothing changed. diff --git a/testdata/attachments.feature.ndjson b/testdata/attachments.feature.ndjson new file mode 100644 index 00000000..66666a66 --- /dev/null +++ b/testdata/attachments.feature.ndjson @@ -0,0 +1,104 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Attachments\n It is sometimes useful to take a screenshot while a scenario runs.\n Or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type\n\n Scenario: Strings can be attached with a media type\n Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.\n\n When the string \"hello\" is attached as \"application/octet-stream\"\n\n Scenario: Log text\n When the string \"hello\" is logged\n\n Scenario: Log ANSI coloured text\n When text with ANSI escapes is logged\n\n Scenario: Log JSON\n When the following string is attached as \"application/json\":\n ```\n {\"message\": \"The big question\", \"foo\": \"bar\"}\n ```\n\n Scenario: Byte arrays are base64-encoded regardless of media type\n When an array with 10 bytes is attached as \"text/plain\"\n\n Scenario: Attaching JPEG images\n When a JPEG image is attached\n\n Scenario: Attaching PNG images\n When a PNG image is attached\n\n Scenario Outline: Attaching images in an examples table\n When a image is attached\n\n Examples:\n | type |\n | JPEG |\n | PNG |\n\n Scenario: Attaching PDFs with a different filename\n When a PDF document is attached and renamed\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/attachments/attachments.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":" Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.","examples":[],"id":"10","keyword":"Scenario","location":{"column":3,"line":12},"name":"Strings can be attached with a media type","steps":[{"id":"9","keyword":"When ","keywordType":"Action","location":{"column":5,"line":16},"text":"the string \"hello\" is attached as \"application/octet-stream\""}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"12","keyword":"Scenario","location":{"column":3,"line":18},"name":"Log text","steps":[{"id":"11","keyword":"When ","keywordType":"Action","location":{"column":5,"line":19},"text":"the string \"hello\" is logged"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"14","keyword":"Scenario","location":{"column":3,"line":21},"name":"Log ANSI coloured text","steps":[{"id":"13","keyword":"When ","keywordType":"Action","location":{"column":5,"line":22},"text":"text with ANSI escapes is logged"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"16","keyword":"Scenario","location":{"column":3,"line":24},"name":"Log JSON","steps":[{"docString":{"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}","delimiter":"```","location":{"column":8,"line":26}},"id":"15","keyword":"When ","keywordType":"Action","location":{"column":6,"line":25},"text":"the following string is attached as \"application/json\":"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"18","keyword":"Scenario","location":{"column":3,"line":30},"name":"Byte arrays are base64-encoded regardless of media type","steps":[{"id":"17","keyword":"When ","keywordType":"Action","location":{"column":5,"line":31},"text":"an array with 10 bytes is attached as \"text/plain\""}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"20","keyword":"Scenario","location":{"column":3,"line":33},"name":"Attaching JPEG images","steps":[{"id":"19","keyword":"When ","keywordType":"Action","location":{"column":5,"line":34},"text":"a JPEG image is attached"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"22","keyword":"Scenario","location":{"column":3,"line":36},"name":"Attaching PNG images","steps":[{"id":"21","keyword":"When ","keywordType":"Action","location":{"column":5,"line":37},"text":"a PNG image is attached"}],"tags":[]}},{"scenario":{"description":"","examples":[{"description":"","id":"27","keyword":"Examples","location":{"column":5,"line":42},"name":"","tableBody":[{"cells":[{"location":{"column":9,"line":44},"value":"JPEG"}],"id":"25","location":{"column":7,"line":44}},{"cells":[{"location":{"column":9,"line":45},"value":"PNG"}],"id":"26","location":{"column":7,"line":45}}],"tableHeader":{"cells":[{"location":{"column":9,"line":43},"value":"type"}],"id":"24","location":{"column":7,"line":43}},"tags":[]}],"id":"28","keyword":"Scenario Outline","location":{"column":3,"line":39},"name":"Attaching images in an examples table","steps":[{"id":"23","keyword":"When ","keywordType":"Action","location":{"column":5,"line":40},"text":"a image is attached"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"30","keyword":"Scenario","location":{"column":3,"line":47},"name":"Attaching PDFs with a different filename","steps":[{"id":"29","keyword":"When ","keywordType":"Action","location":{"column":5,"line":48},"text":"a PDF document is attached and renamed"}],"tags":[]}}],"description":" It is sometimes useful to take a screenshot while a scenario runs.\n Or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Attachments","tags":[]},"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["10"],"id":"32","language":"en","name":"Strings can be attached with a media type","steps":[{"astNodeIds":["9"],"id":"31","text":"the string \"hello\" is attached as \"application/octet-stream\"","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["12"],"id":"34","language":"en","name":"Log text","steps":[{"astNodeIds":["11"],"id":"33","text":"the string \"hello\" is logged","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["14"],"id":"36","language":"en","name":"Log ANSI coloured text","steps":[{"astNodeIds":["13"],"id":"35","text":"text with ANSI escapes is logged","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["16"],"id":"38","language":"en","name":"Log JSON","steps":[{"argument":{"docString":{"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}"}},"astNodeIds":["15"],"id":"37","text":"the following string is attached as \"application/json\":","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["18"],"id":"40","language":"en","name":"Byte arrays are base64-encoded regardless of media type","steps":[{"astNodeIds":["17"],"id":"39","text":"an array with 10 bytes is attached as \"text/plain\"","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["20"],"id":"42","language":"en","name":"Attaching JPEG images","steps":[{"astNodeIds":["19"],"id":"41","text":"a JPEG image is attached","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["22"],"id":"44","language":"en","name":"Attaching PNG images","steps":[{"astNodeIds":["21"],"id":"43","text":"a PNG image is attached","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["28","25"],"id":"46","language":"en","name":"Attaching images in an examples table","steps":[{"astNodeIds":["23","25"],"id":"45","text":"a JPEG image is attached","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["28","26"],"id":"48","language":"en","name":"Attaching images in an examples table","steps":[{"astNodeIds":["23","26"],"id":"47","text":"a PNG image is attached","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"astNodeIds":["30"],"id":"50","language":"en","name":"Attaching PDFs with a different filename","steps":[{"astNodeIds":["29"],"id":"49","text":"a PDF document is attached and renamed","type":"Action"}],"tags":[],"uri":"samples/attachments/attachments.feature"}} +{"stepDefinition":{"id":"1","pattern":{"source":"the string {string} is attached as {string}","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":8},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"the string {string} is logged","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":12},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"text with ANSI escapes is logged","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":16},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"4","pattern":{"source":"the following string is attached as {string}:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":22},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"5","pattern":{"source":"an array with {int} bytes is attached as {string}","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":26},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"6","pattern":{"source":"a JPEG image is attached","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":35},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"7","pattern":{"source":"a PNG image is attached","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":39},"uri":"samples/attachments/attachments.feature.ts"}}} +{"stepDefinition":{"id":"8","pattern":{"source":"a PDF document is attached and renamed","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":43},"uri":"samples/attachments/attachments.feature.ts"}}} +{"hook":{"id":"0","sourceReference":{"location":{"line":6},"uri":"samples/attachments/attachments.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"53","pickleId":"32","testSteps":[{"hookId":"0","id":"51"},{"id":"52","pickleStepId":"31","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[{"children":[{"children":[]}],"start":12,"value":"hello"},{"children":[{"children":[]}]}],"start":11,"value":"\"hello\""},"parameterTypeName":"string"},{"group":{"children":[{"children":[{"children":[]}],"start":35,"value":"application/octet-stream"},{"children":[{"children":[]}]}],"start":34,"value":"\"application/octet-stream\""},"parameterTypeName":"string"}]}]}]}} +{"testCase":{"id":"56","pickleId":"34","testSteps":[{"hookId":"0","id":"54"},{"id":"55","pickleStepId":"33","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[{"children":[{"children":[]}],"start":12,"value":"hello"},{"children":[{"children":[]}]}],"start":11,"value":"\"hello\""},"parameterTypeName":"string"}]}]}]}} +{"testCase":{"id":"59","pickleId":"36","testSteps":[{"hookId":"0","id":"57"},{"id":"58","pickleStepId":"35","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"62","pickleId":"38","testSteps":[{"hookId":"0","id":"60"},{"id":"61","pickleStepId":"37","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[{"children":[{"children":[]}],"start":37,"value":"application/json"},{"children":[{"children":[]}]}],"start":36,"value":"\"application/json\""},"parameterTypeName":"string"}]}]}]}} +{"testCase":{"id":"65","pickleId":"40","testSteps":[{"hookId":"0","id":"63"},{"id":"64","pickleStepId":"39","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"10"},"parameterTypeName":"int"},{"group":{"children":[{"children":[{"children":[]}],"start":39,"value":"text/plain"},{"children":[{"children":[]}]}],"start":38,"value":"\"text/plain\""},"parameterTypeName":"string"}]}]}]}} +{"testCase":{"id":"68","pickleId":"42","testSteps":[{"hookId":"0","id":"66"},{"id":"67","pickleStepId":"41","stepDefinitionIds":["6"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"71","pickleId":"44","testSteps":[{"hookId":"0","id":"69"},{"id":"70","pickleStepId":"43","stepDefinitionIds":["7"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"74","pickleId":"46","testSteps":[{"hookId":"0","id":"72"},{"id":"73","pickleStepId":"45","stepDefinitionIds":["6"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"77","pickleId":"48","testSteps":[{"hookId":"0","id":"75"},{"id":"76","pickleStepId":"47","stepDefinitionIds":["7"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"80","pickleId":"50","testSteps":[{"hookId":"0","id":"78"},{"id":"79","pickleStepId":"49","stepDefinitionIds":["8"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"81","testCaseId":"53","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"81","testStepId":"51","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"81","testStepId":"51","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"81","testStepId":"52","timestamp":{"nanos":4000000,"seconds":0}}} +{"attachment":{"body":"hello","contentEncoding":"IDENTITY","mediaType":"application/octet-stream","testCaseStartedId":"81","testStepId":"52"}} +{"testStepFinished":{"testCaseStartedId":"81","testStepId":"52","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"81","timestamp":{"nanos":6000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"82","testCaseId":"56","timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"82","testStepId":"54","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"82","testStepId":"54","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"82","testStepId":"55","timestamp":{"nanos":10000000,"seconds":0}}} +{"attachment":{"body":"hello","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","testCaseStartedId":"82","testStepId":"55"}} +{"testStepFinished":{"testCaseStartedId":"82","testStepId":"55","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":11000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"82","timestamp":{"nanos":12000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"83","testCaseId":"59","timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"83","testStepId":"57","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"83","testStepId":"57","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"83","testStepId":"58","timestamp":{"nanos":16000000,"seconds":0}}} +{"attachment":{"body":"This displays a \u001b[31mr\u001b[0m\u001b[91ma\u001b[0m\u001b[33mi\u001b[0m\u001b[32mn\u001b[0m\u001b[34mb\u001b[0m\u001b[95mo\u001b[0m\u001b[35mw\u001b[0m","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","testCaseStartedId":"83","testStepId":"58"}} +{"testStepFinished":{"testCaseStartedId":"83","testStepId":"58","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":17000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"83","timestamp":{"nanos":18000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"84","testCaseId":"62","timestamp":{"nanos":19000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"84","testStepId":"60","timestamp":{"nanos":20000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"84","testStepId":"60","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":21000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"84","testStepId":"61","timestamp":{"nanos":22000000,"seconds":0}}} +{"attachment":{"body":"{\"message\": \"The big question\", \"foo\": \"bar\"}","contentEncoding":"IDENTITY","mediaType":"application/json","testCaseStartedId":"84","testStepId":"61"}} +{"testStepFinished":{"testCaseStartedId":"84","testStepId":"61","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":23000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"84","timestamp":{"nanos":24000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"85","testCaseId":"65","timestamp":{"nanos":25000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"85","testStepId":"63","timestamp":{"nanos":26000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"85","testStepId":"63","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":27000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"85","testStepId":"64","timestamp":{"nanos":28000000,"seconds":0}}} +{"attachment":{"body":"AAECAwQFBgcICQ==","contentEncoding":"BASE64","mediaType":"text/plain","testCaseStartedId":"85","testStepId":"64"}} +{"testStepFinished":{"testCaseStartedId":"85","testStepId":"64","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":29000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"85","timestamp":{"nanos":30000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"86","testCaseId":"68","timestamp":{"nanos":31000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"86","testStepId":"66","timestamp":{"nanos":32000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"86","testStepId":"66","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":33000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"86","testStepId":"67","timestamp":{"nanos":34000000,"seconds":0}}} +{"attachment":{"body":"/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wCEAAQEBAQEBAQEBAQGBgUGBggHBwcHCAwJCQkJCQwTDA4MDA4MExEUEA8QFBEeFxUVFx4iHRsdIiolJSo0MjRERFwBBAQEBAQEBAQEBAYGBQYGCAcHBwcIDAkJCQkJDBMMDgwMDgwTERQQDxAUER4XFRUXHiIdGx0iKiUlKjQyNEREXP/CABEIAC4AKQMBIgACEQEDEQH/xAAcAAABBAMBAAAAAAAAAAAAAAAIBAUGBwABAwL/2gAIAQEAAAAAOESYe+lPPw0bK2mvU5gRhNkM/tNMGeuJM5msiEjujvC+s0ApSWvn/8QAFgEBAQEAAAAAAAAAAAAAAAAABQME/9oACAECEAAAADs6pclK4E//xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgT/2gAIAQMQAAAAMJZbKcF1XHit/8QANhAAAQQBAgQDBAcJAAAAAAAAAgEDBAUGABEHEiExEyJREEFCUhRTYXFzgZIVFiMyMzRVY3L/2gAIAQEAAT8AzLMqPBKOReXb6gy3sDbYdXXnS/labH3mWrrMOIWdGb063fxyoPq1XVp8klQ/3v8Aff7E0eCY86fjPtynn99/GclOq5v6782quZnOGmEnEcrmPNN96y1cWTFcH5BUurf5a4bcTKzP6x9QjlBuIKo1YVzq7mwfuJF+IC9y+zPLc8z4kWiuHz1GLuLAht/AU3u+6qfMK+XUuV4TbrTBtFNVoyYZM0RTJE6dO+2+oGcWZY1fzp0URsq5wGuXkUU3dLlHmH1FdYvMs59HCmW7SBKdQiVEHl3Hfyqqe7dNFbOYRlNDnkQlBth9uHaoPZ2C+SCSl9oL1HX0qN9c3+pNY6pkeSG9/XO/sie9fEV5d9Z5FxdbKNKsbeREsUbHZGAVxeQV6Lt8K6gtMPQYzhD43istETjzaC45sm6EaeulzOgC1Kmdkm1KF3wvO2Qjz+m+syECxe7Q+30ZV/NF3TX7dyv5nv06zGpPDOJd/WvAoV+QvHb1znwk8f8AcN/9c3XUuhp5s1qyl17L0poUQDNN+3VN07LqDTZdNg5fLsFdanyxAI4c/wBUSnsGy9B9w6x+kWwrq2blFW2VtHVUF11P4qiC+RT27r9+r6E9kUyiwmDusq8nNMny924zZc7rv3Cia/dSg/xTH6dcQMDpc/oSqbLmZeaNHoUxro9GfHs4C6uoGZYC4cXM6Z+TCb6BdV7avRjH1dEerRagWEO0iNToDyOx3N+Q0RU32XZehbLq4u4VMyByFI33VQI8ZpOZ5416IICnVdcHuHNjUOSs3y5lByGwaRpiL3Svid0b/EL4vavbXDDBM5ymjjRKi3qK2vZ5lOSYOvykRw1Lyhsgawbg9jGGSUtzJ63v1TzWU/zuB+CPZtPb/8QAJREAAgEDBAEEAwAAAAAAAAAAAQIDAAQRBRITIVEUMTJhI0Fx/9oACAECAQE/ALy8eNxb2/z63N4zTy6hbbpJJ9wV9uCdwPWaglFxEkqDGeiPBFSv6bUZJXLhXGQVx3kfdPBbpyvLNyDOAEbsEjOfsVpJ4rUlx83JH8FSwxTqElTI/R9iKGkBJm5X/GGO1R7kV0AABgAYA8Cv/8QAJREAAgIBBAEDBQAAAAAAAAAAAQIDBAUABhESMSFRcRMVIjJB/9oACAEDAQE/AN1bpuJcbFYt+hXgSSDzydG9uLFF7T3yekwjKl+wY8dvHtrAZlMzjo7RAWQHrIvsw1k+2I3LdksmZVcsymPjlg/z/NTU6MIsy2bf1x26hYnHKsy9ufXyB41sWnN9rmlPKrJNyvwBxrL4LH5mMLbj/Nf1dfRhqjsKaa27WZgtRZD1APLsuq1aGpBHXgQLGihVA1//2Q==","contentEncoding":"BASE64","mediaType":"image/jpeg","testCaseStartedId":"86","testStepId":"67"}} +{"testStepFinished":{"testCaseStartedId":"86","testStepId":"67","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":35000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"86","timestamp":{"nanos":36000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"87","testCaseId":"71","timestamp":{"nanos":37000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"87","testStepId":"69","timestamp":{"nanos":38000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"87","testStepId":"69","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":39000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"87","testStepId":"70","timestamp":{"nanos":40000000,"seconds":0}}} +{"attachment":{"body":"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","contentEncoding":"BASE64","mediaType":"image/png","testCaseStartedId":"87","testStepId":"70"}} +{"testStepFinished":{"testCaseStartedId":"87","testStepId":"70","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":41000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"87","timestamp":{"nanos":42000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"88","testCaseId":"74","timestamp":{"nanos":43000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"88","testStepId":"72","timestamp":{"nanos":44000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"88","testStepId":"72","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":45000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"88","testStepId":"73","timestamp":{"nanos":46000000,"seconds":0}}} +{"attachment":{"body":"/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wCEAAQEBAQEBAQEBAQGBgUGBggHBwcHCAwJCQkJCQwTDA4MDA4MExEUEA8QFBEeFxUVFx4iHRsdIiolJSo0MjRERFwBBAQEBAQEBAQEBAYGBQYGCAcHBwcIDAkJCQkJDBMMDgwMDgwTERQQDxAUER4XFRUXHiIdGx0iKiUlKjQyNEREXP/CABEIAC4AKQMBIgACEQEDEQH/xAAcAAABBAMBAAAAAAAAAAAAAAAIBAUGBwABAwL/2gAIAQEAAAAAOESYe+lPPw0bK2mvU5gRhNkM/tNMGeuJM5msiEjujvC+s0ApSWvn/8QAFgEBAQEAAAAAAAAAAAAAAAAABQME/9oACAECEAAAADs6pclK4E//xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgT/2gAIAQMQAAAAMJZbKcF1XHit/8QANhAAAQQBAgQDBAcJAAAAAAAAAgEDBAUGABEHEiExEyJREEFCUhRTYXFzgZIVFiMyMzRVY3L/2gAIAQEAAT8AzLMqPBKOReXb6gy3sDbYdXXnS/labH3mWrrMOIWdGb063fxyoPq1XVp8klQ/3v8Aff7E0eCY86fjPtynn99/GclOq5v6782quZnOGmEnEcrmPNN96y1cWTFcH5BUurf5a4bcTKzP6x9QjlBuIKo1YVzq7mwfuJF+IC9y+zPLc8z4kWiuHz1GLuLAht/AU3u+6qfMK+XUuV4TbrTBtFNVoyYZM0RTJE6dO+2+oGcWZY1fzp0URsq5wGuXkUU3dLlHmH1FdYvMs59HCmW7SBKdQiVEHl3Hfyqqe7dNFbOYRlNDnkQlBth9uHaoPZ2C+SCSl9oL1HX0qN9c3+pNY6pkeSG9/XO/sie9fEV5d9Z5FxdbKNKsbeREsUbHZGAVxeQV6Lt8K6gtMPQYzhD43istETjzaC45sm6EaeulzOgC1Kmdkm1KF3wvO2Qjz+m+syECxe7Q+30ZV/NF3TX7dyv5nv06zGpPDOJd/WvAoV+QvHb1znwk8f8AcN/9c3XUuhp5s1qyl17L0poUQDNN+3VN07LqDTZdNg5fLsFdanyxAI4c/wBUSnsGy9B9w6x+kWwrq2blFW2VtHVUF11P4qiC+RT27r9+r6E9kUyiwmDusq8nNMny924zZc7rv3Cia/dSg/xTH6dcQMDpc/oSqbLmZeaNHoUxro9GfHs4C6uoGZYC4cXM6Z+TCb6BdV7avRjH1dEerRagWEO0iNToDyOx3N+Q0RU32XZehbLq4u4VMyByFI33VQI8ZpOZ5416IICnVdcHuHNjUOSs3y5lByGwaRpiL3Svid0b/EL4vavbXDDBM5ymjjRKi3qK2vZ5lOSYOvykRw1Lyhsgawbg9jGGSUtzJ63v1TzWU/zuB+CPZtPb/8QAJREAAgEDBAEEAwAAAAAAAAAAAQIDAAQRBRITIVEUMTJhI0Fx/9oACAECAQE/ALy8eNxb2/z63N4zTy6hbbpJJ9wV9uCdwPWaglFxEkqDGeiPBFSv6bUZJXLhXGQVx3kfdPBbpyvLNyDOAEbsEjOfsVpJ4rUlx83JH8FSwxTqElTI/R9iKGkBJm5X/GGO1R7kV0AABgAYA8Cv/8QAJREAAgIBBAEDBQAAAAAAAAAAAQIDBAUABhESMSFRcRMVIjJB/9oACAEDAQE/AN1bpuJcbFYt+hXgSSDzydG9uLFF7T3yekwjKl+wY8dvHtrAZlMzjo7RAWQHrIvsw1k+2I3LdksmZVcsymPjlg/z/NTU6MIsy2bf1x26hYnHKsy9ufXyB41sWnN9rmlPKrJNyvwBxrL4LH5mMLbj/Nf1dfRhqjsKaa27WZgtRZD1APLsuq1aGpBHXgQLGihVA1//2Q==","contentEncoding":"BASE64","mediaType":"image/jpeg","testCaseStartedId":"88","testStepId":"73"}} +{"testStepFinished":{"testCaseStartedId":"88","testStepId":"73","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":47000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"88","timestamp":{"nanos":48000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"89","testCaseId":"77","timestamp":{"nanos":49000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"89","testStepId":"75","timestamp":{"nanos":50000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"89","testStepId":"75","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":51000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"89","testStepId":"76","timestamp":{"nanos":52000000,"seconds":0}}} +{"attachment":{"body":"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","contentEncoding":"BASE64","mediaType":"image/png","testCaseStartedId":"89","testStepId":"76"}} +{"testStepFinished":{"testCaseStartedId":"89","testStepId":"76","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":53000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"89","timestamp":{"nanos":54000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"90","testCaseId":"80","timestamp":{"nanos":55000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"90","testStepId":"78","timestamp":{"nanos":56000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"90","testStepId":"78","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":57000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"90","testStepId":"79","timestamp":{"nanos":58000000,"seconds":0}}} +{"attachment":{"body":"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RmlsZTIgOCAwIFI+PgplbmRvYmoKMTAgMCBvYmoKPDwvVHlwZSAvRm9udAovRm9udERlc2NyaXB0b3IgOSAwIFIKL0Jhc2VGb250IC9BQUFBQUErQXJpYWxNVAovU3VidHlwZSAvQ0lERm9udFR5cGUyCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkKL0NJRFN5c3RlbUluZm8gPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKElkZW50aXR5KQovU3VwcGxlbWVudCAwPj4KL1cgWzAgWzc1MF0gNTUgWzYxMC44Mzk4NF0gNzIgWzU1Ni4xNTIzNF0gODcgWzI3Ny44MzIwM11dCi9EVyA1MDA+PgplbmRvYmoKMTEgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDI1MD4+IHN0cmVhbQp4nF2Qy2rEIBSG9z7FWU4Xg0lmMtNFEMqUQha90LQPYPQkFRoVYxZ5+3pJU6ig8PP/n+dCb+1jq5UH+uaM6NDDoLR0OJvFCYQeR6VJWYFUwm8qvWLiltAAd+vscWr1YEjTAND34M7erXB4kKbHO0JfnUSn9AiHz1sXdLdY+40Tag8FYQwkDuGnZ25f+IRAE3ZsZfCVX4+B+Ut8rBahSrrM3QgjcbZcoON6RNIU4TBonsJhBLX851eZ6gfxxV1Mn64hXRT1mUV1vk/qUid2S5W/zF6ivmQos9fTls5+LBqXs08kFufCMGmDaYrYv9K4L9kaG6l4fwAdQH9hCmVuZHN0cmVhbQplbmRvYmoKNCAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMAovQmFzZUZvbnQgL0FBQUFBQStBcmlhbE1UCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFsxMCAwIFJdCi9Ub1VuaWNvZGUgMTEgMCBSPj4KZW5kb2JqCnhyZWYKMCAxMgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDM4MiAwMDAwMCBuIAowMDAwMDAwMTA4IDAwMDAwIG4gCjAwMDAwMDk2MDYgMDAwMDAgbiAKMDAwMDAwMDE0NSAwMDAwMCBuIAowMDAwMDAwNTkwIDAwMDAwIG4gCjAwMDAwMDA2NDUgMDAwMDAgbiAKMDAwMDAwMDY5MiAwMDAwMCBuIAowMDAwMDA4Nzg3IDAwMDAwIG4gCjAwMDAwMDkwMjEgMDAwMDAgbiAKMDAwMDAwOTI4NSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgMTIKL1Jvb3QgNyAwIFIKL0luZm8gMSAwIFI+PgpzdGFydHhyZWYKOTc0NQolJUVPRgo=","contentEncoding":"BASE64","fileName":"renamed.pdf","mediaType":"application/pdf","testCaseStartedId":"90","testStepId":"79"}} +{"testStepFinished":{"testCaseStartedId":"90","testStepId":"79","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":59000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"90","timestamp":{"nanos":60000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":61000000,"seconds":0}}} diff --git a/testdata/cdata.feature.ndjson b/testdata/cdata.feature.ndjson new file mode 100644 index 00000000..e7cf414d --- /dev/null +++ b/testdata/cdata.feature.ndjson @@ -0,0 +1,12 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: cdata\n Cucumber xml formatters should be able to handle xml cdata elements\n\n Scenario: cdata\n Given I have 42 in my belly\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/cdata/cdata.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"2","keyword":"Scenario","location":{"column":3,"line":4},"name":"cdata","steps":[{"id":"1","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":5},"text":"I have 42 in my belly"}],"tags":[]}}],"description":" Cucumber xml formatters should be able to handle xml cdata elements","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"cdata","tags":[]},"uri":"samples/cdata/cdata.feature"}} +{"pickle":{"astNodeIds":["2"],"id":"4","language":"en","name":"cdata","steps":[{"astNodeIds":["1"],"id":"3","text":"I have 42 in my belly","type":"Context"}],"tags":[],"uri":"samples/cdata/cdata.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"I have {int} in my belly","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/cdata/cdata.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"6","pickleId":"4","testSteps":[{"id":"5","pickleStepId":"3","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":7,"value":"42"},"parameterTypeName":"int"}]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"7","testCaseId":"6","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"7","testStepId":"5","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"7","testStepId":"5","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"7","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/testdata/data-tables.feature.ndjson b/testdata/data-tables.feature.ndjson new file mode 100644 index 00000000..c86df805 --- /dev/null +++ b/testdata/data-tables.feature.ndjson @@ -0,0 +1,15 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Data Tables\n Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.\n\n Scenario: transposed table\n When the following table is transposed:\n | a | b |\n | 1 | 2 |\n Then it should be:\n | a | 1 |\n | b | 2 |\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/data-tables/data-tables.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"8","keyword":"Scenario","location":{"column":3,"line":7},"name":"transposed table","steps":[{"dataTable":{"location":{"column":7,"line":9},"rows":[{"cells":[{"location":{"column":9,"line":9},"value":"a"},{"location":{"column":13,"line":9},"value":"b"}],"id":"2","location":{"column":7,"line":9}},{"cells":[{"location":{"column":9,"line":10},"value":"1"},{"location":{"column":13,"line":10},"value":"2"}],"id":"3","location":{"column":7,"line":10}}]},"id":"4","keyword":"When ","keywordType":"Action","location":{"column":5,"line":8},"text":"the following table is transposed:"},{"dataTable":{"location":{"column":7,"line":12},"rows":[{"cells":[{"location":{"column":9,"line":12},"value":"a"},{"location":{"column":13,"line":12},"value":"1"}],"id":"5","location":{"column":7,"line":12}},{"cells":[{"location":{"column":9,"line":13},"value":"b"},{"location":{"column":13,"line":13},"value":"2"}],"id":"6","location":{"column":7,"line":13}}]},"id":"7","keyword":"Then ","keywordType":"Outcome","location":{"column":5,"line":11},"text":"it should be:"}],"tags":[]}}],"description":" Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Data Tables","tags":[]},"uri":"samples/data-tables/data-tables.feature"}} +{"pickle":{"astNodeIds":["8"],"id":"11","language":"en","name":"transposed table","steps":[{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"b"}]},{"cells":[{"value":"1"},{"value":"2"}]}]}},"astNodeIds":["4"],"id":"9","text":"the following table is transposed:","type":"Action"},{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"1"}]},{"cells":[{"value":"b"},{"value":"2"}]}]}},"astNodeIds":["7"],"id":"10","text":"it should be:","type":"Outcome"}],"tags":[],"uri":"samples/data-tables/data-tables.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"the following table is transposed:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":5},"uri":"samples/data-tables/data-tables.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"it should be:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":9},"uri":"samples/data-tables/data-tables.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"14","pickleId":"11","testSteps":[{"id":"12","pickleStepId":"9","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"13","pickleStepId":"10","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"15","testCaseId":"14","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"15","testStepId":"12","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"15","testStepId":"12","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"15","testStepId":"13","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"15","testStepId":"13","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"15","timestamp":{"nanos":6000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":7000000,"seconds":0}}} diff --git a/testdata/empty.feature.ndjson b/testdata/empty.feature.ndjson new file mode 100644 index 00000000..e69de29b diff --git a/testdata/empty.feature.query-results.json b/testdata/empty.feature.query-results.json new file mode 100644 index 00000000..af395f06 --- /dev/null +++ b/testdata/empty.feature.query-results.json @@ -0,0 +1,8 @@ +{ + "countTestCasesStarted" : 0, + "findAllPickles" : 0, + "findAllPickleSteps" : 0, + "findAllTestCaseStarted" : 0, + "findAllTestSteps" : 0, + "findAllTestCaseStartedGroupedByFeature" : [ ] +} \ No newline at end of file diff --git a/testdata/examples-tables.feature.ndjson b/testdata/examples-tables.feature.ndjson new file mode 100644 index 00000000..3e40a3de --- /dev/null +++ b/testdata/examples-tables.feature.ndjson @@ -0,0 +1,68 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Examples Tables\n Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table underneath\n a Scenario, and use in the Scenario which match the table headers.\n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @passing\n Examples: These are passing\n | start | eat | left |\n | 12 | 5 | 7 |\n | 20 | 5 | 15 |\n\n @failing\n Examples: These are failing\n | start | eat | left |\n | 12 | 20 | 0 |\n | 0 | 1 | 0 |\n\n @undefined\n Examples: These are undefined because the value is not an {int}\n | start | eat | left |\n | 12 | banana | 12 |\n | 0 | 1 | apple |\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/examples-tables/examples-tables.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[{"description":"","id":"10","keyword":"Examples","location":{"column":5,"line":12},"name":"These are passing","tableBody":[{"cells":[{"location":{"column":12,"line":14},"value":"12"},{"location":{"column":19,"line":14},"value":"5"},{"location":{"column":26,"line":14},"value":"7"}],"id":"7","location":{"column":7,"line":14}},{"cells":[{"location":{"column":12,"line":15},"value":"20"},{"location":{"column":19,"line":15},"value":"5"},{"location":{"column":25,"line":15},"value":"15"}],"id":"8","location":{"column":7,"line":15}}],"tableHeader":{"cells":[{"location":{"column":9,"line":13},"value":"start"},{"location":{"column":17,"line":13},"value":"eat"},{"location":{"column":23,"line":13},"value":"left"}],"id":"6","location":{"column":7,"line":13}},"tags":[{"id":"9","location":{"column":5,"line":11},"name":"@passing"}]},{"description":"","id":"15","keyword":"Examples","location":{"column":5,"line":18},"name":"These are failing","tableBody":[{"cells":[{"location":{"column":12,"line":20},"value":"12"},{"location":{"column":18,"line":20},"value":"20"},{"location":{"column":26,"line":20},"value":"0"}],"id":"12","location":{"column":7,"line":20}},{"cells":[{"location":{"column":13,"line":21},"value":"0"},{"location":{"column":19,"line":21},"value":"1"},{"location":{"column":26,"line":21},"value":"0"}],"id":"13","location":{"column":7,"line":21}}],"tableHeader":{"cells":[{"location":{"column":9,"line":19},"value":"start"},{"location":{"column":17,"line":19},"value":"eat"},{"location":{"column":23,"line":19},"value":"left"}],"id":"11","location":{"column":7,"line":19}},"tags":[{"id":"14","location":{"column":5,"line":17},"name":"@failing"}]},{"description":"","id":"20","keyword":"Examples","location":{"column":5,"line":24},"name":"These are undefined because the value is not an {int}","tableBody":[{"cells":[{"location":{"column":12,"line":26},"value":"12"},{"location":{"column":17,"line":26},"value":"banana"},{"location":{"column":29,"line":26},"value":"12"}],"id":"17","location":{"column":7,"line":26}},{"cells":[{"location":{"column":13,"line":27},"value":"0"},{"location":{"column":22,"line":27},"value":"1"},{"location":{"column":26,"line":27},"value":"apple"}],"id":"18","location":{"column":7,"line":27}}],"tableHeader":{"cells":[{"location":{"column":9,"line":25},"value":"start"},{"location":{"column":17,"line":25},"value":"eat"},{"location":{"column":26,"line":25},"value":"left"}],"id":"16","location":{"column":7,"line":25}},"tags":[{"id":"19","location":{"column":5,"line":23},"name":"@undefined"}]}],"id":"21","keyword":"Scenario Outline","location":{"column":3,"line":6},"name":"Eating cucumbers","steps":[{"id":"3","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":7},"text":"there are cucumbers"},{"id":"4","keyword":"When ","keywordType":"Action","location":{"column":5,"line":8},"text":"I eat cucumbers"},{"id":"5","keyword":"Then ","keywordType":"Outcome","location":{"column":5,"line":9},"text":"I should have cucumbers"}],"tags":[]}}],"description":" Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table underneath\n a Scenario, and use in the Scenario which match the table headers.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Examples Tables","tags":[]},"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","7"],"id":"25","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","7"],"id":"22","text":"there are 12 cucumbers","type":"Context"},{"astNodeIds":["4","7"],"id":"23","text":"I eat 5 cucumbers","type":"Action"},{"astNodeIds":["5","7"],"id":"24","text":"I should have 7 cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"9","name":"@passing"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","8"],"id":"29","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","8"],"id":"26","text":"there are 20 cucumbers","type":"Context"},{"astNodeIds":["4","8"],"id":"27","text":"I eat 5 cucumbers","type":"Action"},{"astNodeIds":["5","8"],"id":"28","text":"I should have 15 cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"9","name":"@passing"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","12"],"id":"33","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","12"],"id":"30","text":"there are 12 cucumbers","type":"Context"},{"astNodeIds":["4","12"],"id":"31","text":"I eat 20 cucumbers","type":"Action"},{"astNodeIds":["5","12"],"id":"32","text":"I should have 0 cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"14","name":"@failing"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","13"],"id":"37","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","13"],"id":"34","text":"there are 0 cucumbers","type":"Context"},{"astNodeIds":["4","13"],"id":"35","text":"I eat 1 cucumbers","type":"Action"},{"astNodeIds":["5","13"],"id":"36","text":"I should have 0 cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"14","name":"@failing"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","17"],"id":"41","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","17"],"id":"38","text":"there are 12 cucumbers","type":"Context"},{"astNodeIds":["4","17"],"id":"39","text":"I eat banana cucumbers","type":"Action"},{"astNodeIds":["5","17"],"id":"40","text":"I should have 12 cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"19","name":"@undefined"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"astNodeIds":["21","18"],"id":"45","language":"en","name":"Eating cucumbers","steps":[{"astNodeIds":["3","18"],"id":"42","text":"there are 0 cucumbers","type":"Context"},{"astNodeIds":["4","18"],"id":"43","text":"I eat 1 cucumbers","type":"Action"},{"astNodeIds":["5","18"],"id":"44","text":"I should have apple cucumbers","type":"Outcome"}],"tags":[{"astNodeId":"19","name":"@undefined"}],"uri":"samples/examples-tables/examples-tables.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"there are {int} cucumbers","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":4},"uri":"samples/examples-tables/examples-tables.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"I eat {int} cucumbers","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":8},"uri":"samples/examples-tables/examples-tables.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"I should have {int} cucumbers","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":12},"uri":"samples/examples-tables/examples-tables.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"49","pickleId":"25","testSteps":[{"id":"46","pickleStepId":"22","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"12"},"parameterTypeName":"int"}]}]},{"id":"47","pickleStepId":"23","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":6,"value":"5"},"parameterTypeName":"int"}]}]},{"id":"48","pickleStepId":"24","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"7"},"parameterTypeName":"int"}]}]}]}} +{"testCase":{"id":"53","pickleId":"29","testSteps":[{"id":"50","pickleStepId":"26","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"20"},"parameterTypeName":"int"}]}]},{"id":"51","pickleStepId":"27","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":6,"value":"5"},"parameterTypeName":"int"}]}]},{"id":"52","pickleStepId":"28","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"15"},"parameterTypeName":"int"}]}]}]}} +{"testCase":{"id":"57","pickleId":"33","testSteps":[{"id":"54","pickleStepId":"30","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"12"},"parameterTypeName":"int"}]}]},{"id":"55","pickleStepId":"31","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":6,"value":"20"},"parameterTypeName":"int"}]}]},{"id":"56","pickleStepId":"32","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"0"},"parameterTypeName":"int"}]}]}]}} +{"testCase":{"id":"61","pickleId":"37","testSteps":[{"id":"58","pickleStepId":"34","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"0"},"parameterTypeName":"int"}]}]},{"id":"59","pickleStepId":"35","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":6,"value":"1"},"parameterTypeName":"int"}]}]},{"id":"60","pickleStepId":"36","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"0"},"parameterTypeName":"int"}]}]}]}} +{"testCase":{"id":"65","pickleId":"41","testSteps":[{"id":"62","pickleStepId":"38","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"12"},"parameterTypeName":"int"}]}]},{"id":"63","pickleStepId":"39","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"64","pickleStepId":"40","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":14,"value":"12"},"parameterTypeName":"int"}]}]}]}} +{"testCase":{"id":"69","pickleId":"45","testSteps":[{"id":"66","pickleStepId":"42","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":10,"value":"0"},"parameterTypeName":"int"}]}]},{"id":"67","pickleStepId":"43","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":6,"value":"1"},"parameterTypeName":"int"}]}]},{"id":"68","pickleStepId":"44","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}]}} +{"testCaseStarted":{"attempt":0,"id":"70","testCaseId":"49","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"70","testStepId":"46","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"70","testStepId":"46","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"70","testStepId":"47","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"70","testStepId":"47","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"70","testStepId":"48","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"70","testStepId":"48","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"70","timestamp":{"nanos":8000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"71","testCaseId":"53","timestamp":{"nanos":9000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"71","testStepId":"50","timestamp":{"nanos":10000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"71","testStepId":"50","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"71","testStepId":"51","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"71","testStepId":"51","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"71","testStepId":"52","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"71","testStepId":"52","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"71","timestamp":{"nanos":16000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"72","testCaseId":"57","timestamp":{"nanos":17000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"72","testStepId":"54","timestamp":{"nanos":18000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"72","testStepId":"54","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":19000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"72","testStepId":"55","timestamp":{"nanos":20000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"72","testStepId":"55","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":21000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"72","testStepId":"56","timestamp":{"nanos":22000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"72","testStepId":"56","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Expected values to be strictly equal:\n\n-8 !== 0\n","type":"AssertionError"},"message":"Expected values to be strictly equal:\n\n-8 !== 0\n\nsamples/examples-tables/examples-tables.feature:9\nsamples/examples-tables/examples-tables.feature:20","status":"FAILED"},"timestamp":{"nanos":23000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"72","timestamp":{"nanos":24000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"73","testCaseId":"61","timestamp":{"nanos":25000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"73","testStepId":"58","timestamp":{"nanos":26000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"73","testStepId":"58","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":27000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"73","testStepId":"59","timestamp":{"nanos":28000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"73","testStepId":"59","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":29000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"73","testStepId":"60","timestamp":{"nanos":30000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"73","testStepId":"60","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Expected values to be strictly equal:\n\n-1 !== 0\n","type":"AssertionError"},"message":"Expected values to be strictly equal:\n\n-1 !== 0\n\nsamples/examples-tables/examples-tables.feature:9\nsamples/examples-tables/examples-tables.feature:21","status":"FAILED"},"timestamp":{"nanos":31000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"73","timestamp":{"nanos":32000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"74","testCaseId":"65","timestamp":{"nanos":33000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"74","testStepId":"62","timestamp":{"nanos":34000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"74","testStepId":"62","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":35000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"74","testStepId":"63","timestamp":{"nanos":36000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"74","testStepId":"63","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":37000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"74","testStepId":"64","timestamp":{"nanos":38000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"74","testStepId":"64","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":39000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"74","timestamp":{"nanos":40000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"75","testCaseId":"69","timestamp":{"nanos":41000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"75","testStepId":"66","timestamp":{"nanos":42000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"75","testStepId":"66","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":43000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"75","testStepId":"67","timestamp":{"nanos":44000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"75","testStepId":"67","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":45000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"75","testStepId":"68","timestamp":{"nanos":46000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"75","testStepId":"68","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":47000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"75","timestamp":{"nanos":48000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":49000000,"seconds":0}}} diff --git a/testdata/examples-tables.feature.query-results.json b/testdata/examples-tables.feature.query-results.json new file mode 100644 index 00000000..b5129b59 --- /dev/null +++ b/testdata/examples-tables.feature.query-results.json @@ -0,0 +1,316 @@ +{ + "countMostSevereTestStepResultStatus" : { + "PASSED" : 2, + "FAILED" : 2, + "UNDEFINED" : 2 + }, + "countTestCasesStarted" : 6, + "findAllPickles" : 6, + "findAllPickleSteps" : 18, + "findAllTestCaseStarted" : 6, + "findAllTestSteps" : 18, + "findAllTestCaseStartedGroupedByFeature" : [ + [ + "Examples Tables", + [ + "70", + "71", + "72", + "73", + "74", + "75" + ] + ] + ], + "findFeatureBy" : [ + "Examples Tables", + "Examples Tables", + "Examples Tables", + "Examples Tables", + "Examples Tables", + "Examples Tables" + ], + "findMostSevereTestStepResulBy" : [ + "PASSED", + "PASSED", + "FAILED", + "FAILED", + "UNDEFINED", + "UNDEFINED" + ], + "findNameOf" : { + "long" : [ + "Examples Tables - Eating cucumbers - These are passing - Example #1.1", + "Examples Tables - Eating cucumbers - These are passing - Example #1.2", + "Examples Tables - Eating cucumbers - These are failing - Example #2.1", + "Examples Tables - Eating cucumbers - These are failing - Example #2.2", + "Examples Tables - Eating cucumbers - These are undefined because the value is not an {int} - Example #3.1", + "Examples Tables - Eating cucumbers - These are undefined because the value is not an {int} - Example #3.2" + ], + "excludeFeatureName" : [ + "Eating cucumbers - These are passing - Example #1.1", + "Eating cucumbers - These are passing - Example #1.2", + "Eating cucumbers - These are failing - Example #2.1", + "Eating cucumbers - These are failing - Example #2.2", + "Eating cucumbers - These are undefined because the value is not an {int} - Example #3.1", + "Eating cucumbers - These are undefined because the value is not an {int} - Example #3.2" + ], + "longPickleName" : [ + "Examples Tables - Eating cucumbers - These are passing - Eating cucumbers", + "Examples Tables - Eating cucumbers - These are passing - Eating cucumbers", + "Examples Tables - Eating cucumbers - These are failing - Eating cucumbers", + "Examples Tables - Eating cucumbers - These are failing - Eating cucumbers", + "Examples Tables - Eating cucumbers - These are undefined because the value is not an {int} - Eating cucumbers", + "Examples Tables - Eating cucumbers - These are undefined because the value is not an {int} - Eating cucumbers" + ], + "short" : [ + "Example #1.1", + "Example #1.2", + "Example #2.1", + "Example #2.2", + "Example #3.1", + "Example #3.2" + ], + "shortPickleName" : [ + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers" + ] + }, + "findPickleBy" : [ + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers", + "Eating cucumbers" + ], + "findPickleStepBy" : [ + "there are 12 cucumbers", + "I eat 5 cucumbers", + "I should have 7 cucumbers", + "there are 20 cucumbers", + "I eat 5 cucumbers", + "I should have 15 cucumbers", + "there are 12 cucumbers", + "I eat 20 cucumbers", + "I should have 0 cucumbers", + "there are 0 cucumbers", + "I eat 1 cucumbers", + "I should have 0 cucumbers", + "there are 12 cucumbers", + "I eat banana cucumbers", + "I should have 12 cucumbers", + "there are 0 cucumbers", + "I eat 1 cucumbers", + "I should have apple cucumbers" + ], + "findStepBy" : [ + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers", + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers", + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers", + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers", + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers", + "there are cucumbers", + "I eat cucumbers", + "I should have cucumbers" + ], + "findTestCaseBy" : [ + "49", + "53", + "57", + "61", + "65", + "69" + ], + "findTestCaseDurationBy" : [ + { + "seconds" : 0, + "nanos" : 7000000 + }, + { + "seconds" : 0, + "nanos" : 7000000 + }, + { + "seconds" : 0, + "nanos" : 7000000 + }, + { + "seconds" : 0, + "nanos" : 7000000 + }, + { + "seconds" : 0, + "nanos" : 7000000 + }, + { + "seconds" : 0, + "nanos" : 7000000 + } + ], + "findTestCaseFinishedBy" : [ + "70", + "71", + "72", + "73", + "74", + "75" + ], + "findTestRunDuration" : { + "seconds" : 0, + "nanos" : 49000000 + }, + "findTestRunFinished" : { + "success" : false, + "timestamp" : { + "seconds" : 0, + "nanos" : 49000000 + } + }, + "findTestRunStarted" : { + "timestamp" : { + "seconds" : 0, + "nanos" : 0 + } + }, + "findTestStepBy" : [ + "46", + "47", + "48", + "50", + "51", + "52", + "54", + "55", + "56", + "58", + "59", + "60", + "62", + "63", + "64", + "66", + "67", + "68" + ], + "findTestStepsFinishedBy" : [ + [ + "46", + "47", + "48" + ], + [ + "50", + "51", + "52" + ], + [ + "54", + "55", + "56" + ], + [ + "58", + "59", + "60" + ], + [ + "62", + "63", + "64" + ], + [ + "66", + "67", + "68" + ] + ], + "findTestStepFinishedAndTestStepBy" : [ + [ + "46", + "46" + ], + [ + "47", + "47" + ], + [ + "48", + "48" + ], + [ + "50", + "50" + ], + [ + "51", + "51" + ], + [ + "52", + "52" + ], + [ + "54", + "54" + ], + [ + "55", + "55" + ], + [ + "56", + "56" + ], + [ + "58", + "58" + ], + [ + "59", + "59" + ], + [ + "60", + "60" + ], + [ + "62", + "62" + ], + [ + "63", + "63" + ], + [ + "64", + "64" + ], + [ + "66", + "66" + ], + [ + "67", + "67" + ], + [ + "68", + "68" + ] + ] +} \ No newline at end of file diff --git a/testdata/hooks.feature.ndjson b/testdata/hooks.feature.ndjson new file mode 100644 index 00000000..8b405874 --- /dev/null +++ b/testdata/hooks.feature.ndjson @@ -0,0 +1,77 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n\n Scenario: No tags and a undefined step\n When a step does not exist\n\n @some-tag\n Scenario: With a tag, a failure in the hook and a passed step\n When a step passes\n\n @with-attachment\n Scenario: With an tag, an valid attachment in the hook and a passed step\n When a step passes\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/hooks/hooks.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"8","keyword":"Scenario","location":{"column":3,"line":6},"name":"No tags and a passed step","steps":[{"id":"7","keyword":"When ","keywordType":"Action","location":{"column":5,"line":7},"text":"a step passes"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"10","keyword":"Scenario","location":{"column":3,"line":9},"name":"No tags and a failed step","steps":[{"id":"9","keyword":"When ","keywordType":"Action","location":{"column":5,"line":10},"text":"a step fails"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"12","keyword":"Scenario","location":{"column":3,"line":12},"name":"No tags and a undefined step","steps":[{"id":"11","keyword":"When ","keywordType":"Action","location":{"column":5,"line":13},"text":"a step does not exist"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"15","keyword":"Scenario","location":{"column":3,"line":16},"name":"With a tag, a failure in the hook and a passed step","steps":[{"id":"13","keyword":"When ","keywordType":"Action","location":{"column":5,"line":17},"text":"a step passes"}],"tags":[{"id":"14","location":{"column":3,"line":15},"name":"@some-tag"}]}},{"scenario":{"description":"","examples":[],"id":"18","keyword":"Scenario","location":{"column":3,"line":20},"name":"With an tag, an valid attachment in the hook and a passed step","steps":[{"id":"16","keyword":"When ","keywordType":"Action","location":{"column":5,"line":21},"text":"a step passes"}],"tags":[{"id":"17","location":{"column":3,"line":19},"name":"@with-attachment"}]}}],"description":" Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Hooks","tags":[]},"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"astNodeIds":["8"],"id":"20","language":"en","name":"No tags and a passed step","steps":[{"astNodeIds":["7"],"id":"19","text":"a step passes","type":"Action"}],"tags":[],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"astNodeIds":["10"],"id":"22","language":"en","name":"No tags and a failed step","steps":[{"astNodeIds":["9"],"id":"21","text":"a step fails","type":"Action"}],"tags":[],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"astNodeIds":["12"],"id":"24","language":"en","name":"No tags and a undefined step","steps":[{"astNodeIds":["11"],"id":"23","text":"a step does not exist","type":"Action"}],"tags":[],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"astNodeIds":["15"],"id":"26","language":"en","name":"With a tag, a failure in the hook and a passed step","steps":[{"astNodeIds":["13"],"id":"25","text":"a step passes","type":"Action"}],"tags":[{"astNodeId":"14","name":"@some-tag"}],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"astNodeIds":["18"],"id":"28","language":"en","name":"With an tag, an valid attachment in the hook and a passed step","steps":[{"astNodeIds":["16"],"id":"27","text":"a step passes","type":"Action"}],"tags":[{"astNodeId":"17","name":"@with-attachment"}],"uri":"samples/hooks/hooks.feature"}} +{"stepDefinition":{"id":"2","pattern":{"source":"a step passes","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":12},"uri":"samples/hooks/hooks.feature.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"a step fails","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":16},"uri":"samples/hooks/hooks.feature.ts"}}} +{"hook":{"id":"0","sourceReference":{"location":{"line":4},"uri":"samples/hooks/hooks.feature.ts"}}} +{"hook":{"id":"1","name":"A named hook","sourceReference":{"location":{"line":8},"uri":"samples/hooks/hooks.feature.ts"}}} +{"hook":{"id":"4","sourceReference":{"location":{"line":20},"uri":"samples/hooks/hooks.feature.ts"}}} +{"hook":{"id":"5","sourceReference":{"location":{"line":24},"uri":"samples/hooks/hooks.feature.ts"},"tagExpression":"@some-tag or @some-other-tag"}} +{"hook":{"id":"6","sourceReference":{"location":{"line":28},"uri":"samples/hooks/hooks.feature.ts"},"tagExpression":"@with-attachment"}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"33","pickleId":"20","testSteps":[{"hookId":"0","id":"29"},{"hookId":"1","id":"30"},{"id":"31","pickleStepId":"19","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"hookId":"4","id":"32"}]}} +{"testCase":{"id":"38","pickleId":"22","testSteps":[{"hookId":"0","id":"34"},{"hookId":"1","id":"35"},{"id":"36","pickleStepId":"21","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"hookId":"4","id":"37"}]}} +{"testCase":{"id":"43","pickleId":"24","testSteps":[{"hookId":"0","id":"39"},{"hookId":"1","id":"40"},{"id":"41","pickleStepId":"23","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"hookId":"4","id":"42"}]}} +{"testCase":{"id":"49","pickleId":"26","testSteps":[{"hookId":"0","id":"44"},{"hookId":"1","id":"45"},{"id":"46","pickleStepId":"25","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"hookId":"5","id":"47"},{"hookId":"4","id":"48"}]}} +{"testCase":{"id":"55","pickleId":"28","testSteps":[{"hookId":"0","id":"50"},{"hookId":"1","id":"51"},{"id":"52","pickleStepId":"27","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"hookId":"6","id":"53"},{"hookId":"4","id":"54"}]}} +{"testCaseStarted":{"attempt":0,"id":"56","testCaseId":"33","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"29","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"29","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"30","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"30","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"31","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"31","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"32","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"32","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"nanos":10000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"57","testCaseId":"38","timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"34","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"34","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"35","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"35","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"36","timestamp":{"nanos":16000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"36","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/hooks/hooks.feature:10","status":"FAILED"},"timestamp":{"nanos":17000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"37","timestamp":{"nanos":18000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"37","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":19000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"nanos":20000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"58","testCaseId":"43","timestamp":{"nanos":21000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"39","timestamp":{"nanos":22000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"39","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":23000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"40","timestamp":{"nanos":24000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"40","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":25000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"41","timestamp":{"nanos":26000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"41","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":27000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"42","timestamp":{"nanos":28000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"42","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":29000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"58","timestamp":{"nanos":30000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"59","testCaseId":"49","timestamp":{"nanos":31000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"44","timestamp":{"nanos":32000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"44","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":33000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"45","timestamp":{"nanos":34000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"45","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":35000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"46","timestamp":{"nanos":36000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"46","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":37000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"47","timestamp":{"nanos":38000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"47","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in conditional hook","type":"Error"},"message":"Exception in conditional hook\nsamples/hooks/hooks.feature:16","status":"FAILED"},"timestamp":{"nanos":39000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"48","timestamp":{"nanos":40000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"48","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":41000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"59","timestamp":{"nanos":42000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"60","testCaseId":"55","timestamp":{"nanos":43000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"50","timestamp":{"nanos":44000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"50","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":45000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"51","timestamp":{"nanos":46000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"51","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":47000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"52","timestamp":{"nanos":48000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"52","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":49000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"53","timestamp":{"nanos":50000000,"seconds":0}}} +{"attachment":{"body":"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJtbC0zIG1sLW1kLTAiIHZpZXdCb3g9IjAgMCA0MC41OSA0Ni4zMSIgd2lkdGg9IjQwLjU5IiBoZWlnaHQ9IjQ2LjMxIj4KICAgIDxnPgogICAgICAgIDxwYXRoIGZpbGw9IiMyM2Q5NmMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTMwLjI4MyAzLjY0NXEtLjUyOC0uMzE3LTEuMDgtLjU5M2ExNi4xNjQgMTYuMTY0IDAgMDAtMS4xNTQtLjUxOGMtLjEyNC0uMDUyLS4yNDctLjEtLjM3Mi0uMTQ5LS4zNDMtLjEyNy0uNjg5LS4yNjgtMS4wNDItLjM3MWExOS40MjcgMTkuNDI3IDAgMTAtOS43OTIgMzcuNTF2NS41NmMxMS42NzYtMS43NTMgMjIuMDE2LTEwLjk3OSAyMi43ODctMjMuMDkzLjQ1OS03LjI4OS0zLjE5My0xNC43My05LjM0Ny0xOC4zNDZ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZD0iTTE1Ljc4NyA0Ni4zMDd2LTUuOTM1QTIwLjQ3MiAyMC40NzIgMCAxMTI2Ljk1OSAxLjAxNWMuMjc0LjA4LjU1Ny4xODcuODMyLjI5MWwuMjQ4LjA5M2MuMTY1LjA2NC4yOTEuMTEzLjQxNy4xNjcuMzQ4LjEzNy43MzkuMzEzIDEuMjA4LjU0M3EuNTg5LjI5NSAxLjE1My42MzNjNi4zOTMgMy43NTYgMTAuMzU0IDExLjUxOCA5Ljg1NyAxOS4zMTYtLjc2MyAxMi0xMC43MjIgMjIuMTIyLTIzLjY3OSAyNC4wNjd6bTQuOC00NC4yMTRoLS4wMjZhMTguMzY2IDE4LjM2NiAwIDAwLTMuNTI0IDM2LjQwOGwuODUuMTY1djUuMThjMTEuMzkyLTIuMjI0IDIwLjAwOS0xMS4yNzIgMjAuNjg2LTIxLjkyMi40NDgtNy4wMzMtMy4xLTE0LjAxOC04LjgzLTE3LjM4M2wtLjAwOC0uMDA1QTE0LjY5MSAxNC42OTEgMCAwMDI3LjY1NCAzLjVhNS43NCA1Ljc0IDAgMDAtLjM0NC0uMTM4bC0uMjctLjFhOS40OSA5LjQ5IDAgMDAtLjcwOC0uMjQ5IDE4LjQyNSAxOC40MjUgMCAwMC01Ljc0My0uOTJ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTYuNjY2IDEwLjU4YTEuOCAxLjggMCAwMTEuNTgzLjYwOCA0LjE4NCA0LjE4NCAwIDAxLjcyOCAxLjEwN2MuNjQ1IDEuNDIyIDEuMDI3IDMuNDYxLjIzIDQuNjA1YTYuMzM0IDYuMzM0IDAgMDEtMy45ODEtMy4wODcgMy4yMzYgMy4yMzYgMCAwMS0uMzQ3LTEuMzM5IDEuOTU3IDEuOTU3IDAgMDExLjc4Ny0xLjg5NHptLTUuNjgzIDguMDI1YTcuNzQyIDcuNzQyIDAgMDAxLjIxOC43MzcgNS43ODkgNS43ODkgMCAwMDQuODgzLS4xMzggNi4xMTYgNi4xMTYgMCAwMC0zLjM0NS0zLjQ1IDMuNjY0IDMuNjY0IDAgMDAtMS40NDItLjMyMSAxLjg4NCAxLjg4NCAwIDAwLS4zMTkgMCAxLjc2NiAxLjc2NiAwIDAwLS45OTUgMy4xNzJ6bTYuMSAzLjQzM2MtLjc3Ny0uNTE4LTIuMzc5LS4zMDktMy4zMTItLjI5MmE0LjQxNiA0LjQxNiAwIDAwLTEuNjY2LjM1MiAzLjUgMy41IDAgMDAtMS4yMTguNzM4IDEuODE3IDEuODE3IDAgMDAxLjQwOSAzLjE3MSAzLjMgMy4zIDAgMDAxLjQ0Mi0uMzIxYzEuNDM2LS42MiAzLjE0MS0yLjMyIDMuMzQ2LTMuNjQ4em0yLjYxIDJhNi41NTYgNi41NTYgMCAwMC0zLjcyNCAzLjUwNiAzLjA5MSAzLjA5MSAwIDAwLS4zMjEgMS4zMTQgMS45MDcgMS45MDcgMCAwMDMuMyAxLjM0NiA3LjQyMiA3LjQyMiAwIDAwLjctMS4yMThjLjYyMS0xLjMzMy44NjYtMy43Mi4wNDYtNC45NDh6bTIuNTU3LTcuMTY3YTUuOTQxIDUuOTQxIDAgMDAzLjctMy4xNjcgMy4yNDMgMy4yNDMgMCAwMC4zMTktMS4zNDYgMS45MTUgMS45MTUgMCAwMC0xLjc5NC0xLjk1NCAxLjgzMiAxLjgzMiAwIDAwLTEuNi42NDEgNy4zODIgNy4zODIgMCAwMC0uNzA1IDEuMjE4Yy0uNjIgMS40MzQtLjg0MiAzLjQ4LjA4MSA0LjYwM3ptNC4yMDggMTIuMTE1YTMuMjQ0IDMuMjQ0IDAgMDAtLjMyMS0xLjM0NSA1Ljg2OSA1Ljg2OSAwIDAwLTMuNTU0LTMuMjY5IDUuMzg2IDUuMzg2IDAgMDAtLjIyNiA0LjcxMSA0LjE0NyA0LjE0NyAwIDAwLjcgMS4xMjFjMS4xMzMgMS4yMyAzLjUwNS4zMiAzLjQwMi0xLjIxOHptNC4yLTYuMjhhNy40NjYgNy40NjYgMCAwMC0xLjIxNy0uNyA0LjQyNSA0LjQyNSAwIDAwLTEuNjY2LS4zNTIgNi40IDYuNCAwIDAwLTMuMTg4LjU1NSA1Ljk1OSA1Ljk1OSAwIDAwMy4zMTYgMy4zODYgMy42NzIgMy42NzIgMCAwMDEuNDQyLjMyIDEuOCAxLjggMCAwMDEuMzEtMy4yMDl6Ii8+CiAgICA8L2c+Cjwvc3ZnPg==","contentEncoding":"BASE64","mediaType":"image/svg+xml","testCaseStartedId":"60","testStepId":"53"}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"53","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":51000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"54","timestamp":{"nanos":52000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"54","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":53000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"60","timestamp":{"nanos":54000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":55000000,"seconds":0}}} diff --git a/testdata/markdown.feature.md.ndjson b/testdata/markdown.feature.md.ndjson new file mode 100644 index 00000000..8aaa0732 --- /dev/null +++ b/testdata/markdown.feature.md.ndjson @@ -0,0 +1,35 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"# Feature: Cheese\n\nThis table is not picked up by Gherkin (not indented 2+ spaces)\n\n| foo | bar |\n| --- | --- |\n| boz | boo |\n\n\n## Rule: Nom nom nom\n\nI love cheese, especially fromage macaroni cheese. Rubber cheese ricotta caerphilly blue castello who moved my cheese queso bavarian bergkase melted cheese.\n\n### Scenario Outline: Ylajali!\n\n* Given some TypeScript code:\n ```typescript\n type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'\n ```\n* And some classic Gherkin:\n ```gherkin\n Given there are 24 apples in Mary's basket\n ```\n* When we use a data table and attach something and then \n | name | age |\n | ---- | --: |\n | Bill | 3 |\n | Jane | 6 |\n | Isla | 5 |\n* Then this might or might not run\n\n#### Examples: because we need more tables\n\nThis table is indented 2 spaces, so Gherkin will pick it up\n\n | what |\n | ---- |\n | fail |\n | pass |\n\nAnd oh by the way, this table is also ignored by Gherkin because it doesn't have 2+ space indent:\n\n| cheese |\n| -------- |\n| gouda |\n| gamalost |\n","mediaType":"text/x.cucumber.gherkin+markdown","uri":"samples/markdown/markdown.feature.md"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"rule":{"children":[{"scenario":{"description":"","examples":[{"description":"","id":"15","keyword":"Examples","location":{"column":6,"line":32},"name":"because we need more tables","tableBody":[{"cells":[{"location":{"column":5,"line":38},"value":"fail"}],"id":"13","location":{"column":3,"line":38}},{"cells":[{"location":{"column":5,"line":39},"value":"pass"}],"id":"14","location":{"column":3,"line":39}}],"tableHeader":{"cells":[{"location":{"column":5,"line":36},"value":"what"}],"id":"12","location":{"column":3,"line":36}},"tags":[]}],"id":"16","keyword":"Scenario Outline","location":{"column":5,"line":14},"name":"Ylajali!","steps":[{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","delimiter":"```","location":{"column":3,"line":17},"mediaType":"typescript"},"id":"4","keyword":"Given ","keywordType":"Context","location":{"column":3,"line":16},"text":"some TypeScript code:"},{"docString":{"content":"Given there are 24 apples in Mary's basket","delimiter":"```","location":{"column":3,"line":21},"mediaType":"gherkin"},"id":"5","keyword":"And ","keywordType":"Conjunction","location":{"column":3,"line":20},"text":"some classic Gherkin:"},{"dataTable":{"location":{"column":3,"line":25},"rows":[{"cells":[{"location":{"column":5,"line":25},"value":"name"},{"location":{"column":12,"line":25},"value":"age"}],"id":"6","location":{"column":3,"line":25}},{"cells":[{"location":{"column":5,"line":27},"value":"Bill"},{"location":{"column":14,"line":27},"value":"3"}],"id":"7","location":{"column":3,"line":27}},{"cells":[{"location":{"column":5,"line":28},"value":"Jane"},{"location":{"column":14,"line":28},"value":"6"}],"id":"8","location":{"column":3,"line":28}},{"cells":[{"location":{"column":5,"line":29},"value":"Isla"},{"location":{"column":14,"line":29},"value":"5"}],"id":"9","location":{"column":3,"line":29}}]},"id":"10","keyword":"When ","keywordType":"Action","location":{"column":3,"line":24},"text":"we use a data table and attach something and then "},{"id":"11","keyword":"Then ","keywordType":"Outcome","location":{"column":3,"line":30},"text":"this might or might not run"}],"tags":[]}}],"description":"","id":"17","keyword":"Rule","location":{"column":4,"line":10},"name":"Nom nom nom","tags":[]}}],"description":"","keyword":"Feature","language":"en","location":{"column":3,"line":1},"name":"Cheese","tags":[]},"uri":"samples/markdown/markdown.feature.md"}} +{"pickle":{"astNodeIds":["16","13"],"id":"22","language":"en","name":"Ylajali!","steps":[{"argument":{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","mediaType":"typescript"}},"astNodeIds":["4","13"],"id":"18","text":"some TypeScript code:","type":"Context"},{"argument":{"docString":{"content":"Given there are 24 apples in Mary's basket","mediaType":"gherkin"}},"astNodeIds":["5","13"],"id":"19","text":"some classic Gherkin:","type":"Context"},{"argument":{"dataTable":{"rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"Bill"},{"value":"3"}]},{"cells":[{"value":"Jane"},{"value":"6"}]},{"cells":[{"value":"Isla"},{"value":"5"}]}]}},"astNodeIds":["10","13"],"id":"20","text":"we use a data table and attach something and then fail","type":"Action"},{"astNodeIds":["11","13"],"id":"21","text":"this might or might not run","type":"Outcome"}],"tags":[],"uri":"samples/markdown/markdown.feature.md"}} +{"pickle":{"astNodeIds":["16","14"],"id":"27","language":"en","name":"Ylajali!","steps":[{"argument":{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","mediaType":"typescript"}},"astNodeIds":["4","14"],"id":"23","text":"some TypeScript code:","type":"Context"},{"argument":{"docString":{"content":"Given there are 24 apples in Mary's basket","mediaType":"gherkin"}},"astNodeIds":["5","14"],"id":"24","text":"some classic Gherkin:","type":"Context"},{"argument":{"dataTable":{"rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"Bill"},{"value":"3"}]},{"cells":[{"value":"Jane"},{"value":"6"}]},{"cells":[{"value":"Isla"},{"value":"5"}]}]}},"astNodeIds":["10","14"],"id":"25","text":"we use a data table and attach something and then pass","type":"Action"},{"astNodeIds":["11","14"],"id":"26","text":"this might or might not run","type":"Outcome"}],"tags":[],"uri":"samples/markdown/markdown.feature.md"}} +{"stepDefinition":{"id":"0","pattern":{"source":"some TypeScript code:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":4},"uri":"samples/markdown/markdown.feature.md.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"some classic Gherkin:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":8},"uri":"samples/markdown/markdown.feature.md.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"we use a data table and attach something and then {word}","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":12},"uri":"samples/markdown/markdown.feature.md.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"this might or might not run","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":23},"uri":"samples/markdown/markdown.feature.md.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"32","pickleId":"22","testSteps":[{"id":"28","pickleStepId":"18","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"29","pickleStepId":"19","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"30","pickleStepId":"20","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":50,"value":"fail"},"parameterTypeName":"word"}]}]},{"id":"31","pickleStepId":"21","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"37","pickleId":"27","testSteps":[{"id":"33","pickleStepId":"23","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"34","pickleStepId":"24","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"35","pickleStepId":"25","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":50,"value":"pass"},"parameterTypeName":"word"}]}]},{"id":"36","pickleStepId":"26","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"38","testCaseId":"32","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"28","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"28","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"29","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"29","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"30","timestamp":{"nanos":6000000,"seconds":0}}} +{"attachment":{"body":"We are logging some plain text (fail)","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","testCaseStartedId":"38","testStepId":"30"}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"30","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"You asked me to fail","type":"Error"},"message":"You asked me to fail\nsamples/markdown/markdown.feature.md:24\nsamples/markdown/markdown.feature.md:38","status":"FAILED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"31","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"31","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"38","timestamp":{"nanos":10000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"39","testCaseId":"37","timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"33","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"33","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"34","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"34","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"35","timestamp":{"nanos":16000000,"seconds":0}}} +{"attachment":{"body":"We are logging some plain text (pass)","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","testCaseStartedId":"39","testStepId":"35"}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"35","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":17000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"36","timestamp":{"nanos":18000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"36","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":19000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"nanos":20000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":21000000,"seconds":0}}} diff --git a/testdata/minimal.feature.ndjson b/testdata/minimal.feature.ndjson new file mode 100644 index 00000000..647f47b7 --- /dev/null +++ b/testdata/minimal.feature.ndjson @@ -0,0 +1,12 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/minimal/minimal.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"2","keyword":"Scenario","location":{"column":3,"line":9},"name":"cukes","steps":[{"id":"1","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":10},"text":"I have 42 cukes in my belly"}],"tags":[]}}],"description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it\n \n * This is\n * a bullet\n * list","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"minimal","tags":[]},"uri":"samples/minimal/minimal.feature"}} +{"pickle":{"astNodeIds":["2"],"id":"4","language":"en","name":"cukes","steps":[{"astNodeIds":["1"],"id":"3","text":"I have 42 cukes in my belly","type":"Context"}],"tags":[],"uri":"samples/minimal/minimal.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"I have {int} cukes in my belly","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/minimal/minimal.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"6","pickleId":"4","testSteps":[{"id":"5","pickleStepId":"3","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":7,"value":"42"},"parameterTypeName":"int"}]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"7","testCaseId":"6","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"7","testStepId":"5","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"7","testStepId":"5","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"7","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/testdata/minimal.feature.query-results.json b/testdata/minimal.feature.query-results.json new file mode 100644 index 00000000..e50eaca8 --- /dev/null +++ b/testdata/minimal.feature.query-results.json @@ -0,0 +1,93 @@ +{ + "countMostSevereTestStepResultStatus" : { + "PASSED" : 1 + }, + "countTestCasesStarted" : 1, + "findAllPickles" : 1, + "findAllPickleSteps" : 1, + "findAllTestCaseStarted" : 1, + "findAllTestSteps" : 1, + "findAllTestCaseStartedGroupedByFeature" : [ + [ + "minimal", + [ + "7" + ] + ] + ], + "findFeatureBy" : [ + "minimal" + ], + "findMostSevereTestStepResulBy" : [ + "PASSED" + ], + "findNameOf" : { + "long" : [ + "minimal - cukes" + ], + "excludeFeatureName" : [ + "cukes" + ], + "longPickleName" : [ + "minimal - cukes" + ], + "short" : [ + "cukes" + ], + "shortPickleName" : [ + "cukes" + ] + }, + "findPickleBy" : [ + "cukes" + ], + "findPickleStepBy" : [ + "I have 42 cukes in my belly" + ], + "findStepBy" : [ + "I have 42 cukes in my belly" + ], + "findTestCaseBy" : [ + "6" + ], + "findTestCaseDurationBy" : [ + { + "seconds" : 0, + "nanos" : 3000000 + } + ], + "findTestCaseFinishedBy" : [ + "7" + ], + "findTestRunDuration" : { + "seconds" : 0, + "nanos" : 5000000 + }, + "findTestRunFinished" : { + "success" : true, + "timestamp" : { + "seconds" : 0, + "nanos" : 5000000 + } + }, + "findTestRunStarted" : { + "timestamp" : { + "seconds" : 0, + "nanos" : 0 + } + }, + "findTestStepBy" : [ + "5" + ], + "findTestStepsFinishedBy" : [ + [ + "5" + ] + ], + "findTestStepFinishedAndTestStepBy" : [ + [ + "5", + "5" + ] + ] +} \ No newline at end of file diff --git a/testdata/package-lock.json b/testdata/package-lock.json new file mode 100644 index 00000000..53dc59f1 --- /dev/null +++ b/testdata/package-lock.json @@ -0,0 +1,434 @@ +{ + "name": "demo-formatter-testdata", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "demo-formatter-testdata", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC", + "devDependencies": { + "@cucumber/compatibility-kit": "^15.0.0", + "shx": "^0.3.4" + } + }, + "node_modules/@cucumber/compatibility-kit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-15.0.0.tgz", + "integrity": "sha512-5pzgNY0ylsQpcU0CxTBIRlo7+2F1helz/CUMbgE8E7IBxKVQ1czA/WgWtHoeVGm0i0zK5QE7TyDOUL5Nikek0Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", + "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + }, + "dependencies": { + "@cucumber/compatibility-kit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-15.0.0.tgz", + "integrity": "sha512-5pzgNY0ylsQpcU0CxTBIRlo7+2F1helz/CUMbgE8E7IBxKVQ1czA/WgWtHoeVGm0i0zK5QE7TyDOUL5Nikek0Q==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "minimatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", + "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "requires": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/testdata/package.json b/testdata/package.json new file mode 100644 index 00000000..7f1cf369 --- /dev/null +++ b/testdata/package.json @@ -0,0 +1,15 @@ +{ + "name": "demo-formatter-testdata", + "version": "1.0.0", + "description": "The demo formatter uses some test data for acceptance testing.", + "main": "index.js", + "scripts": { + "postinstall": "shx cp node_modules/@cucumber/compatibility-kit/features/**/*.ndjson ." + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@cucumber/compatibility-kit": "^15.0.0", + "shx": "^0.3.4" + } +} diff --git a/testdata/parameter-types.feature.ndjson b/testdata/parameter-types.feature.ndjson new file mode 100644 index 00000000..e6a76360 --- /dev/null +++ b/testdata/parameter-types.feature.ndjson @@ -0,0 +1,13 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Parameter Types\n Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.\n\n Scenario: Flight transformer\n Given LHR-CDG has been delayed\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/parameter-types/parameter-types.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"3","keyword":"Scenario","location":{"column":3,"line":10},"name":"Flight transformer","steps":[{"id":"2","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":11},"text":"LHR-CDG has been delayed"}],"tags":[]}}],"description":" Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Parameter Types","tags":[]},"uri":"samples/parameter-types/parameter-types.feature"}} +{"pickle":{"astNodeIds":["3"],"id":"5","language":"en","name":"Flight transformer","steps":[{"astNodeIds":["2"],"id":"4","text":"LHR-CDG has been delayed","type":"Context"}],"tags":[],"uri":"samples/parameter-types/parameter-types.feature"}} +{"parameterType":{"id":"0","name":"flight","preferForRegularExpressionMatch":false,"regularExpressions":["([A-Z]{3})-([A-Z]{3})"],"sourceReference":{"location":{"line":8},"uri":"samples/parameter-types/parameter-types.feature.ts"},"useForSnippets":true}} +{"stepDefinition":{"id":"1","pattern":{"source":"{flight} has been delayed","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":16},"uri":"samples/parameter-types/parameter-types.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"7","pickleId":"5","testSteps":[{"id":"6","pickleStepId":"4","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[{"children":[],"start":0,"value":"LHR"},{"children":[],"start":4,"value":"CDG"}],"start":0,"value":"LHR-CDG"},"parameterTypeName":"flight"}]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"8","testCaseId":"7","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"6","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"6","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/testdata/pending.feature.ndjson b/testdata/pending.feature.ndjson new file mode 100644 index 00000000..fb2dc441 --- /dev/null +++ b/testdata/pending.feature.ndjson @@ -0,0 +1,30 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Pending steps\n During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.\n\n Scenario: Unimplemented step signals pending status\n Given an unimplemented pending step\n\n Scenario: Steps before unimplemented steps are executed\n Given an implemented non-pending step\n And an unimplemented pending step\n\n Scenario: Steps after unimplemented steps are skipped\n Given an unimplemented pending step\n And an implemented step that is skipped\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/pending/pending.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"4","keyword":"Scenario","location":{"column":3,"line":9},"name":"Unimplemented step signals pending status","steps":[{"id":"3","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":10},"text":"an unimplemented pending step"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"7","keyword":"Scenario","location":{"column":3,"line":12},"name":"Steps before unimplemented steps are executed","steps":[{"id":"5","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":13},"text":"an implemented non-pending step"},{"id":"6","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":14},"text":"an unimplemented pending step"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"10","keyword":"Scenario","location":{"column":3,"line":16},"name":"Steps after unimplemented steps are skipped","steps":[{"id":"8","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":17},"text":"an unimplemented pending step"},{"id":"9","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":18},"text":"an implemented step that is skipped"}],"tags":[]}}],"description":" During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Pending steps","tags":[]},"uri":"samples/pending/pending.feature"}} +{"pickle":{"astNodeIds":["4"],"id":"12","language":"en","name":"Unimplemented step signals pending status","steps":[{"astNodeIds":["3"],"id":"11","text":"an unimplemented pending step","type":"Context"}],"tags":[],"uri":"samples/pending/pending.feature"}} +{"pickle":{"astNodeIds":["7"],"id":"15","language":"en","name":"Steps before unimplemented steps are executed","steps":[{"astNodeIds":["5"],"id":"13","text":"an implemented non-pending step","type":"Context"},{"astNodeIds":["6"],"id":"14","text":"an unimplemented pending step","type":"Context"}],"tags":[],"uri":"samples/pending/pending.feature"}} +{"pickle":{"astNodeIds":["10"],"id":"18","language":"en","name":"Steps after unimplemented steps are skipped","steps":[{"astNodeIds":["8"],"id":"16","text":"an unimplemented pending step","type":"Context"},{"astNodeIds":["9"],"id":"17","text":"an implemented step that is skipped","type":"Context"}],"tags":[],"uri":"samples/pending/pending.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"an implemented non-pending step","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/pending/pending.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"an implemented step that is skipped","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":7},"uri":"samples/pending/pending.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"an unimplemented pending step","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":11},"uri":"samples/pending/pending.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"20","pickleId":"12","testSteps":[{"id":"19","pickleStepId":"11","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"23","pickleId":"15","testSteps":[{"id":"21","pickleStepId":"13","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"22","pickleStepId":"14","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"26","pickleId":"18","testSteps":[{"id":"24","pickleStepId":"16","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"25","pickleStepId":"17","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"27","testCaseId":"20","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"27","testStepId":"19","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"27","testStepId":"19","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"message":"TODO","status":"PENDING"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"27","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"28","testCaseId":"23","timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"28","testStepId":"21","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"28","testStepId":"21","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"28","testStepId":"22","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"28","testStepId":"22","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"message":"TODO","status":"PENDING"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"28","timestamp":{"nanos":10000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"29","testCaseId":"26","timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"29","testStepId":"24","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"29","testStepId":"24","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"message":"TODO","status":"PENDING"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"29","testStepId":"25","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"29","testStepId":"25","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"nanos":16000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":17000000,"seconds":0}}} diff --git a/testdata/retry.feature.ndjson b/testdata/retry.feature.ndjson new file mode 100644 index 00000000..6cac8a51 --- /dev/null +++ b/testdata/retry.feature.ndjson @@ -0,0 +1,59 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Retry\n Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.\n\n Scenario: Test cases that pass aren't retried\n Given a step that always passes\n\n Scenario: Test cases that fail are retried if within the --retry limit\n Given a step that passes the second time\n\n Scenario: Test cases that fail will continue to retry up to the --retry limit\n Given a step that passes the third time\n\n Scenario: Test cases won't retry after failing more than the --retry limit\n Given a step that always fails\n\n Scenario: Test cases won't retry when the status is UNDEFINED\n Given a non-existent step\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/retry/retry.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"5","keyword":"Scenario","location":{"column":3,"line":8},"name":"Test cases that pass aren't retried","steps":[{"id":"4","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":9},"text":"a step that always passes"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"7","keyword":"Scenario","location":{"column":3,"line":11},"name":"Test cases that fail are retried if within the --retry limit","steps":[{"id":"6","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":12},"text":"a step that passes the second time"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"9","keyword":"Scenario","location":{"column":3,"line":14},"name":"Test cases that fail will continue to retry up to the --retry limit","steps":[{"id":"8","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":15},"text":"a step that passes the third time"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"11","keyword":"Scenario","location":{"column":3,"line":17},"name":"Test cases won't retry after failing more than the --retry limit","steps":[{"id":"10","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":18},"text":"a step that always fails"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"13","keyword":"Scenario","location":{"column":3,"line":20},"name":"Test cases won't retry when the status is UNDEFINED","steps":[{"id":"12","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":21},"text":"a non-existent step"}],"tags":[]}}],"description":" Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Retry","tags":[]},"uri":"samples/retry/retry.feature"}} +{"pickle":{"astNodeIds":["5"],"id":"15","language":"en","name":"Test cases that pass aren't retried","steps":[{"astNodeIds":["4"],"id":"14","text":"a step that always passes","type":"Context"}],"tags":[],"uri":"samples/retry/retry.feature"}} +{"pickle":{"astNodeIds":["7"],"id":"17","language":"en","name":"Test cases that fail are retried if within the --retry limit","steps":[{"astNodeIds":["6"],"id":"16","text":"a step that passes the second time","type":"Context"}],"tags":[],"uri":"samples/retry/retry.feature"}} +{"pickle":{"astNodeIds":["9"],"id":"19","language":"en","name":"Test cases that fail will continue to retry up to the --retry limit","steps":[{"astNodeIds":["8"],"id":"18","text":"a step that passes the third time","type":"Context"}],"tags":[],"uri":"samples/retry/retry.feature"}} +{"pickle":{"astNodeIds":["11"],"id":"21","language":"en","name":"Test cases won't retry after failing more than the --retry limit","steps":[{"astNodeIds":["10"],"id":"20","text":"a step that always fails","type":"Context"}],"tags":[],"uri":"samples/retry/retry.feature"}} +{"pickle":{"astNodeIds":["13"],"id":"23","language":"en","name":"Test cases won't retry when the status is UNDEFINED","steps":[{"astNodeIds":["12"],"id":"22","text":"a non-existent step","type":"Context"}],"tags":[],"uri":"samples/retry/retry.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"a step that always passes","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/retry/retry.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"a step that passes the second time","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":8},"uri":"samples/retry/retry.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"a step that passes the third time","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":16},"uri":"samples/retry/retry.feature.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"a step that always fails","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":23},"uri":"samples/retry/retry.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"25","pickleId":"15","testSteps":[{"id":"24","pickleStepId":"14","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"27","pickleId":"17","testSteps":[{"id":"26","pickleStepId":"16","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"29","pickleId":"19","testSteps":[{"id":"28","pickleStepId":"18","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"31","pickleId":"21","testSteps":[{"id":"30","pickleStepId":"20","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"33","pickleId":"23","testSteps":[{"id":"32","pickleStepId":"22","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}]}} +{"testCaseStarted":{"attempt":0,"id":"34","testCaseId":"25","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"34","testStepId":"24","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"34","testStepId":"24","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"34","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"35","testCaseId":"27","timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"35","testStepId":"26","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"35","testStepId":"26","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:12","status":"FAILED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"35","timestamp":{"nanos":8000000,"seconds":0},"willBeRetried":true}} +{"testCaseStarted":{"attempt":1,"id":"36","testCaseId":"27","timestamp":{"nanos":9000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"36","testStepId":"26","timestamp":{"nanos":10000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"36","testStepId":"26","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":11000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"36","timestamp":{"nanos":12000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"37","testCaseId":"29","timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"37","testStepId":"28","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"37","testStepId":"28","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:15","status":"FAILED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"37","timestamp":{"nanos":16000000,"seconds":0},"willBeRetried":true}} +{"testCaseStarted":{"attempt":1,"id":"38","testCaseId":"29","timestamp":{"nanos":17000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"28","timestamp":{"nanos":18000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"28","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:15","status":"FAILED"},"timestamp":{"nanos":19000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"38","timestamp":{"nanos":20000000,"seconds":0},"willBeRetried":true}} +{"testCaseStarted":{"attempt":2,"id":"39","testCaseId":"29","timestamp":{"nanos":21000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"28","timestamp":{"nanos":22000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"28","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":23000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"nanos":24000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"40","testCaseId":"31","timestamp":{"nanos":25000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"30","timestamp":{"nanos":26000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"30","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:18","status":"FAILED"},"timestamp":{"nanos":27000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"40","timestamp":{"nanos":28000000,"seconds":0},"willBeRetried":true}} +{"testCaseStarted":{"attempt":1,"id":"41","testCaseId":"31","timestamp":{"nanos":29000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"41","testStepId":"30","timestamp":{"nanos":30000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"41","testStepId":"30","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:18","status":"FAILED"},"timestamp":{"nanos":31000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"41","timestamp":{"nanos":32000000,"seconds":0},"willBeRetried":true}} +{"testCaseStarted":{"attempt":2,"id":"42","testCaseId":"31","timestamp":{"nanos":33000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"42","testStepId":"30","timestamp":{"nanos":34000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"42","testStepId":"30","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"Exception in step","type":"Error"},"message":"Exception in step\nsamples/retry/retry.feature:18","status":"FAILED"},"timestamp":{"nanos":35000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"42","timestamp":{"nanos":36000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"43","testCaseId":"33","timestamp":{"nanos":37000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"32","timestamp":{"nanos":38000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"32","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":39000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"43","timestamp":{"nanos":40000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":41000000,"seconds":0}}} diff --git a/testdata/rules.feature.ndjson b/testdata/rules.feature.ndjson new file mode 100644 index 00000000..8ad4d5f5 --- /dev/null +++ b/testdata/rules.feature.ndjson @@ -0,0 +1,47 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Usage of a `Rule`\n You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.\n\n Rule: A sale cannot happen if the customer does not have enough money\n # Unhappy path\n Example: Not enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 125 cent chocolate bar\n Then the sale should not happen\n\n # Happy path\n Example: Enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 75 cent chocolate bar\n Then the sale should happen\n\n @some-tag\n Rule: a sale cannot happen if there is no stock\n # Unhappy path\n Example: No chocolates left\n Given the customer has 100 cents\n And there are no chocolate bars in stock\n When the customer tries to buy a 1 cent chocolate bar\n Then the sale should not happen\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/rules/rules.feature"}} +{"gherkinDocument":{"comments":[{"location":{"column":1,"line":8},"text":" # Unhappy path"},{"location":{"column":1,"line":15},"text":" # Happy path"},{"location":{"column":1,"line":24},"text":" # Unhappy path"}],"feature":{"children":[{"rule":{"children":[{"scenario":{"description":"","examples":[],"id":"10","keyword":"Example","location":{"column":5,"line":9},"name":"Not enough money","steps":[{"id":"6","keyword":"Given ","keywordType":"Context","location":{"column":7,"line":10},"text":"the customer has 100 cents"},{"id":"7","keyword":"And ","keywordType":"Conjunction","location":{"column":7,"line":11},"text":"there are chocolate bars in stock"},{"id":"8","keyword":"When ","keywordType":"Action","location":{"column":7,"line":12},"text":"the customer tries to buy a 125 cent chocolate bar"},{"id":"9","keyword":"Then ","keywordType":"Outcome","location":{"column":7,"line":13},"text":"the sale should not happen"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"15","keyword":"Example","location":{"column":5,"line":16},"name":"Enough money","steps":[{"id":"11","keyword":"Given ","keywordType":"Context","location":{"column":7,"line":17},"text":"the customer has 100 cents"},{"id":"12","keyword":"And ","keywordType":"Conjunction","location":{"column":7,"line":18},"text":"there are chocolate bars in stock"},{"id":"13","keyword":"When ","keywordType":"Action","location":{"column":7,"line":19},"text":"the customer tries to buy a 75 cent chocolate bar"},{"id":"14","keyword":"Then ","keywordType":"Outcome","location":{"column":7,"line":20},"text":"the sale should happen"}],"tags":[]}}],"description":"","id":"16","keyword":"Rule","location":{"column":3,"line":7},"name":"A sale cannot happen if the customer does not have enough money","tags":[]}},{"rule":{"children":[{"scenario":{"description":"","examples":[],"id":"21","keyword":"Example","location":{"column":5,"line":25},"name":"No chocolates left","steps":[{"id":"17","keyword":"Given ","keywordType":"Context","location":{"column":7,"line":26},"text":"the customer has 100 cents"},{"id":"18","keyword":"And ","keywordType":"Conjunction","location":{"column":7,"line":27},"text":"there are no chocolate bars in stock"},{"id":"19","keyword":"When ","keywordType":"Action","location":{"column":7,"line":28},"text":"the customer tries to buy a 1 cent chocolate bar"},{"id":"20","keyword":"Then ","keywordType":"Outcome","location":{"column":7,"line":29},"text":"the sale should not happen"}],"tags":[]}}],"description":"","id":"23","keyword":"Rule","location":{"column":3,"line":23},"name":"a sale cannot happen if there is no stock","tags":[{"id":"22","location":{"column":3,"line":22},"name":"@some-tag"}]}}],"description":" You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Usage of a `Rule`","tags":[]},"uri":"samples/rules/rules.feature"}} +{"pickle":{"astNodeIds":["10"],"id":"28","language":"en","name":"Not enough money","steps":[{"astNodeIds":["6"],"id":"24","text":"the customer has 100 cents","type":"Context"},{"astNodeIds":["7"],"id":"25","text":"there are chocolate bars in stock","type":"Context"},{"astNodeIds":["8"],"id":"26","text":"the customer tries to buy a 125 cent chocolate bar","type":"Action"},{"astNodeIds":["9"],"id":"27","text":"the sale should not happen","type":"Outcome"}],"tags":[],"uri":"samples/rules/rules.feature"}} +{"pickle":{"astNodeIds":["15"],"id":"33","language":"en","name":"Enough money","steps":[{"astNodeIds":["11"],"id":"29","text":"the customer has 100 cents","type":"Context"},{"astNodeIds":["12"],"id":"30","text":"there are chocolate bars in stock","type":"Context"},{"astNodeIds":["13"],"id":"31","text":"the customer tries to buy a 75 cent chocolate bar","type":"Action"},{"astNodeIds":["14"],"id":"32","text":"the sale should happen","type":"Outcome"}],"tags":[],"uri":"samples/rules/rules.feature"}} +{"pickle":{"astNodeIds":["21"],"id":"38","language":"en","name":"No chocolates left","steps":[{"astNodeIds":["17"],"id":"34","text":"the customer has 100 cents","type":"Context"},{"astNodeIds":["18"],"id":"35","text":"there are no chocolate bars in stock","type":"Context"},{"astNodeIds":["19"],"id":"36","text":"the customer tries to buy a 1 cent chocolate bar","type":"Action"},{"astNodeIds":["20"],"id":"37","text":"the sale should not happen","type":"Outcome"}],"tags":[{"astNodeId":"22","name":"@some-tag"}],"uri":"samples/rules/rules.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"the customer has {int} cents","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":4},"uri":"samples/rules/rules.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"there are chocolate bars in stock","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":8},"uri":"samples/rules/rules.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"there are no chocolate bars in stock","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":12},"uri":"samples/rules/rules.feature.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"the customer tries to buy a {int} cent chocolate bar","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":16},"uri":"samples/rules/rules.feature.ts"}}} +{"stepDefinition":{"id":"4","pattern":{"source":"the sale should not happen","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":22},"uri":"samples/rules/rules.feature.ts"}}} +{"stepDefinition":{"id":"5","pattern":{"source":"the sale should happen","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":26},"uri":"samples/rules/rules.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"43","pickleId":"28","testSteps":[{"id":"39","pickleStepId":"24","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":17,"value":"100"},"parameterTypeName":"int"}]}]},{"id":"40","pickleStepId":"25","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"41","pickleStepId":"26","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":28,"value":"125"},"parameterTypeName":"int"}]}]},{"id":"42","pickleStepId":"27","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"48","pickleId":"33","testSteps":[{"id":"44","pickleStepId":"29","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":17,"value":"100"},"parameterTypeName":"int"}]}]},{"id":"45","pickleStepId":"30","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"46","pickleStepId":"31","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":28,"value":"75"},"parameterTypeName":"int"}]}]},{"id":"47","pickleStepId":"32","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"53","pickleId":"38","testSteps":[{"id":"49","pickleStepId":"34","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":17,"value":"100"},"parameterTypeName":"int"}]}]},{"id":"50","pickleStepId":"35","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"51","pickleStepId":"36","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":28,"value":"1"},"parameterTypeName":"int"}]}]},{"id":"52","pickleStepId":"37","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"54","testCaseId":"43","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"39","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"39","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"40","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"40","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"41","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"41","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"42","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"42","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"54","timestamp":{"nanos":10000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"55","testCaseId":"48","timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"44","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"44","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"45","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"45","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"46","timestamp":{"nanos":16000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"46","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":17000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"47","timestamp":{"nanos":18000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"47","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":19000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"nanos":20000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"56","testCaseId":"53","timestamp":{"nanos":21000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"nanos":22000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":23000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"50","timestamp":{"nanos":24000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"50","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":25000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"51","timestamp":{"nanos":26000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"51","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":27000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"52","timestamp":{"nanos":28000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"52","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":29000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"nanos":30000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":31000000,"seconds":0}}} diff --git a/testdata/rules.feature.query-results.json b/testdata/rules.feature.query-results.json new file mode 100644 index 00000000..6de5353c --- /dev/null +++ b/testdata/rules.feature.query-results.json @@ -0,0 +1,215 @@ +{ + "countMostSevereTestStepResultStatus" : { + "PASSED" : 3 + }, + "countTestCasesStarted" : 3, + "findAllPickles" : 3, + "findAllPickleSteps" : 12, + "findAllTestCaseStarted" : 3, + "findAllTestSteps" : 12, + "findAllTestCaseStartedGroupedByFeature" : [ + [ + "Usage of a `Rule`", + [ + "54", + "55", + "56" + ] + ] + ], + "findFeatureBy" : [ + "Usage of a `Rule`", + "Usage of a `Rule`", + "Usage of a `Rule`" + ], + "findMostSevereTestStepResulBy" : [ + "PASSED", + "PASSED", + "PASSED" + ], + "findNameOf" : { + "long" : [ + "Usage of a `Rule` - A sale cannot happen if the customer does not have enough money - Not enough money", + "Usage of a `Rule` - A sale cannot happen if the customer does not have enough money - Enough money", + "Usage of a `Rule` - a sale cannot happen if there is no stock - No chocolates left" + ], + "excludeFeatureName" : [ + "A sale cannot happen if the customer does not have enough money - Not enough money", + "A sale cannot happen if the customer does not have enough money - Enough money", + "a sale cannot happen if there is no stock - No chocolates left" + ], + "longPickleName" : [ + "Usage of a `Rule` - A sale cannot happen if the customer does not have enough money - Not enough money", + "Usage of a `Rule` - A sale cannot happen if the customer does not have enough money - Enough money", + "Usage of a `Rule` - a sale cannot happen if there is no stock - No chocolates left" + ], + "short" : [ + "Not enough money", + "Enough money", + "No chocolates left" + ], + "shortPickleName" : [ + "Not enough money", + "Enough money", + "No chocolates left" + ] + }, + "findPickleBy" : [ + "Not enough money", + "Enough money", + "No chocolates left" + ], + "findPickleStepBy" : [ + "the customer has 100 cents", + "there are chocolate bars in stock", + "the customer tries to buy a 125 cent chocolate bar", + "the sale should not happen", + "the customer has 100 cents", + "there are chocolate bars in stock", + "the customer tries to buy a 75 cent chocolate bar", + "the sale should happen", + "the customer has 100 cents", + "there are no chocolate bars in stock", + "the customer tries to buy a 1 cent chocolate bar", + "the sale should not happen" + ], + "findStepBy" : [ + "the customer has 100 cents", + "there are chocolate bars in stock", + "the customer tries to buy a 125 cent chocolate bar", + "the sale should not happen", + "the customer has 100 cents", + "there are chocolate bars in stock", + "the customer tries to buy a 75 cent chocolate bar", + "the sale should happen", + "the customer has 100 cents", + "there are no chocolate bars in stock", + "the customer tries to buy a 1 cent chocolate bar", + "the sale should not happen" + ], + "findTestCaseBy" : [ + "43", + "48", + "53" + ], + "findTestCaseDurationBy" : [ + { + "seconds" : 0, + "nanos" : 9000000 + }, + { + "seconds" : 0, + "nanos" : 9000000 + }, + { + "seconds" : 0, + "nanos" : 9000000 + } + ], + "findTestCaseFinishedBy" : [ + "54", + "55", + "56" + ], + "findTestRunDuration" : { + "seconds" : 0, + "nanos" : 31000000 + }, + "findTestRunFinished" : { + "success" : true, + "timestamp" : { + "seconds" : 0, + "nanos" : 31000000 + } + }, + "findTestRunStarted" : { + "timestamp" : { + "seconds" : 0, + "nanos" : 0 + } + }, + "findTestStepBy" : [ + "39", + "40", + "41", + "42", + "44", + "45", + "46", + "47", + "49", + "50", + "51", + "52" + ], + "findTestStepsFinishedBy" : [ + [ + "39", + "40", + "41", + "42" + ], + [ + "44", + "45", + "46", + "47" + ], + [ + "49", + "50", + "51", + "52" + ] + ], + "findTestStepFinishedAndTestStepBy" : [ + [ + "39", + "39" + ], + [ + "40", + "40" + ], + [ + "41", + "41" + ], + [ + "42", + "42" + ], + [ + "44", + "44" + ], + [ + "45", + "45" + ], + [ + "46", + "46" + ], + [ + "47", + "47" + ], + [ + "49", + "49" + ], + [ + "50", + "50" + ], + [ + "51", + "51" + ], + [ + "52", + "52" + ] + ] +} \ No newline at end of file diff --git a/testdata/skipped.feature.ndjson b/testdata/skipped.feature.ndjson new file mode 100644 index 00000000..fd87b9ef --- /dev/null +++ b/testdata/skipped.feature.ndjson @@ -0,0 +1,33 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Skipping scenarios\n\n Hooks and step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.\n\n @skip\n Scenario: Skipping from a Before hook\n Given a step that is skipped\n\n Scenario: Skipping from a step doesn't affect the previous steps\n Given a step that does not skip\n And I skip a step\n\n Scenario: Skipping from a step causes the rest of the scenario to be skipped\n Given I skip a step\n And a step that is skipped\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/skipped/skipped.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"6","keyword":"Scenario","location":{"column":3,"line":10},"name":"Skipping from a Before hook","steps":[{"id":"4","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":11},"text":"a step that is skipped"}],"tags":[{"id":"5","location":{"column":3,"line":9},"name":"@skip"}]}},{"scenario":{"description":"","examples":[],"id":"9","keyword":"Scenario","location":{"column":3,"line":13},"name":"Skipping from a step doesn't affect the previous steps","steps":[{"id":"7","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":14},"text":"a step that does not skip"},{"id":"8","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":15},"text":"I skip a step"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"12","keyword":"Scenario","location":{"column":3,"line":17},"name":"Skipping from a step causes the rest of the scenario to be skipped","steps":[{"id":"10","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":18},"text":"I skip a step"},{"id":"11","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":19},"text":"a step that is skipped"}],"tags":[]}}],"description":" Hooks and step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Skipping scenarios","tags":[]},"uri":"samples/skipped/skipped.feature"}} +{"pickle":{"astNodeIds":["6"],"id":"14","language":"en","name":"Skipping from a Before hook","steps":[{"astNodeIds":["4"],"id":"13","text":"a step that is skipped","type":"Context"}],"tags":[{"astNodeId":"5","name":"@skip"}],"uri":"samples/skipped/skipped.feature"}} +{"pickle":{"astNodeIds":["9"],"id":"17","language":"en","name":"Skipping from a step doesn't affect the previous steps","steps":[{"astNodeIds":["7"],"id":"15","text":"a step that does not skip","type":"Context"},{"astNodeIds":["8"],"id":"16","text":"I skip a step","type":"Context"}],"tags":[],"uri":"samples/skipped/skipped.feature"}} +{"pickle":{"astNodeIds":["12"],"id":"20","language":"en","name":"Skipping from a step causes the rest of the scenario to be skipped","steps":[{"astNodeIds":["10"],"id":"18","text":"I skip a step","type":"Context"},{"astNodeIds":["11"],"id":"19","text":"a step that is skipped","type":"Context"}],"tags":[],"uri":"samples/skipped/skipped.feature"}} +{"stepDefinition":{"id":"1","pattern":{"source":"a step that does not skip","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":7},"uri":"samples/skipped/skipped.feature.ts"}}} +{"stepDefinition":{"id":"2","pattern":{"source":"a step that is skipped","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":11},"uri":"samples/skipped/skipped.feature.ts"}}} +{"stepDefinition":{"id":"3","pattern":{"source":"I skip a step","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":15},"uri":"samples/skipped/skipped.feature.ts"}}} +{"hook":{"id":"0","sourceReference":{"location":{"line":3},"uri":"samples/skipped/skipped.feature.ts"},"tagExpression":"@skip"}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"23","pickleId":"14","testSteps":[{"hookId":"0","id":"21"},{"id":"22","pickleStepId":"13","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"26","pickleId":"17","testSteps":[{"id":"24","pickleStepId":"15","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"25","pickleStepId":"16","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCase":{"id":"29","pickleId":"20","testSteps":[{"id":"27","pickleStepId":"18","stepDefinitionIds":["3"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"28","pickleStepId":"19","stepDefinitionIds":["2"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"30","testCaseId":"23","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"30","testStepId":"21","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"30","testStepId":"21","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"30","testStepId":"22","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"30","testStepId":"22","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"nanos":6000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"31","testCaseId":"26","timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"31","testStepId":"24","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"31","testStepId":"24","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"31","testStepId":"25","timestamp":{"nanos":10000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"31","testStepId":"25","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":11000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"31","timestamp":{"nanos":12000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"32","testCaseId":"29","timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"32","testStepId":"27","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"32","testStepId":"27","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"32","testStepId":"28","timestamp":{"nanos":16000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"32","testStepId":"28","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":17000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"32","timestamp":{"nanos":18000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"timestamp":{"nanos":19000000,"seconds":0}}} diff --git a/testdata/stack-traces.feature.ndjson b/testdata/stack-traces.feature.ndjson new file mode 100644 index 00000000..b11bcc16 --- /dev/null +++ b/testdata/stack-traces.feature.ndjson @@ -0,0 +1,12 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Stack traces\n Stack traces can help you diagnose the source of a bug.\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default\n\n The first line of the stack trace will contain a reference to the feature file.\n\n Scenario: A failing step\n When a step throws an exception\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/stack-traces/stack-traces.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"2","keyword":"Scenario","location":{"column":3,"line":8},"name":"A failing step","steps":[{"id":"1","keyword":"When ","keywordType":"Action","location":{"column":5,"line":9},"text":"a step throws an exception"}],"tags":[]}}],"description":" Stack traces can help you diagnose the source of a bug.\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default\n\n The first line of the stack trace will contain a reference to the feature file.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Stack traces","tags":[]},"uri":"samples/stack-traces/stack-traces.feature"}} +{"pickle":{"astNodeIds":["2"],"id":"4","language":"en","name":"A failing step","steps":[{"astNodeIds":["1"],"id":"3","text":"a step throws an exception","type":"Action"}],"tags":[],"uri":"samples/stack-traces/stack-traces.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"a step throws an exception","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/stack-traces/stack-traces.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"6","pickleId":"4","testSteps":[{"id":"5","pickleStepId":"3","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"7","testCaseId":"6","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"7","testStepId":"5","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"7","testStepId":"5","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"exception":{"message":"BOOM","type":"Error"},"message":"BOOM\nsamples/stack-traces/stack-traces.feature:9","status":"FAILED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"7","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/testdata/undefined.feature.ndjson b/testdata/undefined.feature.ndjson new file mode 100644 index 00000000..88402985 --- /dev/null +++ b/testdata/undefined.feature.ndjson @@ -0,0 +1,29 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Undefined steps\n\n At runtime, Cucumber may encounter a step in a scenario that it cannot match to a\n step definition. In these cases, the scenario is not able to run and so the step status\n will be UNDEFINED, with subsequent steps being SKIPPED and the overall result will be FAILURE\n\n Scenario: An undefined step causes a failure\n Given a step that is yet to be defined\n\n Scenario: Steps before undefined steps are executed\n Given an implemented step\n And a step that is yet to be defined\n\n Scenario: Steps after undefined steps are skipped\n Given a step that is yet to be defined\n And a step that will be skipped\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/undefined/undefined.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"3","keyword":"Scenario","location":{"column":3,"line":7},"name":"An undefined step causes a failure","steps":[{"id":"2","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":8},"text":"a step that is yet to be defined"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"6","keyword":"Scenario","location":{"column":3,"line":10},"name":"Steps before undefined steps are executed","steps":[{"id":"4","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":11},"text":"an implemented step"},{"id":"5","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":12},"text":"a step that is yet to be defined"}],"tags":[]}},{"scenario":{"description":"","examples":[],"id":"9","keyword":"Scenario","location":{"column":3,"line":14},"name":"Steps after undefined steps are skipped","steps":[{"id":"7","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":15},"text":"a step that is yet to be defined"},{"id":"8","keyword":"And ","keywordType":"Conjunction","location":{"column":5,"line":16},"text":"a step that will be skipped"}],"tags":[]}}],"description":" At runtime, Cucumber may encounter a step in a scenario that it cannot match to a\n step definition. In these cases, the scenario is not able to run and so the step status\n will be UNDEFINED, with subsequent steps being SKIPPED and the overall result will be FAILURE","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Undefined steps","tags":[]},"uri":"samples/undefined/undefined.feature"}} +{"pickle":{"astNodeIds":["3"],"id":"11","language":"en","name":"An undefined step causes a failure","steps":[{"astNodeIds":["2"],"id":"10","text":"a step that is yet to be defined","type":"Context"}],"tags":[],"uri":"samples/undefined/undefined.feature"}} +{"pickle":{"astNodeIds":["6"],"id":"14","language":"en","name":"Steps before undefined steps are executed","steps":[{"astNodeIds":["4"],"id":"12","text":"an implemented step","type":"Context"},{"astNodeIds":["5"],"id":"13","text":"a step that is yet to be defined","type":"Context"}],"tags":[],"uri":"samples/undefined/undefined.feature"}} +{"pickle":{"astNodeIds":["9"],"id":"17","language":"en","name":"Steps after undefined steps are skipped","steps":[{"astNodeIds":["7"],"id":"15","text":"a step that is yet to be defined","type":"Context"},{"astNodeIds":["8"],"id":"16","text":"a step that will be skipped","type":"Context"}],"tags":[],"uri":"samples/undefined/undefined.feature"}} +{"stepDefinition":{"id":"0","pattern":{"source":"an implemented step","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"samples/undefined/undefined.feature.ts"}}} +{"stepDefinition":{"id":"1","pattern":{"source":"a step that will be skipped","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":7},"uri":"samples/undefined/undefined.feature.ts"}}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"19","pickleId":"11","testSteps":[{"id":"18","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}]}} +{"testCase":{"id":"22","pickleId":"14","testSteps":[{"id":"20","pickleStepId":"12","stepDefinitionIds":["0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"21","pickleStepId":"13","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}]}} +{"testCase":{"id":"25","pickleId":"17","testSteps":[{"id":"23","pickleStepId":"15","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"24","pickleStepId":"16","stepDefinitionIds":["1"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"26","testCaseId":"19","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"26","testStepId":"18","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"26","testStepId":"18","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"26","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"27","testCaseId":"22","timestamp":{"nanos":5000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"27","testStepId":"20","timestamp":{"nanos":6000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"27","testStepId":"20","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":7000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"27","testStepId":"21","timestamp":{"nanos":8000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"27","testStepId":"21","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":9000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"27","timestamp":{"nanos":10000000,"seconds":0},"willBeRetried":false}} +{"testCaseStarted":{"attempt":0,"id":"28","testCaseId":"25","timestamp":{"nanos":11000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"28","testStepId":"23","timestamp":{"nanos":12000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"28","testStepId":"23","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":13000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"28","testStepId":"24","timestamp":{"nanos":14000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"28","testStepId":"24","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"SKIPPED"},"timestamp":{"nanos":15000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"28","timestamp":{"nanos":16000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":17000000,"seconds":0}}} diff --git a/testdata/unknown-parameter-type.feature.ndjson b/testdata/unknown-parameter-type.feature.ndjson new file mode 100644 index 00000000..d02795f2 --- /dev/null +++ b/testdata/unknown-parameter-type.feature.ndjson @@ -0,0 +1,12 @@ +{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.4.0"},"os":{"name":"darwin","version":"23.0.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"20.8.0"}}} +{"source":{"data":"Feature: Parameter Types\n Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run.\n\n Scenario: undefined parameter type\n Given CDG is closed because of a strike\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"1","keyword":"Scenario","location":{"column":3,"line":5},"name":"undefined parameter type","steps":[{"id":"0","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":6},"text":"CDG is closed because of a strike"}],"tags":[]}}],"description":" Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Parameter Types","tags":[]},"uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}} +{"pickle":{"astNodeIds":["1"],"id":"3","language":"en","name":"undefined parameter type","steps":[{"astNodeIds":["0"],"id":"2","text":"CDG is closed because of a strike","type":"Context"}],"tags":[],"uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}} +{"undefinedParameterType":{"expression":"{airport} is closed because of a strike","name":"airport"}} +{"testRunStarted":{"timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"5","pickleId":"3","testSteps":[{"id":"4","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}]}} +{"testCaseStarted":{"attempt":0,"id":"6","testCaseId":"5","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"6","testStepId":"4","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"6","testStepId":"4","testStepResult":{"duration":{"nanos":0,"seconds":0},"status":"UNDEFINED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"6","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":false,"timestamp":{"nanos":5000000,"seconds":0}}} From 41b28466d0d0ee1306afbf86a84c8964cd291fbb Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 5 Apr 2024 02:51:26 +0200 Subject: [PATCH 2/4] Retain insertion order for test case started events --- .../main/java/io/cucumber/query/Query.java | 13 +- .../cucumber/query/QueryAcceptanceTest.java | 220 ++++++++++++++++++ .../java/io/cucumber/query/QueryTest.java | 218 ++--------------- 3 files changed, 244 insertions(+), 207 deletions(-) create mode 100644 java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java diff --git a/java/src/main/java/io/cucumber/query/Query.java b/java/src/main/java/io/cucumber/query/Query.java index 6708bbee..b3c7e110 100644 --- a/java/src/main/java/io/cucumber/query/Query.java +++ b/java/src/main/java/io/cucumber/query/Query.java @@ -98,10 +98,9 @@ public List findAllPickleSteps() { } public List findAllTestCaseStarted() { - return testCaseStarted.stream() - .sorted(comparing(TestCaseStarted::getId)) - .collect(toList()); + return new ArrayList<>(testCaseStarted); } + public List findAllTestSteps() { return testStepById.values().stream() .sorted(comparing(TestStep::getId)) @@ -109,7 +108,7 @@ public List findAllTestSteps() { } public Map, List> findAllTestCaseStartedGroupedByFeature() { - return findAllTestCaseStarted() + List, TestCaseStarted>> collect = findAllTestCaseStarted() .stream() .map(testCaseStarted -> { Optional astNodes = findGherkinAstNodesBy(testCaseStarted); @@ -119,6 +118,9 @@ public Map, List> findAllTestCaseStartedGroup .sorted(nullsFirst(comparing(entry -> entry.getKey() .flatMap(nodes -> nodes.document().getUri()) .orElse(null)))) + .collect(toList()); + return collect + .stream() .map(entry -> { // Unpack the now sorted entries Optional feature = entry.getKey().flatMap(GherkinDocumentElements::feature); @@ -127,7 +129,8 @@ public Map, List> findAllTestCaseStartedGroup }) // Group into a linked hashmap to preserve order .collect(groupingBy(SimpleEntry::getKey, LinkedHashMap::new, collectingAndThen(toList(), - entries -> entries.stream().map(SimpleEntry::getValue).collect(toList())))); + entries -> entries.stream().map(SimpleEntry::getValue) + .collect(toList())))); } diff --git a/java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java b/java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java new file mode 100644 index 00000000..a8442bcf --- /dev/null +++ b/java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java @@ -0,0 +1,220 @@ +package io.cucumber.query; + +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import io.cucumber.messages.Convertor; +import io.cucumber.messages.NdjsonToMessageIterable; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.PickleStep; +import io.cucumber.messages.types.Step; +import io.cucumber.messages.types.TestCaseFinished; +import io.cucumber.messages.types.TestCaseStarted; +import io.cucumber.messages.types.TestStep; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static com.fasterxml.jackson.core.util.DefaultIndenter.SYSTEM_LINEFEED_INSTANCE; +import static io.cucumber.query.Jackson.OBJECT_MAPPER; +import static io.cucumber.query.NamingStrategy.ExampleName.PICKLE; +import static io.cucumber.query.NamingStrategy.FeatureName.EXCLUDE; +import static io.cucumber.query.NamingStrategy.Strategy.LONG; +import static io.cucumber.query.NamingStrategy.Strategy.SHORT; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +public class QueryAcceptanceTest { + private static final NdjsonToMessageIterable.Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class); + + static List acceptance() { + + return Stream.of( + Paths.get("../testdata/empty.feature.ndjson"), + Paths.get("../testdata/minimal.feature.ndjson"), + Paths.get("../testdata/rules.feature.ndjson"), + Paths.get("../testdata/examples-tables.feature.ndjson") + ) + .map(TestCase::new) + .sorted(Comparator.comparing(testCase -> testCase.source)) + .collect(toList()); + } + + @ParameterizedTest + @MethodSource("acceptance") + void test(TestCase testCase) throws IOException { + ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream()); + String expected = new String(Files.readAllBytes(testCase.expected), UTF_8); + String actual = new String(bytes.toByteArray(), UTF_8); + assertThat(actual).isEqualTo(expected); + } + + + @ParameterizedTest + @MethodSource("acceptance") + @Disabled + void updateExpectedQueryResultFiles(TestCase testCase) throws IOException { + try (OutputStream out = Files.newOutputStream(testCase.expected)) { + writeQueryResults(testCase, out); + } + } + + private static T writeQueryResults(TestCase testCase, T out) throws IOException { + try (InputStream in = Files.newInputStream(testCase.source)) { + try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) { + Query query = new Query(); + for (Envelope envelope : envelopes) { + query.update(envelope); + } + Map queryResults = createQueryResults(query); + DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() + .withArrayIndenter(SYSTEM_LINEFEED_INSTANCE); + OBJECT_MAPPER.writer(prettyPrinter).writeValue(out, queryResults); + } + } + return out; + } + + private static Map createQueryResults(Query query) { + + Map results = new LinkedHashMap<>(); + + results.put("countMostSevereTestStepResultStatus", query.countMostSevereTestStepResultStatus()); + results.put("countTestCasesStarted", query.countTestCasesStarted()); + results.put("findAllPickles", query.findAllPickles().size()); + results.put("findAllPickleSteps", query.findAllPickleSteps().size()); + results.put("findAllTestCaseStarted", query.findAllTestCaseStarted().size()); + results.put("findAllTestSteps", query.findAllTestSteps().size()); + results.put("findAllTestCaseStartedGroupedByFeature", query.findAllTestCaseStartedGroupedByFeature() + .entrySet() + .stream() + .map(entry -> Arrays.asList(entry.getKey().map(Feature::getName), entry.getValue().stream() + .map(TestCaseStarted::getId) + .collect(toList())))); + results.put("findFeatureBy", query.findAllTestCaseStarted().stream() + .map(query::findFeatureBy) + .map(feature -> feature.map(Feature::getName)) + .collect(toList())); + results.put("findMostSevereTestStepResulBy", query.findAllTestCaseStarted().stream() + .map(query::findMostSevereTestStepResulBy) + .map(testStepResult -> testStepResult.map(TestStepResult::getStatus)) + .collect(toList())); + + Map names = new LinkedHashMap<>(); + names.put("long", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).build())) + .collect(toList())); + names.put("excludeFeatureName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).featureName(EXCLUDE).build())) + .collect(toList())); + names.put("longPickleName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).exampleName(PICKLE).build())) + .collect(toList())); + names.put("short", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).build())) + .collect(toList())); + names.put("shortPickleName", query.findAllPickles().stream() + .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).exampleName(PICKLE).build())) + .collect(toList())); + results.put("findNameOf", names); + + results.put("findPickleBy", query.findAllTestCaseStarted().stream() + .map(query::findPickleBy) + .map(pickle -> pickle.map(Pickle::getName)) + .collect(toList())); + results.put("findPickleStepBy", query.findAllTestSteps().stream() + .map(query::findPickleStepBy) + .map(pickleStep -> pickleStep.map(PickleStep::getText)) + .collect(toList())); + results.put("findStepBy", query.findAllPickleSteps().stream() + .map(query::findStepBy) + .map(step -> step.map(Step::getText)) + .collect(toList())); + results.put("findTestCaseBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseBy) + .map(testCase -> testCase.map(io.cucumber.messages.types.TestCase::getId)) + .collect(toList())); + results.put("findTestCaseDurationBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseDurationBy) + .map(duration -> duration.map(Convertor::toMessage)) + .collect(toList())); + results.put("findTestCaseFinishedBy", query.findAllTestCaseStarted().stream() + .map(query::findTestCaseFinishedBy) + .map(testCaseFinished -> testCaseFinished.map(TestCaseFinished::getTestCaseStartedId)) + .collect(toList())); + results.put("findTestRunDuration", query.findTestRunDuration() + .map(Convertor::toMessage)); + results.put("findTestRunFinished", query.findTestRunFinished()); + results.put("findTestRunStarted", query.findTestRunStarted()); + results.put("findTestStepBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepsFinishedBy) + .flatMap(Collection::stream) + .map(query::findTestStepBy) + .map(testStep -> testStep.map(TestStep::getId)) + .collect(toList())); + results.put("findTestStepsFinishedBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepsFinishedBy) + .map(testStepFinisheds -> testStepFinisheds.stream().map(TestStepFinished::getTestStepId).collect(toList())) + .collect(toList())); + results.put("findTestStepFinishedAndTestStepBy", query.findAllTestCaseStarted().stream() + .map(query::findTestStepFinishedAndTestStepBy) + .flatMap(Collection::stream) + .map(entry -> asList(entry.getKey().getTestStepId(), entry.getValue().getId())) + .collect(toList())); + + return results; + } + + static class TestCase { + private final Path source; + private final Path expected; + + private final String name; + + TestCase(Path source) { + this.source = source; + String fileName = source.getFileName().toString(); + this.name = fileName.substring(0, fileName.lastIndexOf(".ndjson")); + this.expected = source.getParent().resolve(name + ".query-results.json"); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestCase testCase = (TestCase) o; + return source.equals(testCase.source); + } + + @Override + public int hashCode() { + return Objects.hash(source); + } + } + +} diff --git a/java/src/test/java/io/cucumber/query/QueryTest.java b/java/src/test/java/io/cucumber/query/QueryTest.java index 019654f6..beb73d35 100644 --- a/java/src/test/java/io/cucumber/query/QueryTest.java +++ b/java/src/test/java/io/cucumber/query/QueryTest.java @@ -1,220 +1,34 @@ package io.cucumber.query; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import io.cucumber.messages.Convertor; -import io.cucumber.messages.NdjsonToMessageIterable; import io.cucumber.messages.types.Envelope; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.Pickle; -import io.cucumber.messages.types.PickleStep; -import io.cucumber.messages.types.Step; -import io.cucumber.messages.types.TestCaseFinished; import io.cucumber.messages.types.TestCaseStarted; -import io.cucumber.messages.types.TestStep; -import io.cucumber.messages.types.TestStepFinished; -import io.cucumber.messages.types.TestStepResult; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import io.cucumber.messages.types.Timestamp; +import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.UUID; import java.util.stream.Stream; -import static com.fasterxml.jackson.core.util.DefaultIndenter.SYSTEM_LINEFEED_INSTANCE; -import static io.cucumber.query.Jackson.OBJECT_MAPPER; -import static io.cucumber.query.NamingStrategy.ExampleName.PICKLE; -import static io.cucumber.query.NamingStrategy.FeatureName.EXCLUDE; -import static io.cucumber.query.NamingStrategy.Strategy.LONG; -import static io.cucumber.query.NamingStrategy.Strategy.SHORT; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -public class QueryTest { - private static final NdjsonToMessageIterable.Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class); +class QueryTest { - static List acceptance() { + final Query query = new Query(); - return Stream.of( - Paths.get("../testdata/empty.feature.ndjson"), - Paths.get("../testdata/minimal.feature.ndjson"), - Paths.get("../testdata/rules.feature.ndjson"), - Paths.get("../testdata/examples-tables.feature.ndjson") - ) - .map(TestCase::new) - .sorted(Comparator.comparing(testCase -> testCase.source)) - .collect(toList()); - } - - @ParameterizedTest - @MethodSource("acceptance") - void test(TestCase testCase) throws IOException { - ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream()); - String expected = new String(Files.readAllBytes(testCase.expected), UTF_8); - String actual = new String(bytes.toByteArray(), UTF_8); - assertThat(actual).isEqualTo(expected); - } + @Test + void retainsInsertionOrderForTestCaseStarted() { + TestCaseStarted a = new TestCaseStarted(0L, randomId(), randomId(), "main", new Timestamp(0L, 0L)); + TestCaseStarted b = new TestCaseStarted(0L, randomId(), randomId(), "main", new Timestamp(0L, 0L)); + TestCaseStarted c = new TestCaseStarted(0L, randomId(), randomId(), "main", new Timestamp(0L, 0L)); + Stream.of(a, b, c) + .map(Envelope::of) + .forEach(query::update); - @ParameterizedTest - @MethodSource("acceptance") - @Disabled - void updateExpectedQueryResultFiles(TestCase testCase) throws IOException { - try (OutputStream out = Files.newOutputStream(testCase.expected)) { - writeQueryResults(testCase, out); - } - } - - private static T writeQueryResults(TestCase testCase, T out) throws IOException { - try (InputStream in = Files.newInputStream(testCase.source)) { - try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) { - Query query = new Query(); - for (Envelope envelope : envelopes) { - query.update(envelope); - } - Map queryResults = createQueryResults(query); - DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() - .withArrayIndenter(SYSTEM_LINEFEED_INSTANCE); - OBJECT_MAPPER.writer(prettyPrinter).writeValue(out, queryResults); - } - } - return out; + assertThat(query.findAllTestCaseStarted()).containsExactly(a, b, c); } - private static Map createQueryResults(Query query) { - - Map results = new LinkedHashMap<>(); - - results.put("countMostSevereTestStepResultStatus", query.countMostSevereTestStepResultStatus()); - results.put("countTestCasesStarted", query.countTestCasesStarted()); - results.put("findAllPickles", query.findAllPickles().size()); - results.put("findAllPickleSteps", query.findAllPickleSteps().size()); - results.put("findAllTestCaseStarted", query.findAllTestCaseStarted().size()); - results.put("findAllTestSteps", query.findAllTestSteps().size()); - results.put("findAllTestCaseStartedGroupedByFeature", query.findAllTestCaseStartedGroupedByFeature() - .entrySet() - .stream() - .map(entry -> Arrays.asList(entry.getKey().map(Feature::getName), entry.getValue().stream() - .map(TestCaseStarted::getId) - .collect(toList())))); - results.put("findFeatureBy", query.findAllTestCaseStarted().stream() - .map(query::findFeatureBy) - .map(feature -> feature.map(Feature::getName)) - .collect(toList())); - results.put("findMostSevereTestStepResulBy", query.findAllTestCaseStarted().stream() - .map(query::findMostSevereTestStepResulBy) - .map(testStepResult -> testStepResult.map(TestStepResult::getStatus)) - .collect(toList())); - - Map names = new LinkedHashMap<>(); - names.put("long", query.findAllPickles().stream() - .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).build())) - .collect(toList())); - names.put("excludeFeatureName", query.findAllPickles().stream() - .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).featureName(EXCLUDE).build())) - .collect(toList())); - names.put("longPickleName", query.findAllPickles().stream() - .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(LONG).exampleName(PICKLE).build())) - .collect(toList())); - names.put("short", query.findAllPickles().stream() - .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).build())) - .collect(toList())); - names.put("shortPickleName", query.findAllPickles().stream() - .map(pickle -> query.findNameOf(pickle, NamingStrategy.strategy(SHORT).exampleName(PICKLE).build())) - .collect(toList())); - results.put("findNameOf", names); - - results.put("findPickleBy", query.findAllTestCaseStarted().stream() - .map(query::findPickleBy) - .map(pickle -> pickle.map(Pickle::getName)) - .collect(toList())); - results.put("findPickleStepBy", query.findAllTestSteps().stream() - .map(query::findPickleStepBy) - .map(pickleStep -> pickleStep.map(PickleStep::getText)) - .collect(toList())); - results.put("findStepBy", query.findAllPickleSteps().stream() - .map(query::findStepBy) - .map(step -> step.map(Step::getText)) - .collect(toList())); - results.put("findTestCaseBy", query.findAllTestCaseStarted().stream() - .map(query::findTestCaseBy) - .map(testCase -> testCase.map(io.cucumber.messages.types.TestCase::getId)) - .collect(toList())); - results.put("findTestCaseDurationBy", query.findAllTestCaseStarted().stream() - .map(query::findTestCaseDurationBy) - .map(duration -> duration.map(Convertor::toMessage)) - .collect(toList())); - results.put("findTestCaseFinishedBy", query.findAllTestCaseStarted().stream() - .map(query::findTestCaseFinishedBy) - .map(testCaseFinished -> testCaseFinished.map(TestCaseFinished::getTestCaseStartedId)) - .collect(toList())); - results.put("findTestRunDuration", query.findTestRunDuration() - .map(Convertor::toMessage)); - results.put("findTestRunFinished", query.findTestRunFinished()); - results.put("findTestRunStarted", query.findTestRunStarted()); - results.put("findTestStepBy", query.findAllTestCaseStarted().stream() - .map(query::findTestStepsFinishedBy) - .flatMap(Collection::stream) - .map(query::findTestStepBy) - .map(testStep -> testStep.map(TestStep::getId)) - .collect(toList())); - results.put("findTestStepsFinishedBy", query.findAllTestCaseStarted().stream() - .map(query::findTestStepsFinishedBy) - .map(testStepFinisheds -> testStepFinisheds.stream().map(TestStepFinished::getTestStepId).collect(toList())) - .collect(toList())); - results.put("findTestStepFinishedAndTestStepBy", query.findAllTestCaseStarted().stream() - .map(query::findTestStepFinishedAndTestStepBy) - .flatMap(Collection::stream) - .map(entry -> asList(entry.getKey().getTestStepId(), entry.getValue().getId())) - .collect(toList())); - - return results; - } - - static class TestCase { - private final Path source; - private final Path expected; - - private final String name; - - TestCase(Path source) { - this.source = source; - String fileName = source.getFileName().toString(); - this.name = fileName.substring(0, fileName.lastIndexOf(".ndjson")); - this.expected = source.getParent().resolve(name + ".query-results.json"); - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TestCase testCase = (TestCase) o; - return source.equals(testCase.source); - } - - @Override - public int hashCode() { - return Objects.hash(source); - } + private static String randomId() { + return UUID.randomUUID().toString(); } } From 0ee9b49907c3fcc21417e8fd9929be0e7b50eedc Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 5 Apr 2024 03:40:31 +0200 Subject: [PATCH 3/4] Optimize --- java/src/main/java/io/cucumber/query/Query.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/java/src/main/java/io/cucumber/query/Query.java b/java/src/main/java/io/cucumber/query/Query.java index b3c7e110..6289c77a 100644 --- a/java/src/main/java/io/cucumber/query/Query.java +++ b/java/src/main/java/io/cucumber/query/Query.java @@ -108,19 +108,16 @@ public List findAllTestSteps() { } public Map, List> findAllTestCaseStartedGroupedByFeature() { - List, TestCaseStarted>> collect = findAllTestCaseStarted() + return findAllTestCaseStarted() .stream() - .map(testCaseStarted -> { - Optional astNodes = findGherkinAstNodesBy(testCaseStarted); - return new SimpleEntry<>(astNodes, testCaseStarted); + .map(testCaseStarted1 -> { + Optional astNodes = findGherkinAstNodesBy(testCaseStarted1); + return new SimpleEntry<>(astNodes, testCaseStarted1); }) // Sort entries by gherkin document URI for consistent ordering - .sorted(nullsFirst(comparing(entry -> entry.getKey() + .sorted(nullsFirst(comparing(entry1 -> entry1.getKey() .flatMap(nodes -> nodes.document().getUri()) .orElse(null)))) - .collect(toList()); - return collect - .stream() .map(entry -> { // Unpack the now sorted entries Optional feature = entry.getKey().flatMap(GherkinDocumentElements::feature); From 1a51b9420f5a1b41afb5aa2962752f80becf4961 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 5 Apr 2024 04:25:04 +0200 Subject: [PATCH 4/4] Fix broken link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 699cf0e7..11716ebb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cucumber Query -This is a query API for [cucumber-messages](../cucumber-messages). +This is a query API for [cucumber-messages](https://github.com/cucumber/messages). ## Overview