Skip to content

Commit 108c963

Browse files
committed
Include pickle name if parameterized
Cucumber originally only had Features with Scenarios. When Scenario Outlines were added, it became hard to distinguish which example failed. To work around this Scenario Outline may have placeholders in their name. These placeholders are replaced when creating a pickle name. This helps ensure that each scenario is unique. ``` 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 | ``` This would be rendered as: ``` Examples Tables └── Eating 5 cucumbers ``` But with the addition of the Rule element and test frameworks supporting hierarchical test structures it also becomes desirable to include the structure of a feature file in the test name. So we would now render: ``` Examples Tables └── Eating cucumbers └──These are passing └── #1.1 ``` And while this hierarchy is sufficient to identify a failed example, it is not very easy to do so when there are many. So by including the pickle name when the Scenario Outline is parameterized we can render: ``` Examples Tables └── Eating <eat> cucumbers └──These are passing └── #1.1: Eating 5 cucumbers ```
1 parent 708a486 commit 108c963

14 files changed

+921
-332
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
9+
### Changed
10+
- Include pickle name if parameterized ((#44)[https://github.com/cucumber/query/pull/44])
11+
912
### Fixed
1013
- java: Require all arguments to the naming strategy builder to be non-null
1114

java/src/main/java/io/cucumber/query/GherkinDocumentElements.java renamed to java/src/main/java/io/cucumber/query/Lineage.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,62 @@
99

1010
import java.util.Objects;
1111
import java.util.Optional;
12+
import java.util.function.Supplier;
1213

1314
import static java.util.Objects.requireNonNull;
1415

1516
/**
16-
* A structure containing all ancestors of a given element.
17+
* A structure containing all ancestors of a given
18+
* {@linkplain GherkinDocument GherkinDocument element} or
19+
* {@link io.cucumber.messages.types.Pickle}.
1720
* <p>
18-
* This works without any ordering because Gherkins document
19-
* structure is simple enough to hard code.
21+
*
22+
* @see LineageReducer
2023
*/
21-
class GherkinDocumentElements {
24+
class Lineage {
25+
2226
private final GherkinDocument document;
2327
private final Feature feature;
2428
private final Rule rule;
2529
private final Scenario scenario;
2630
private final Examples examples;
27-
private final TableRow example;
2831
private final Integer examplesIndex;
32+
private final TableRow example;
2933
private final Integer exampleIndex;
3034

31-
GherkinDocumentElements(GherkinDocument document, Feature feature, Rule rule, Scenario scenario) {
32-
this(document, feature, rule, scenario, null, null, null, null);
35+
Lineage(GherkinDocument document) {
36+
this(document, null, null, null, null, null, null, null);
37+
}
38+
39+
Lineage(Lineage parent, Feature feature) {
40+
this(parent.document, feature, null, null, null, null, null, null);
3341
}
3442

35-
GherkinDocumentElements(GherkinDocument document, Feature feature, Rule rule, Scenario scenario, Integer examplesIndex, Examples examples, Integer exampleIndex, TableRow example) {
43+
Lineage(Lineage parent, Rule rule) {
44+
this(parent.document, parent.feature, rule, null, null, null, null, null);
45+
}
46+
47+
Lineage(Lineage parent, Scenario scenario) {
48+
this(parent.document, parent.feature, parent.rule, scenario, null, null, null, null);
49+
}
50+
51+
Lineage(Lineage parent, Examples examples, int examplesIndex) {
52+
this(parent.document, parent.feature, parent.rule, parent.scenario, examples, examplesIndex, null, null);
53+
}
54+
55+
Lineage(Lineage parent, TableRow example, int exampleIndex) {
56+
this(parent.document, parent.feature, parent.rule, parent.scenario, parent.examples, parent.examplesIndex, example, exampleIndex);
57+
}
58+
59+
private Lineage(GherkinDocument document, Feature feature, Rule rule, Scenario scenario, Examples examples, Integer examplesIndex, TableRow example, Integer exampleIndex) {
3660
this.document = requireNonNull(document);
3761
this.feature = feature;
3862
this.rule = rule;
3963
this.scenario = scenario;
40-
this.examplesIndex = examplesIndex;
4164
this.examples = examples;
42-
this.exampleIndex = exampleIndex;
65+
this.examplesIndex = examplesIndex;
4366
this.example = example;
67+
this.exampleIndex = exampleIndex;
4468
}
4569

4670
GherkinDocument document() {
@@ -75,11 +99,15 @@ Optional<Integer> exampleIndex() {
7599
return Optional.ofNullable(exampleIndex);
76100
}
77101

102+
<T> LineageReducer reduce(Supplier<LineageCollector<T>> collector) {
103+
return new LineageReducerDescending(collector);
104+
}
105+
78106
@Override
79107
public boolean equals(Object o) {
80108
if (this == o) return true;
81109
if (o == null || getClass() != o.getClass()) return false;
82-
GherkinDocumentElements that = (GherkinDocumentElements) o;
110+
Lineage that = (Lineage) o;
83111
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);
84112
}
85113

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.cucumber.query;
2+
3+
import io.cucumber.messages.types.Examples;
4+
import io.cucumber.messages.types.Feature;
5+
import io.cucumber.messages.types.GherkinDocument;
6+
import io.cucumber.messages.types.Pickle;
7+
import io.cucumber.messages.types.Rule;
8+
import io.cucumber.messages.types.Scenario;
9+
import io.cucumber.messages.types.TableRow;
10+
11+
/**
12+
* Collect the {@link Lineage} of a
13+
* {@linkplain io.cucumber.messages.types.GherkinDocument GherkinDocument element}
14+
* or {@link Pickle} and reduce it to a single result.
15+
*
16+
* @param <T> the type reduced to.
17+
*/
18+
interface LineageCollector<T> {
19+
default void add(GherkinDocument document) {
20+
21+
}
22+
23+
default void add(Feature feature) {
24+
25+
}
26+
27+
default void add(Rule rule) {
28+
29+
}
30+
31+
default void add(Scenario scenario) {
32+
33+
}
34+
35+
default void add(Examples examples, int index) {
36+
}
37+
38+
default void add(TableRow example, int index) {
39+
}
40+
41+
default void add(Pickle pickle) {
42+
}
43+
44+
T finish();
45+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.cucumber.query;
2+
3+
import io.cucumber.messages.types.Pickle;
4+
5+
import java.util.function.Supplier;
6+
7+
import static java.util.Objects.requireNonNull;
8+
9+
/**
10+
* Visit the {@link Lineage} of a {@linkplain io.cucumber.messages.types.GherkinDocument GherkinDocument element}
11+
* or {@link Pickle} and reduce it.
12+
* <p>
13+
* Because we are using messages we can not express the hierarchy of elements in
14+
* a {@link io.cucumber.messages.types.GherkinDocument} programmatically as a
15+
* tree of nodes. But we can still express the operations that would be typically
16+
* done this way as an operation on the lineage of those messages.
17+
*
18+
* @param <T> the type reduced to.
19+
*/
20+
interface LineageReducer<T> {
21+
22+
static <T> LineageReducer<T> descending(Supplier<? extends LineageCollector<T>> collector) {
23+
return new LineageReducerDescending<>(collector);
24+
}
25+
26+
T reduce(Lineage lineage);
27+
28+
T reduce(Lineage lineage, Pickle pickle);
29+
30+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.cucumber.query;
2+
3+
import io.cucumber.messages.types.Pickle;
4+
5+
import java.util.function.Supplier;
6+
7+
import static java.util.Objects.requireNonNull;
8+
9+
/**
10+
* Reduces the lineage of a Gherkin document element in descending order.
11+
*
12+
* @param <T> type to which the lineage is reduced.
13+
*/
14+
class LineageReducerDescending<T> implements LineageReducer<T> {
15+
16+
private final Supplier<? extends LineageCollector<T>> reducerSupplier;
17+
18+
LineageReducerDescending(Supplier<? extends LineageCollector<T>> reducerSupplier) {
19+
this.reducerSupplier = requireNonNull(reducerSupplier);
20+
}
21+
22+
@Override
23+
public T reduce(Lineage lineage) {
24+
LineageCollector<T> reducer = reducerSupplier.get();
25+
reduceAddLineage(reducer, lineage);
26+
return reducer.finish();
27+
}
28+
29+
@Override
30+
public T reduce(Lineage lineage, Pickle pickle) {
31+
LineageCollector<T> reducer = reducerSupplier.get();
32+
reduceAddLineage(reducer, lineage);
33+
reducer.add(pickle);
34+
return reducer.finish();
35+
}
36+
37+
private static <T> void reduceAddLineage(LineageCollector<T> reducer, Lineage lineage) {
38+
reducer.add(lineage.document());
39+
lineage.feature().ifPresent(reducer::add);
40+
lineage.rule().ifPresent(reducer::add);
41+
lineage.scenario().ifPresent(reducer::add);
42+
lineage.examples().ifPresent(examples -> reducer.add(examples, lineage.examplesIndex().orElse(0)));
43+
lineage.example().ifPresent(example -> reducer.add(example, lineage.exampleIndex().orElse(0)));
44+
}
45+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.cucumber.query;
2+
3+
import io.cucumber.messages.types.Examples;
4+
import io.cucumber.messages.types.Feature;
5+
import io.cucumber.messages.types.FeatureChild;
6+
import io.cucumber.messages.types.GherkinDocument;
7+
import io.cucumber.messages.types.Rule;
8+
import io.cucumber.messages.types.RuleChild;
9+
import io.cucumber.messages.types.Scenario;
10+
import io.cucumber.messages.types.TableRow;
11+
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Optional;
16+
import java.util.function.BiConsumer;
17+
import java.util.function.Consumer;
18+
import java.util.function.Supplier;
19+
20+
class Lineages {
21+
22+
/**
23+
* Create map of a {@link GherkinDocument} element to its {@link Lineage} in that document.
24+
* <p>
25+
* @param document to create the lineage of
26+
* @return a map of the document elements to their lineage.
27+
*/
28+
static Map<String, Lineage> of(GherkinDocument document) {
29+
Map<String, Lineage> elements = new HashMap<>();
30+
Lineage lineage = new Lineage(document);
31+
String uri = document.getUri()
32+
.orElseThrow(() -> new IllegalArgumentException("document.uri must not be null"));
33+
elements.put(uri, lineage);
34+
document.getFeature().ifPresent(ofFeature(lineage, elements));
35+
return elements;
36+
}
37+
38+
private static Consumer<Feature> ofFeature(Lineage parent, Map<String, Lineage> elements) {
39+
return feature -> {
40+
Lineage lineage = new Lineage(parent, feature);
41+
feature.getChildren().forEach(ofFeatureChild(lineage, elements));
42+
};
43+
}
44+
45+
private static Consumer<FeatureChild> ofFeatureChild(Lineage parent, Map<String, Lineage> elements) {
46+
return featureChild -> {
47+
featureChild.getScenario().ifPresent(ofScenario(parent, elements));
48+
featureChild.getRule().ifPresent(ofRule(parent, elements));
49+
};
50+
}
51+
52+
private static Consumer<Rule> ofRule(Lineage parent, Map<String, Lineage> elements) {
53+
return rule -> {
54+
Lineage lineage = new Lineage(parent, rule);
55+
elements.put(rule.getId(), lineage);
56+
rule.getChildren().forEach(ofRuleChild(lineage, elements));
57+
};
58+
}
59+
60+
private static Consumer<RuleChild> ofRuleChild(Lineage parent, Map<String, Lineage> elements) {
61+
return ruleChild -> ruleChild.getScenario().ifPresent(ofScenario(parent, elements));
62+
}
63+
64+
private static Consumer<Scenario> ofScenario(Lineage parent, Map<String, Lineage> elements) {
65+
return scenario -> {
66+
Lineage lineage = new Lineage(parent, scenario);
67+
elements.put(scenario.getId(), lineage);
68+
forEachIndexed(scenario.getExamples(), ofExamples(lineage, elements));
69+
};
70+
}
71+
72+
private static BiConsumer<Examples, Integer> ofExamples(Lineage parent, Map<String, Lineage> elements) {
73+
return (examples, examplesIndex) -> {
74+
Lineage lineage = new Lineage(parent, examples, examplesIndex);
75+
elements.put(examples.getId(), lineage);
76+
forEachIndexed(examples.getTableBody(), ofExample(lineage, elements));
77+
};
78+
}
79+
80+
private static BiConsumer<TableRow, Integer> ofExample(Lineage parent, Map<String, Lineage> elements) {
81+
return (example, exampleIndex) -> {
82+
Lineage lineage = new Lineage(parent, example, exampleIndex);
83+
elements.put(example.getId(), lineage);
84+
};
85+
}
86+
87+
private static <T> void forEachIndexed(List<T> items, BiConsumer<T, Integer> consumer) {
88+
for (int i = 0; i < items.size(); i++) {
89+
consumer.accept(items.get(i), i);
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)