Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Allow feature with line syntax to target rules and examples #2884

Merged
merged 4 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
### Added
- [Core] The TeamCityPlugin for IntelliJ IDEA now uses the hook's method name for the name of the hook itself. ([#2798](https://github.com/cucumber/cucumber-jvm/issues/2798) V.V. Belov)
- [Core] Allow feature with line syntax to target rules and examples. ([#2884](https://github.com/cucumber/cucumber-jvm/issues/2884) M.P. Korstanje)

## [7.17.0] - 2024-04-18
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import java.util.stream.Collectors;

/**
* Identifies either a directory containing feature files, a specific feature or
* specific scenarios and examples (pickles) in a feature.
* Identifies either a directory containing feature files, a specific feature
* file or a feature, rules, scenarios, and/or examples in a feature file.
* <p>
* The syntax of a feature with lines defined as either a {@link FeaturePath} or
* a {@link FeatureIdentifier} followed by a sequence of line numbers each
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.cucumber.core.filter;

import io.cucumber.core.gherkin.Pickle;
import io.cucumber.plugin.event.Location;

import java.net.URI;
import java.util.Collection;
Expand All @@ -24,7 +25,10 @@ public boolean test(Pickle pickle) {
}
for (Integer line : lineFilters.get(picklePath)) {
if (Objects.equals(line, pickle.getLocation().getLine())
|| Objects.equals(line, pickle.getScenarioLocation().getLine())) {
|| Objects.equals(line, pickle.getScenarioLocation().getLine())
|| pickle.getExamplesLocation().map(Location::getLine).map(line::equals).orElse(false)
|| pickle.getRuleLocation().map(Location::getLine).map(line::equals).orElse(false)
|| pickle.getFeatureLocation().map(Location::getLine).map(line::equals).orElse(false)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ class LinePredicateTest {
featurePath,
"" +
"Feature: Test feature\n" +
" Scenario Outline: Test scenario\n" +
" Given I have 4 <thing> in my belly\n" +
" Examples:\n" +
" | thing | \n" +
" | cucumber | \n" +
" | gherkin | \n");
private final Pickle pickle = feature.getPickles().get(0);
" Rule: Test rule\n" +
" Scenario Outline: Test scenario\n" +
" Given I have 4 <thing> in my belly\n" +
" Examples: First\n" +
" | thing | \n" +
" | cucumber | \n" +
" | gherkin | \n" +
"\n" +
" Examples: Second\n" +
" | thing | \n" +
" | zukini | \n" +
" | pickle | \n");
private final Pickle firstPickle = feature.getPickles().get(0);
private final Pickle secondPickle = feature.getPickles().get(1);
private final Pickle thirdPickle = feature.getPickles().get(2);
private final Pickle fourthPickle = feature.getPickles().get(3);

@Test
void matches_pickles_from_files_not_in_the_predicate_map() {
Expand All @@ -37,47 +46,172 @@ void matches_pickles_from_files_not_in_the_predicate_map() {
LinePredicate predicate = new LinePredicate(singletonMap(
URI.create("classpath:another_path/file.feature"),
singletonList(8)));
assertTrue(predicate.test(pickle));
assertTrue(predicate.test(firstPickle));
}

@Test
void does_not_matches_pickles_for_no_lines_in_predicate() {
void empty() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
emptyList()));
assertFalse(predicate.test(pickle));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_pickles_for_any_line_in_predicate() {
void matches_at_least_one_line() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
asList(2, 4)));
assertTrue(predicate.test(pickle));
asList(3, 4)));
assertTrue(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

@Test
void matches_pickles_on_scenario_location_of_the_pickle() {
void matches_feature() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(1)));
assertTrue(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

@Test
void matches_rule() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(2)));
assertTrue(predicate.test(pickle));
assertTrue(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

@Test
void matches_pickles_on_example_location_of_the_pickle() {
void matches_scenario() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(6)));
assertTrue(predicate.test(pickle));
singletonList(3)));
assertTrue(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

@Test
void does_not_matches_pickles_not_on_any_line_of_the_predicate() {
void does_not_match_step() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(4)));
assertFalse(predicate.test(pickle));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_first_examples() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(5)));
assertTrue(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void does_not_match_example_header() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(6)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_first_example() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(7)));
assertTrue(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void Matches_second_example() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(8)));
assertFalse(predicate.test(firstPickle));
assertTrue(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void does_not_match_empty_line() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(9)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_second_examples() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(10)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

@Test
void does_not_match_second_examples_header() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(11)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_third_example() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(12)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertTrue(predicate.test(thirdPickle));
assertFalse(predicate.test(fourthPickle));
}

@Test
void matches_fourth_example() {
LinePredicate predicate = new LinePredicate(singletonMap(
featurePath,
singletonList(13)));
assertFalse(predicate.test(firstPickle));
assertFalse(predicate.test(secondPickle));
assertFalse(predicate.test(thirdPickle));
assertTrue(predicate.test(fourthPickle));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,63 @@
import io.cucumber.messages.types.Examples;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.Location;
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 java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

final class CucumberQuery {

private final Map<String, Rule> ruleByScenarioId = new HashMap<>();
private final Map<String, Examples> examplesByExampleId = new HashMap<>();
private final Map<String, Feature> featureByScenarioId = new HashMap<>();
private final Map<String, Step> gherkinStepById = new HashMap<>();
private final Map<String, Scenario> gherkinScenarioById = new HashMap<>();
private final Map<String, Location> locationBySourceId = new HashMap<>();

void update(Feature feature) {
feature.getChildren().forEach(featureChild -> {
featureChild.getBackground().ifPresent(this::updateBackground);
featureChild.getScenario().ifPresent(this::updateScenario);
featureChild.getRule().ifPresent(rule -> rule.getChildren().forEach(ruleChild -> {
ruleChild.getBackground().ifPresent(this::updateBackground);
ruleChild.getScenario().ifPresent(this::updateScenario);
}));
featureChild.getScenario().ifPresent(scenario -> updateScenario(feature, null, scenario));
featureChild.getRule().ifPresent(rule -> {
rule.getChildren().forEach(ruleChild -> {
ruleChild.getBackground().ifPresent(this::updateBackground);
ruleChild.getScenario().ifPresent(scenario -> updateScenario(feature, rule, scenario));
});
});
});
}

private void updateBackground(Background background) {
updateStep(background.getSteps());
}

private void updateScenario(Scenario scenario) {
private void updateScenario(Feature feature, Rule rule, Scenario scenario) {
gherkinScenarioById.put(requireNonNull(scenario.getId()), scenario);
locationBySourceId.put(requireNonNull(scenario.getId()), scenario.getLocation());
updateStep(scenario.getSteps());

for (Examples examples : scenario.getExamples()) {
for (TableRow tableRow : examples.getTableBody()) {
this.locationBySourceId.put(requireNonNull(tableRow.getId()), tableRow.getLocation());
this.examplesByExampleId.put(tableRow.getId(), examples);
this.locationBySourceId.put(tableRow.getId(), tableRow.getLocation());
}
}

if (rule != null) {
ruleByScenarioId.put(scenario.getId(), rule);
}

featureByScenarioId.put(scenario.getId(), feature);
}

private void updateStep(List<Step> stepsList) {
Expand All @@ -54,17 +70,41 @@ private void updateStep(List<Step> stepsList) {
}
}

Step getGherkinStep(String sourceId) {
return requireNonNull(gherkinStepById.get(requireNonNull(sourceId)));
Step getStepBy(PickleStep pickleStep) {
requireNonNull(pickleStep);
String gherkinStepId = pickleStep.getAstNodeIds().get(0);
return requireNonNull(gherkinStepById.get(gherkinStepId));
}

Scenario getScenarioBy(Pickle pickle) {
requireNonNull(pickle);
return requireNonNull(gherkinScenarioById.get(pickle.getAstNodeIds().get(0)));
}

Scenario getGherkinScenario(String sourceId) {
return requireNonNull(gherkinScenarioById.get(requireNonNull(sourceId)));
Optional<Rule> findRuleBy(Pickle pickle) {
requireNonNull(pickle);
Scenario scenario = getScenarioBy(pickle);
return Optional.ofNullable(ruleByScenarioId.get(scenario.getId()));
}

Location getLocation(String sourceId) {
Location location = locationBySourceId.get(requireNonNull(sourceId));
Location getLocationBy(Pickle pickle) {
requireNonNull(pickle);
List<String> sourceIds = pickle.getAstNodeIds();
String sourceId = sourceIds.get(sourceIds.size() - 1);
Location location = locationBySourceId.get(sourceId);
return requireNonNull(location);
}

Optional<Feature> findFeatureBy(Pickle pickle) {
requireNonNull(pickle);
Scenario scenario = getScenarioBy(pickle);
return Optional.ofNullable(featureByScenarioId.get(scenario.getId()));
}

Optional<Examples> findExamplesBy(Pickle pickle) {
requireNonNull(pickle);
List<String> sourceIds = pickle.getAstNodeIds();
String sourceId = sourceIds.get(sourceIds.size() - 1);
return Optional.ofNullable(examplesByExampleId.get(sourceId));
}
}
Loading