Skip to content

Commit bf83e06

Browse files
committed
Added position tracking in URI
1 parent 00289a6 commit bf83e06

File tree

8 files changed

+302
-87
lines changed

8 files changed

+302
-87
lines changed

src/main/java/com/pdsl/gherkin/DefaultGherkinTestSpecificationFactory.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import com.pdsl.exceptions.SentenceNotFoundException;
55
import com.pdsl.gherkin.filter.GherkinTagsVisitorImpl;
66
import com.pdsl.gherkin.models.GherkinBackground;
7+
import com.pdsl.gherkin.models.GherkinScenario;
78
import com.pdsl.gherkin.models.GherkinStep;
89
import com.pdsl.gherkin.specifications.GherkinTestSpecification;
910
import com.pdsl.gherkin.specifications.GherkinTestSpecificationFactory;
10-
import com.pdsl.gherkin.testcases.GherkinTestCaseSpecification;
1111
import com.pdsl.logging.AnsiTerminalColorHelper;
1212
import com.pdsl.runners.PdslTest;
1313
import com.pdsl.runners.RecognizedBy;
@@ -24,6 +24,7 @@
2424
import java.io.InputStream;
2525
import java.io.OutputStream;
2626
import java.net.URI;
27+
import java.net.URISyntaxException;
2728
import java.nio.charset.Charset;
2829
import java.util.ArrayList;
2930
import java.util.Collection;
@@ -230,7 +231,7 @@ public Optional<Collection<TestSpecification>> getTestSpecifications(Set<URI> te
230231
featureBuilder.withMetaData(
231232
new ByteArrayInputStream(featureMetaData.toByteArray()));
232233
} catch(IOException e){
233-
//TODO Y
234+
throw new IllegalStateException("There was an issue processing a feature file!", e);
234235
}
235236

236237
List<TestSpecification> pickles = getGherkinStepSpecificationScenarios(pickleJar.getScenarios(),
@@ -241,18 +242,38 @@ public Optional<Collection<TestSpecification>> getTestSpecifications(Set<URI> te
241242
pickles.addAll(transformRulesToTestSpecifications(pickleJar.getRules(), pickleJar.getLocation()));
242243
}
243244
featureBuilder.withChildTestSpecifications(pickles);
244-
featureTestSpecifications.add(new GherkinTestCaseSpecification(allTagsForTestCase, featureBuilder.build()));
245+
featureTestSpecifications.add(new GherkinTestSpecification(featureBuilder.build(), allTagsForTestCase));
245246
}
246247
return Optional.of(featureTestSpecifications);
247248
}
248249

