Skip to content
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.google.pdsl</groupId>
<artifactId>pdsl</artifactId>
<version>1.9.0</version>
<version>1.10.0</version>

<name>pdsl</name>
<url>http://www.github.com/google/polymorphicDSL</url>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import com.pdsl.exceptions.SentenceNotFoundException;
import com.pdsl.gherkin.filter.GherkinTagsVisitorImpl;
import com.pdsl.gherkin.models.GherkinBackground;
import com.pdsl.gherkin.models.GherkinScenario;
import com.pdsl.gherkin.models.GherkinStep;
import com.pdsl.gherkin.specifications.GherkinTestSpecification;
import com.pdsl.gherkin.specifications.GherkinTestSpecificationFactory;
import com.pdsl.gherkin.testcases.GherkinTestCaseSpecification;
import com.pdsl.logging.AnsiTerminalColorHelper;
import com.pdsl.runners.PdslTest;
import com.pdsl.runners.RecognizedBy;
Expand All @@ -24,6 +24,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -230,7 +231,7 @@ public Optional<Collection<TestSpecification>> getTestSpecifications(Set<URI> te
featureBuilder.withMetaData(
new ByteArrayInputStream(featureMetaData.toByteArray()));
} catch(IOException e){
//TODO Y
throw new IllegalStateException("There was an issue processing a feature file!", e);
}

List<TestSpecification> pickles = getGherkinStepSpecificationScenarios(pickleJar.getScenarios(),
Expand All @@ -241,18 +242,38 @@ public Optional<Collection<TestSpecification>> getTestSpecifications(Set<URI> te
pickles.addAll(transformRulesToTestSpecifications(pickleJar.getRules(), pickleJar.getLocation()));
}
featureBuilder.withChildTestSpecifications(pickles);
featureTestSpecifications.add(new GherkinTestCaseSpecification(allTagsForTestCase, featureBuilder.build()));
featureTestSpecifications.add(new GherkinTestSpecification(featureBuilder.build(), allTagsForTestCase));
}
return Optional.of(featureTestSpecifications);
}

private List<GherkinTestSpecification> transformScenariosToTestSpecifications(List<PickleJar.PickleJarScenario> scenarios, Set<String> parentTags, URI originalSourceLocation) {
List<GherkinTestSpecification> gherkinTestSpecifications = new ArrayList<>();
for (PickleJar.PickleJarScenario pickleJarScenario : scenarios) {
var position = pickleJarScenario.getScenarioPosition();
URI processedUri = null;
try {
processedUri = new URI(
originalSourceLocation.getScheme(),
originalSourceLocation.getRawUserInfo(), // Use raw to preserve encoding
originalSourceLocation.getHost(),
originalSourceLocation.getPort(),
originalSourceLocation.getRawPath(), // Use raw to preserve encoding
// Provide the line number using the rfc 5147 standard for 'text/plain'
// Also provide positional data as params so that test frameworks can group them together
// Preserve existing fragment
String.format("%s=%d&%s=%d&%s=%d",
GherkinScenario.ScenarioPosition.RULE_INDEX, position.ruleIndex(),
GherkinScenario.ScenarioPosition.ORDINAL, position.ordinal(),
GherkinScenario.ScenarioPosition.TABLE_INDEX, position.testIndex()), // Use the query string to provide position information
String.format("line=%d", pickleJarScenario.getLineNumber()));
} catch (URISyntaxException e) {
processedUri = originalSourceLocation;
}
DefaultTestSpecification.Builder topLevelScenario = new DefaultTestSpecification.Builder(
pickleJarScenario.getTitleWithSubstitutions(),
// Provide the line number using the rfc 5147 standard for 'text/plain'
originalSourceLocation.resolve("#line=" + pickleJarScenario.getLineNumber()));
pickleJarScenario.getTitleWithSubstitutions(), processedUri

);
// Provide metadata
topLevelScenario.withMetaData(new ByteArrayInputStream(extractMetaData(pickleJarScenario).toByteArray()));
// Process step body
Expand Down Expand Up @@ -316,7 +337,9 @@ private Optional<TestSpecification> processStepBody(String title, List<String> s

private List<TestSpecification> transformRulesToTestSpecifications(List<PickleJar.PickleJarRule> rules, URI originalSourceLocation) {
List<TestSpecification> testSpecifications = new ArrayList<>();
int ruleIndex = 0;
for (PickleJar.PickleJarRule rule : rules) {
ruleIndex++;
DefaultTestSpecification.Builder ruleBuilder = new DefaultTestSpecification.Builder(rule.getTitle(),originalSourceLocation);
ByteArrayOutputStream ruleMetaData = new ByteArrayOutputStream();
if (rule.getLongDescription().isPresent()) {
Expand All @@ -327,7 +350,7 @@ private List<TestSpecification> transformRulesToTestSpecifications(List<PickleJa
// Nest the scenarios in a background TestSpecification
GherkinBackground bg = rule.getBackground().get();
addBytesWithCorrectEncoding(ruleMetaData, getBackgroundText(bg));
logger.debug(String.format("%sRule Background%s in %s", AnsiTerminalColorHelper.CYAN, AnsiTerminalColorHelper.RESET, rule.getTitle()));
logger.debug("{}Rule Background{} in {}", AnsiTerminalColorHelper.CYAN, AnsiTerminalColorHelper.RESET, rule.getTitle());
Optional<List<FilteredPhrase>> filteredBackgroundStepBody = processStepBodyContent(bg.getSteps().orElseThrow());
filteredBackgroundStepBody.ifPresent(ruleBuilder::withTestPhrases);
}
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/pdsl/gherkin/PickleJar.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.base.Preconditions;
import com.pdsl.gherkin.models.GherkinBackground;
import com.pdsl.gherkin.models.GherkinScenario;

import java.net.URI;
import java.util.*;
Expand Down Expand Up @@ -201,16 +202,23 @@ static class PickleJarScenario {
private final List<String> stepsWithParameterSubstitutionsIfNeeded;
private Optional<Set<String>> tags;
private final int lineNumber;
private final GherkinScenario.ScenarioPosition scenarioPosition;

private PickleJarScenario(Builder builder) {
this.tags = builder.tags;
this.longDescription = builder.longDescription;
this.tags = builder.tags;
this.lineNumber = builder.lineNumber;
this.scenarioTitleWithParameterSubstitutionsIfNeeded = builder.titleWithSubstitutions;
this.stepsWithParameterSubstitutionsIfNeeded = builder.stepsWithSubstitutions;
this.scenarioPosition = builder.scenarioPosition.orElseThrow();
}

public int getLineNumber() { return lineNumber; }

public GherkinScenario.ScenarioPosition getScenarioPosition() {
return scenarioPosition;
}
public Optional<Set<String>> getTags() {
return tags;
}
Expand All @@ -233,6 +241,7 @@ public static class Builder {
private Optional<Set<String>> tags = Optional.empty();
private Optional<String> longDescription = Optional.empty();
private int lineNumber = -1;
private Optional<GherkinScenario.ScenarioPosition> scenarioPosition;

public Builder(String titleWithSubstitutions, List<String> stepsWithSubstitutions) {
this.titleWithSubstitutions = titleWithSubstitutions;
Expand All @@ -248,6 +257,11 @@ public Builder withLineNumber(int lineNumber) {
return this;
}

public Builder withScenarioPosition(int depth, int ordinal, int tableIndex) {
this.scenarioPosition = Optional.of(new GherkinScenario.ScenarioPosition(depth, ordinal, tableIndex));
return this;
}

public Builder withLongDescription(String longDescription) {
this.longDescription = Optional.ofNullable(longDescription);
return this;
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/com/pdsl/gherkin/PickleJarFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public List<PickleJar> getPickleJars(Set<URI> testResources) {
// Top level scenarios
if (feature.getOptionalGherkinScenarios().isPresent()) {
List<PickleJar.PickleJarScenario> topLevelPickles =
convertScenariosToPickleJarScenarios(feature.getOptionalGherkinScenarios().get());
convertScenariosToPickleJarScenarios(feature.getOptionalGherkinScenarios().get(), 0);
pickleJarBuilder.withFeatureLevelScenarios(topLevelPickles);
}
// Add all rules
Expand All @@ -97,15 +97,18 @@ public List<PickleJar> getPickleJars(Set<URI> testResources) {
}


private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(List<GherkinScenario> scenarios) {
private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(List<GherkinScenario> scenarios, int depth) {
int nextOrdinal = 0;
List<PickleJar.PickleJarScenario> pickleJarScenarios = new ArrayList<>();
for (GherkinScenario scenario : scenarios) {
nextOrdinal++;
// If the scenario has an examples table the tags will need to be combined with the scenario level
Set<String> tags = new HashSet<>();
if (scenario.getTags().isPresent()) {
tags.addAll(processTags(scenario.getTags().get()));
}
if (scenario.getExamples().isPresent()) {
int tableIndex = 1;
for (GherkinExamplesTable table : scenario.getExamples().get()) {
Set<String> tableTags = new HashSet<>(tags);
for (Map<String, GherkinExamplesTable.CellOfExamplesTable> substitutions : table.getRowsWithCell()) {
Expand All @@ -120,7 +123,8 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L
scenario.getTitle().orElseThrow().getStringWithSubstitutions(substitutionsAsStrings),
substitutedSteps)
// All parameters should have the same line number, so just get the number from the first one
.withLineNumber(substitutions.values().stream().findFirst().orElseThrow().lineNumber());
.withLineNumber(substitutions.values().stream().findFirst().orElseThrow().lineNumber())
.withScenarioPosition(depth, nextOrdinal, tableIndex++);
if (scenario.getLongDescription().isPresent()) {
builder.withLongDescription(scenario.getLongDescription().get().getStringWithSubstitutions(substitutionsAsStrings));
}
Expand All @@ -147,7 +151,8 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L
PickleJar.PickleJarScenario.Builder builder = new PickleJar.PickleJarScenario.Builder(
scenario.getTitle().orElseThrow().getRawString(),
stepBody)
.withLineNumber(scenario.getLineNumber());
.withLineNumber(scenario.getLineNumber())
.withScenarioPosition(depth,nextOrdinal, 0);
if (!tags.isEmpty()) {
builder.withTags(processTags(tags));
}
Expand All @@ -163,9 +168,9 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L

private List<PickleJar.PickleJarRule> convertRulesToPickles(List<GherkinRule> rules) {
List<PickleJar.PickleJarRule> pickleJarRules = new ArrayList<>();
for (GherkinRule rule : rules) {

List<PickleJar.PickleJarScenario> scenarios = convertScenariosToPickleJarScenarios(rule.getScenarios().orElseThrow());
for (int i=0; i < rules.size(); i++) {
GherkinRule rule = rules.get(i);
List<PickleJar.PickleJarScenario> scenarios = convertScenariosToPickleJarScenarios(rule.getScenarios().orElseThrow(), i+1);
PickleJar.PickleJarRule.Builder builder = new PickleJar.PickleJarRule.Builder(rule.getTitle().orElseThrow(), scenarios);
if (rule.getBackground().isPresent()) {
builder.withBackground(rule.getBackground().get());
Expand Down
123 changes: 120 additions & 3 deletions src/main/java/com/pdsl/gherkin/models/GherkinScenario.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.pdsl.gherkin.models;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

public class GherkinScenario {
private final Optional<List<String>> tags;
private final Optional<GherkinString> title;
private final Optional<GherkinString> longDescription;
private final Optional<List<GherkinStep>> stepsList;
private final Optional<List<GherkinExamplesTable>> examples;
private final Optional<ScenarioPosition> scenarioPosition;
private final int lineNumber;

public GherkinScenario(Builder builder) {
Expand All @@ -22,8 +23,118 @@ public GherkinScenario(Builder builder) {
this.examples = builder.examples.isEmpty() ? Optional.empty()
: Optional.of(builder.examples);
this.lineNumber = builder.lineNumber;
this.scenarioPosition = builder.scenarioPosition;
}

/**
* A specification of the hierarchical position this scenario appeared in with relation to other scenarios.
* <p>
* The ruleIndex specifies which rule the scenario was found. [e.g. which rule it was found in]
* If the test was not nested in a rule the value will be 0
* <p>
* The ordinal specifies which order this test showed up in relation to the others in the same rule index.
* <p>
* The testIndex specifies is the test was derived from an examples table. If it was, it is the
* nth row it appeared in starting from one. If it was not in an examples table the number will be 0.
* <p>
* This encoding CANNOT guarantee whether an arbitrary scenario was declared before another as it is possible
* to intersperse rule nodes between root level scenarios. If you want to know the prceise order the test
* was declared in the original source file use #getLineNumber() and compare using the
* {@see com.pdsl.testcases.DefaultTestCase.PdslTestCaseComparator}
* <p>
* For example:
* <p>
* <pre>
* {@code
* Feature:
* Scenario:
* # First part is "0" because it is a root node. Last part is "0" because it is not in a table.
* Then this group ordinal is 0.1.0
* Scenario:
* Then this group ordinal is 0.2.0 # The second test in the root, se we increment to 2.
* Scenario:
* Then this group ordinal is <ORDINAL>
* Examples:
* |ORDINAL|
* | 0.3.1 | # Second test in the root, so we increment the second value to 2
* | 0.3.2 | # Increment last digit as it comes from the same group
* | 0.3.3 |
*
* Rule: First rule (1)
* Scenario:
* Then this group ordinal is 1.1.0
* Scenario:
* Then this group ordinal is 1.2.0
* Scenario:
* Then this group oridinal is <ORDINAL>
* Examples:
* |ORDINAL|
* | 1.3.1 |
* | 1.3.2 |
*
* # Multi-tables continue from the index used in the last table
* Examples:
* |ORDINAL|
* | 1.3.3 |
* | 1.3.4
*
* Scenario:
* Then this group ordinal is 0.4.0 # Note we're back at root and continue from the last testPosition
*
* Rule: Second rule (2)
* Scenario:
* Then this group ordinal is 2.1.0
* }
* </pre>
*
* @param ruleIndex the nth rule this scenario was derived from, 0 if not in a rule
* @param ordinal the nth position of this scenario relative to others in the same depth
* @param testIndex 0 if not derived from an examples table, otherwise the nth row starting from 1
*/
public record ScenarioPosition(int ruleIndex, int ordinal, int testIndex) implements Comparable<ScenarioPosition> {

public static String RULE_INDEX= "ruleIndex";
public static String ORDINAL = "ordinal";
public static String TABLE_INDEX = "tableIndex";
private static final ScenarioPositionComparator SINGLETON = new ScenarioPositionComparator();

private static class ScenarioPositionComparator implements Comparator<ScenarioPosition> {
@Override
public int compare(ScenarioPosition p1, ScenarioPosition p2) {
if (p1.ruleIndex != p2.ruleIndex) {
return Integer.compare(p1.ruleIndex, p2.ruleIndex);
}
if (p1.ordinal != p2.ordinal) {
return Integer.compare(p1.ordinal, p2.ordinal);
}
return Integer.compare(p1.testIndex, p2.testIndex);
}
}

@Override
public int compareTo(ScenarioPosition scenarioPosition) {
return SINGLETON.compare(this, scenarioPosition);
}

public static Optional<ScenarioPosition> from(URI uri) {
Map<String, String> params = Arrays.stream(uri.getQuery().split("&"))
.map(param -> param.split("="))
.filter(arr -> arr.length == 2)
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
try {
int ruleIndex = Integer.parseInt(params.get(RULE_INDEX));
int ordinal = Integer.parseInt(params.get(ORDINAL));
int tableIndex = Integer.parseInt(params.get(TABLE_INDEX));
return Optional.of(new ScenarioPosition(ruleIndex, ordinal, tableIndex));
} catch(RuntimeException e) {
return Optional.empty();
}
}
}


public Optional<ScenarioPosition> getScenarioPositition() { return scenarioPosition; }

public Optional<List<String>> getTags() {
return tags;
}
Expand Down Expand Up @@ -55,11 +166,17 @@ public static class Builder {
private String longDescription = "";
private Optional<List<GherkinStep>> stepsList = Optional.empty();
private int lineNumber = -1;
private Optional<ScenarioPosition> scenarioPosition = Optional.empty();

public GherkinScenario build() {
return new GherkinScenario(this);
}

public Builder withScenarioPosition(ScenarioPosition scenarioPosition) {
this.scenarioPosition = Optional.ofNullable(scenarioPosition);
return this;
}

public Builder addExamples(GherkinExamplesTable examples) {
this.examples.add(examples);
return this;
Expand Down
Loading
Loading