249250
private List<GherkinTestSpecification> transformScenariosToTestSpecifications(List<PickleJar.PickleJarScenario> scenarios, Set<String> parentTags, URI originalSourceLocation) {
250251
List<GherkinTestSpecification> gherkinTestSpecifications = new ArrayList<>();
251252
for (PickleJar.PickleJarScenario pickleJarScenario : scenarios) {
253+
var position = pickleJarScenario.getScenarioPosition();
254+
URI processedUri = null;
255+
try {
256+
processedUri = new URI(
257+
originalSourceLocation.getScheme(),
258+
originalSourceLocation.getRawUserInfo(), // Use raw to preserve encoding
259+
originalSourceLocation.getHost(),
260+
originalSourceLocation.getPort(),
261+
originalSourceLocation.getRawPath(), // Use raw to preserve encoding
262+
// Provide the line number using the rfc 5147 standard for 'text/plain'
263+
// Also provide positional data as params so that test frameworks can group them together
264+
// Preserve existing fragment
265+
String.format("%s=%d&%s=%d&%s=%d",
266+
GherkinScenario.ScenarioPosition.RULE_INDEX, position.ruleIndex(),
267+
GherkinScenario.ScenarioPosition.ORDINAL, position.ordinal(),
268+
GherkinScenario.ScenarioPosition.TABLE_INDEX, position.testIndex()), // Use the query string to provide position information
269+
String.format("line=%d", pickleJarScenario.getLineNumber()));
270+
} catch (URISyntaxException e) {
271+
processedUri = originalSourceLocation;
272+
}
252273
DefaultTestSpecification.Builder topLevelScenario = new DefaultTestSpecification.Builder(
253-
pickleJarScenario.getTitleWithSubstitutions(),
254-
// Provide the line number using the rfc 5147 standard for 'text/plain'
255-
originalSourceLocation.resolve("#line=" + pickleJarScenario.getLineNumber()));
274+
pickleJarScenario.getTitleWithSubstitutions(), processedUri
275+
276+
);
256277
// Provide metadata
257278
topLevelScenario.withMetaData(new ByteArrayInputStream(extractMetaData(pickleJarScenario).toByteArray()));
258279
// Process step body
@@ -316,7 +337,9 @@ private Optional<TestSpecification> processStepBody(String title, List<String> s
316337

317338
private List<TestSpecification> transformRulesToTestSpecifications(List<PickleJar.PickleJarRule> rules, URI originalSourceLocation) {
318339
List<TestSpecification> testSpecifications = new ArrayList<>();
340+
int ruleIndex = 0;
319341
for (PickleJar.PickleJarRule rule : rules) {
342+
ruleIndex++;
320343
DefaultTestSpecification.Builder ruleBuilder = new DefaultTestSpecification.Builder(rule.getTitle(),originalSourceLocation);
321344
ByteArrayOutputStream ruleMetaData = new ByteArrayOutputStream();
322345
if (rule.getLongDescription().isPresent()) {
@@ -327,7 +350,7 @@ private List<TestSpecification> transformRulesToTestSpecifications(List<PickleJa
327350
// Nest the scenarios in a background TestSpecification
328351
GherkinBackground bg = rule.getBackground().get();
329352
addBytesWithCorrectEncoding(ruleMetaData, getBackgroundText(bg));
330-
logger.debug(String.format("%sRule Background%s in %s", AnsiTerminalColorHelper.CYAN, AnsiTerminalColorHelper.RESET, rule.getTitle()));
353+
logger.debug("{}Rule Background{} in {}", AnsiTerminalColorHelper.CYAN, AnsiTerminalColorHelper.RESET, rule.getTitle());
331354
Optional<List<FilteredPhrase>> filteredBackgroundStepBody = processStepBodyContent(bg.getSteps().orElseThrow());
332355
filteredBackgroundStepBody.ifPresent(ruleBuilder::withTestPhrases);
333356
}

src/main/java/com/pdsl/gherkin/PickleJar.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.common.base.Preconditions;
44
import com.pdsl.gherkin.models.GherkinBackground;
5+
import com.pdsl.gherkin.models.GherkinScenario;
56

67
import java.net.URI;
78
import java.util.*;
@@ -201,16 +202,23 @@ static class PickleJarScenario {
201202
private final List<String> stepsWithParameterSubstitutionsIfNeeded;
202203
private Optional<Set<String>> tags;
203204
private final int lineNumber;
205+
private final GherkinScenario.ScenarioPosition scenarioPosition;
206+
204207
private PickleJarScenario(Builder builder) {
205208
this.tags = builder.tags;
206209
this.longDescription = builder.longDescription;
207210
this.tags = builder.tags;
208211
this.lineNumber = builder.lineNumber;
209212
this.scenarioTitleWithParameterSubstitutionsIfNeeded = builder.titleWithSubstitutions;
210213
this.stepsWithParameterSubstitutionsIfNeeded = builder.stepsWithSubstitutions;
214+
this.scenarioPosition = builder.scenarioPosition.orElseThrow();
211215
}
212216

213217
public int getLineNumber() { return lineNumber; }
218+
219+
public GherkinScenario.ScenarioPosition getScenarioPosition() {
220+
return scenarioPosition;
221+
}
214222
public Optional<Set<String>> getTags() {
215223
return tags;
216224
}
@@ -233,6 +241,7 @@ public static class Builder {
233241
private Optional<Set<String>> tags = Optional.empty();
234242
private Optional<String> longDescription = Optional.empty();
235243
private int lineNumber = -1;
244+
private Optional<GherkinScenario.ScenarioPosition> scenarioPosition;
236245

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

260+
public Builder withScenarioPosition(int depth, int ordinal, int tableIndex) {
261+
this.scenarioPosition = Optional.of(new GherkinScenario.ScenarioPosition(depth, ordinal, tableIndex));
262+
return this;
263+
}
264+
251265
public Builder withLongDescription(String longDescription) {
252266
this.longDescription = Optional.ofNullable(longDescription);
253267
return this;

src/main/java/com/pdsl/gherkin/PickleJarFactory.java

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public List<PickleJar> getPickleJars(Set<URI> testResources) {
8484
// Top level scenarios
8585
if (feature.getOptionalGherkinScenarios().isPresent()) {
8686
List<PickleJar.PickleJarScenario> topLevelPickles =
87-
convertScenariosToPickleJarScenarios(feature.getOptionalGherkinScenarios().get());
87+
convertScenariosToPickleJarScenarios(feature.getOptionalGherkinScenarios().get(), 0);
8888
pickleJarBuilder.withFeatureLevelScenarios(topLevelPickles);
8989
}
9090
// Add all rules
@@ -97,15 +97,18 @@ public List<PickleJar> getPickleJars(Set<URI> testResources) {
9797
}
9898

9999

100-
private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(List<GherkinScenario> scenarios) {
100+
private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(List<GherkinScenario> scenarios, int depth) {
101+
int nextOrdinal = 0;
101102
List<PickleJar.PickleJarScenario> pickleJarScenarios = new ArrayList<>();
102103
for (GherkinScenario scenario : scenarios) {
104+
nextOrdinal++;
103105
// If the scenario has an examples table the tags will need to be combined with the scenario level
104106
Set<String> tags = new HashSet<>();
105107
if (scenario.getTags().isPresent()) {
106108
tags.addAll(processTags(scenario.getTags().get()));
107109
}
108110
if (scenario.getExamples().isPresent()) {
111+
int tableIndex = 1;
109112
for (GherkinExamplesTable table : scenario.getExamples().get()) {
110113
Set<String> tableTags = new HashSet<>(tags);
111114
for (Map<String, GherkinExamplesTable.CellOfExamplesTable> substitutions : table.getRowsWithCell()) {
@@ -120,7 +123,8 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L
120123
scenario.getTitle().orElseThrow().getStringWithSubstitutions(substitutionsAsStrings),
121124
substitutedSteps)
122125
// All parameters should have the same line number, so just get the number from the first one
123-
.withLineNumber(substitutions.values().stream().findFirst().orElseThrow().lineNumber());
126+
.withLineNumber(substitutions.values().stream().findFirst().orElseThrow().lineNumber())
127+
.withScenarioPosition(depth, nextOrdinal, tableIndex++);
124128
if (scenario.getLongDescription().isPresent()) {
125129
builder.withLongDescription(scenario.getLongDescription().get().getStringWithSubstitutions(substitutionsAsStrings));
126130
}
@@ -147,7 +151,8 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L
147151
PickleJar.PickleJarScenario.Builder builder = new PickleJar.PickleJarScenario.Builder(
148152
scenario.getTitle().orElseThrow().getRawString(),
149153
stepBody)
150-
.withLineNumber(scenario.getLineNumber());
154+
.withLineNumber(scenario.getLineNumber())
155+
.withScenarioPosition(depth,nextOrdinal, 0);
151156
if (!tags.isEmpty()) {
152157
builder.withTags(processTags(tags));
153158
}
@@ -163,9 +168,9 @@ private List<PickleJar.PickleJarScenario> convertScenariosToPickleJarScenarios(L
163168

164169
private List<PickleJar.PickleJarRule> convertRulesToPickles(List<GherkinRule> rules) {
165170
List<PickleJar.PickleJarRule> pickleJarRules = new ArrayList<>();
166-
for (GherkinRule rule : rules) {
167-
168-
List<PickleJar.PickleJarScenario> scenarios = convertScenariosToPickleJarScenarios(rule.getScenarios().orElseThrow());
171+
for (int i=0; i < rules.size(); i++) {
172+
GherkinRule rule = rules.get(i);
173+
List<PickleJar.PickleJarScenario> scenarios = convertScenariosToPickleJarScenarios(rule.getScenarios().orElseThrow(), i+1);
169174
PickleJar.PickleJarRule.Builder builder = new PickleJar.PickleJarRule.Builder(rule.getTitle().orElseThrow(), scenarios);
170175
if (rule.getBackground().isPresent()) {
171176
builder.withBackground(rule.getBackground().get());
@@ -305,4 +310,26 @@ public void notifyObservers(String title, List<String> steps, Set<String> tags,
305310
observer.onScenarioConverted(title, steps, tags, substitutions);
306311
}
307312
}
313+
314+
public static class PdslGherkinSourceComparator implements Comparator<URI> {
315+
316+
private static final int NUMBER_INDEX = "line=".length();
317+
@Override
318+
public int compare(URI source1, URI source2) {
319+
String fragment1 = source1.getFragment();
320+
String fragment2 = source2.getFragment();
321+
int compareUris = source1.getRawSchemeSpecificPart().compareTo(source2.getRawSchemeSpecificPart());
322+
// If the scenarios came from the same file have the most recent scenario first
323+
if (compareUris == 0) {
324+
try {
325+
return Integer.compare(Integer.parseInt(fragment1.substring(NUMBER_INDEX)),
326+
Integer.parseInt(fragment2.substring(NUMBER_INDEX)));
327+
} catch (NumberFormatException e) {
328+
return 0;
329+
}
330+
}
331+
return compareUris;
332+
}
333+
}
334+
308335
}

src/main/java/com/pdsl/gherkin/models/GherkinScenario.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.pdsl.gherkin.models;
22

3+
import java.net.URI;
34
import java.util.*;
5+
import java.util.stream.Collectors;
46

57
public class GherkinScenario {
68
private final Optional<List<String>> tags;
79
private final Optional<GherkinString> title;
810
private final Optional<GherkinString> longDescription;
911
private final Optional<List<GherkinStep>> stepsList;
1012
private final Optional<List<GherkinExamplesTable>> examples;
13+
private final Optional<ScenarioPosition> scenarioPosition;
1114
private final int lineNumber;
1215

1316
public GherkinScenario(Builder builder) {
@@ -20,6 +23,7 @@ public GherkinScenario(Builder builder) {
2023
this.examples = builder.examples.isEmpty() ? Optional.empty()
2124
: Optional.of(builder.examples);
2225
this.lineNumber = builder.lineNumber;
26+
this.scenarioPosition = builder.scenarioPosition;
2327
}
2428

2529
/**
@@ -82,26 +86,55 @@ public GherkinScenario(Builder builder) {
8286
* Then this group ordinal is 2.1.0
8387
* }
8488
* </pre>
89+
*
8590
* @param ruleIndex the nth rule this scenario was derived from, 0 if not in a rule
86-
* @param ordinal the nth position of this scenario relative to others in the same depth
91+
* @param ordinal the nth position of this scenario relative to others in the same depth
8792
* @param testIndex 0 if not derived from an examples table, otherwise the nth row starting from 1
8893
*/
89-
public record ScenarioPosition(int ruleIndex, int ordinal, int testIndex) implements Comparator<ScenarioPosition> {
94+
public record ScenarioPosition(int ruleIndex, int ordinal, int testIndex) implements Comparable<ScenarioPosition> {
95+
96+
public static String RULE_INDEX= "ruleIndex";
97+
public static String ORDINAL = "ordinal";
98+
public static String TABLE_INDEX = "tableIndex";
99+
private static final ScenarioPositionComparator SINGLETON = new ScenarioPositionComparator();
100+
101+
private static class ScenarioPositionComparator implements Comparator<ScenarioPosition> {
102+
@Override
103+
public int compare(ScenarioPosition p1, ScenarioPosition p2) {
104+
if (p1.ruleIndex != p2.ruleIndex) {
105+
return Integer.compare(p1.ruleIndex, p2.ruleIndex);
106+
}
107+
if (p1.ordinal != p2.ordinal) {
108+
return Integer.compare(p1.ordinal, p2.ordinal);
109+
}
110+
return Integer.compare(p1.testIndex, p2.testIndex);
111+
}
112+
}
90113

91114
@Override
92-
public int compare(ScenarioPosition p1, ScenarioPosition p2) {
93-
if (p1.ruleIndex != p2.ruleIndex) {
94-
return Integer.compare(p1.ruleIndex, p2.ruleIndex);
95-
}
96-
if (p1.ordinal != p2.ordinal) {
97-
return Integer.compare(p1.ordinal, p2.ordinal);
98-
}
99-
return Integer.compare(p1.testIndex, p2.testIndex);
115+
public int compareTo(ScenarioPosition scenarioPosition) {
116+
return SINGLETON.compare(this, scenarioPosition);
100117
}
101118

119+
public static Optional<ScenarioPosition> from(URI uri) {
120+
Map<String, String> params = Arrays.stream(uri.getQuery().split("&"))
121+
.map(param -> param.split("="))
122+
.filter(arr -> arr.length == 2)
123+
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
124+
try {
125+
int ruleIndex = Integer.parseInt(params.get(RULE_INDEX));
126+
int ordinal = Integer.parseInt(params.get(ORDINAL));
127+
int tableIndex = Integer.parseInt(params.get(TABLE_INDEX));
128+
return Optional.of(new ScenarioPosition(ruleIndex, ordinal, tableIndex));
129+
} catch(RuntimeException e) {
130+
return Optional.empty();
131+
}
132+
}
102133
}
103134

104135

136+
public Optional<ScenarioPosition> getScenarioPositition() { return scenarioPosition; }
137+
105138
public Optional<List<String>> getTags() {
106139
return tags;
107140
}
@@ -133,7 +166,7 @@ public static class Builder {
133166
private String longDescription = "";
134167
private Optional<List<GherkinStep>> stepsList = Optional.empty();
135168
private int lineNumber = -1;
136-
private Optional<ScenarioPosition> scenarioPosition = Optional.empty();
169+
private Optional<ScenarioPosition> scenarioPosition = Optional.empty();
137170

138171
public GherkinScenario build() {
139172
return new GherkinScenario(this);

src/main/java/com/pdsl/gherkin/testcases/GherkinTestCaseSpecification.java

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)