Skip to content

Commit 407ad15

Browse files
committed
Make the distinction between feature identifiers and feature path
1 parent 1564b43 commit 407ad15

File tree

9 files changed

+266
-175
lines changed

9 files changed

+266
-175
lines changed

core/src/main/java/cucumber/runtime/RuntimeOptions.java

+8-35
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
import cucumber.api.SnippetType;
44
import cucumber.runtime.formatter.PluginFactory;
55
import cucumber.runtime.io.MultiLoader;
6-
import cucumber.runtime.io.Resource;
76
import cucumber.runtime.io.ResourceLoader;
7+
import io.cucumber.core.model.FeaturePath;
88
import io.cucumber.core.model.FeatureWithLines;
99
import cucumber.util.FixJava;
1010
import cucumber.util.Mapper;
1111
import gherkin.GherkinDialect;
1212
import gherkin.GherkinDialectProvider;
1313
import gherkin.IGherkinDialectProvider;
14+
import io.cucumber.core.model.RerunLoader;
1415
import io.cucumber.core.options.FeatureOptions;
1516
import io.cucumber.core.options.FilterOptions;
1617
import io.cucumber.core.options.PluginOptions;
1718
import io.cucumber.core.options.RunnerOptions;
1819
import io.cucumber.datatable.DataTable;
1920

2021
import java.io.File;
21-
import java.io.IOException;
2222
import java.io.InputStreamReader;
2323
import java.io.Reader;
2424
import java.net.URI;
@@ -29,7 +29,6 @@
2929
import java.util.ResourceBundle;
3030
import java.util.Set;
3131
import java.util.TreeSet;
32-
import java.util.regex.Matcher;
3332
import java.util.regex.Pattern;
3433

3534
import static cucumber.util.FixJava.join;
@@ -55,16 +54,15 @@ public String map(String keyword) {
5554
return keyword.replaceAll("[\\s',!]", "");
5655
}
5756
};
58-
private static final Pattern RERUN_PATH_SPECIFICATION = Pattern.compile("(?m:^| |)(.*?\\.feature(?:(?::\\d+)*))");
5957
private final List<String> glue = new ArrayList<String>();
6058
private final List<String> tagFilters = new ArrayList<String>();
6159
private final List<Pattern> nameFilters = new ArrayList<Pattern>();
6260
private final Map<URI, Set<Integer>> lineFilters = new HashMap<>();
6361
private final List<URI> featurePaths = new ArrayList<>();
6462

6563
private final List<String> junitOptions = new ArrayList<String>();
66-
private final ResourceLoader resourceLoader;
6764
private final char fileSeparatorChar;
65+
private final RerunLoader rerunLoader;
6866
private boolean dryRun;
6967
private boolean strict = false;
7068
private boolean monochrome = false;
@@ -109,7 +107,7 @@ public RuntimeOptions(ResourceLoader resourceLoader, Env env, List<String> argv)
109107

110108
RuntimeOptions(char fileSeparatorChar, ResourceLoader resourceLoader, Env env, List<String> argv) {
111109
this.fileSeparatorChar = fileSeparatorChar;
112-
this.resourceLoader = resourceLoader;
110+
this.rerunLoader= new RerunLoader(resourceLoader);
113111
argv = new ArrayList<>(argv); // in case the one passed in is unmodifiable.
114112
parse(argv);
115113

@@ -190,10 +188,10 @@ private void parse(List<String> args) {
190188
printUsage();
191189
throw new CucumberException("Unknown option: " + arg);
192190
} else if (arg.startsWith("@")) {
193-
FeatureWithLines featureWithLines = parseFeatureWithLines(arg.substring(1));
194-
processPathWitheLinesFromRerunFile(parsedLineFilters, parsedFeaturePaths, featureWithLines.uri());
191+
URI rerunFile = FeaturePath.parse(arg.substring(1));
192+
processPathWitheLinesFromRerunFile(parsedLineFilters, parsedFeaturePaths, rerunFile);
195193
} else if (!arg.isEmpty()){
196-
FeatureWithLines featureWithLines = parseFeatureWithLines(arg);
194+
FeatureWithLines featureWithLines = FeatureWithLines.parse(arg);
197195
processFeatureWithLines(parsedLineFilters, parsedFeaturePaths, featureWithLines);
198196
}
199197
}
@@ -226,10 +224,6 @@ private void parse(List<String> args) {
226224
parsedPluginData.updatePluginSummaryPrinterNames(pluginSummaryPrinterNames);
227225
}
228226

229-
private FeatureWithLines parseFeatureWithLines(String pathWithLines) {
230-
return FeatureWithLines.parse(pathWithLines);
231-
}
232-
233227
private void addLineFilters(Map<URI, Set<Integer>> parsedLineFilters, URI key, Set<Integer> lines) {
234228
if(lines.isEmpty()){
235229
return;
@@ -247,33 +241,12 @@ private void processFeatureWithLines(Map<URI, Set<Integer>> parsedLineFilters, L
247241
}
248242

249243
private void processPathWitheLinesFromRerunFile(Map<URI, Set<Integer>> parsedLineFilters, List<URI> parsedFeaturePaths, URI rerunPath) {
250-
for (FeatureWithLines featureWithLines : loadRerunFile(rerunPath)) {
244+
for (FeatureWithLines featureWithLines : rerunLoader.load(rerunPath)) {
251245
processFeatureWithLines(parsedLineFilters, parsedFeaturePaths, featureWithLines);
252246
}
253247
}
254248

255-
private List<FeatureWithLines> loadRerunFile(URI rerunPath) {
256-
List<FeatureWithLines> featurePaths = new ArrayList<>();
257-
Iterable<Resource> resources = resourceLoader.resources(rerunPath, null);
258-
for (Resource resource : resources) {
259-
String source = read(resource);
260-
if (!source.isEmpty()) {
261-
Matcher matcher = RERUN_PATH_SPECIFICATION.matcher(source);
262-
while (matcher.find()) {
263-
featurePaths.add(parseFeatureWithLines(matcher.group(1)));
264-
}
265-
}
266-
}
267-
return featurePaths;
268-
}
269249

270-
private static String read(Resource resource) {
271-
try {
272-
return FixJava.readReader(new InputStreamReader(resource.getInputStream()));
273-
} catch (IOException e) {
274-
throw new CucumberException("Failed to read resource:" + resource.getPath(), e);
275-
}
276-
}
277250

278251
private String parseGlue(String gluePath) {
279252
return convertFileSeparatorToForwardSlash(gluePath);
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
11
package io.cucumber.core.model;
22

3-
import java.io.File;
43
import java.net.URI;
5-
import java.net.URISyntaxException;
64

75
/**
8-
* Identifies a feature.
6+
* Identifies a single feature.
97
* <p>
10-
* Features are identified by a URI. This URI can either be absolute:
11-
* {@code scheme:/absolute/path/to.feature}, or relative to the
12-
* current working directory: {@code scheme:relative/path/to.feature}.
13-
* <p>
14-
* In either form, when the scheme is omitted {@code file} will be
15-
* assumed: {@code path/to.feature}.
16-
* <p>
17-
* <p>
18-
* On systems that use a {@code File.separatorChar} other then `{@code /}`
19-
* {@code File.separatorChar} can be used as a path separator. When
20-
* doing so when the scheme must be omitted: {@code path\to.feature}.
21-
* <em>It is recommended to use `{@code /}` as the path separator.</em>
8+
* Features are identified by a URI as defined in {@link FeaturePath}.
9+
* Additionally the scheme specific part must end with {@code .feature}
2210
*
2311
* @see FeatureWithLines
2412
*/
@@ -29,48 +17,15 @@ private FeatureIdentifier() {
2917
}
3018

3119
public static URI parse(String featureIdentifier) {
32-
if (nonStandardPathSeparatorInUse(featureIdentifier)) {
33-
String standardized = replaceNonStandardPathSeparator(featureIdentifier);
34-
return parseAssumeFileScheme(standardized);
35-
}
36-
37-
if (probablyURI(featureIdentifier)) {
38-
return parseProbableURI(featureIdentifier);
39-
}
40-
41-
return parseAssumeFileScheme(featureIdentifier);
20+
return parse(FeaturePath.parse(featureIdentifier));
4221
}
4322

44-
private static URI parseProbableURI(String featureIdentifier) {
45-
return URI.create(featureIdentifier);
46-
}
47-
48-
private static boolean probablyURI(String featureIdentifier) {
49-
return featureIdentifier.matches("^\\w+:.*$");
50-
}
51-
52-
53-
private static String replaceNonStandardPathSeparator(String featureIdentifier) {
54-
return featureIdentifier.replace(File.separatorChar, '/');
55-
}
56-
57-
private static boolean nonStandardPathSeparatorInUse(String featureIdentifier) {
58-
return File.separatorChar != '/' && featureIdentifier.contains(File.separator);
59-
}
60-
61-
private static URI parseAssumeFileScheme(String featureIdentifier) {
62-
File featureFile = new File(featureIdentifier);
63-
if (featureFile.isAbsolute()) {
64-
return featureFile.toURI();
65-
}
66-
67-
try {
68-
URI root = new File("").toURI();
69-
URI relative = root.relativize(featureFile.toURI());
70-
// Scheme is lost by relativize
71-
return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment());
72-
} catch (URISyntaxException e) {
73-
throw new IllegalArgumentException(e.getMessage(), e);
23+
public static URI parse(URI featureIdentifier) {
24+
String schemeSpecificPart = featureIdentifier.getSchemeSpecificPart();
25+
if (!schemeSpecificPart.endsWith(".feature")) {
26+
throw new IllegalArgumentException("featureIdentifier does not reference a single feature file: " + featureIdentifier);
7427
}
28+
return featureIdentifier;
7529
}
30+
7631
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.cucumber.core.model;
2+
3+
import java.io.File;
4+
import java.net.URI;
5+
import java.net.URISyntaxException;
6+
7+
8+
/**
9+
* A feature path is a URI to a single feature file or directory of features.
10+
* <p>
11+
* This URI can either be absolute:
12+
* {@code scheme:/absolute/path/to.feature}, or relative to the
13+
* current working directory: {@code scheme:relative/path/to.feature}. In
14+
* either form, when the scheme is omitted {@code file} will be assumed.
15+
* <p>
16+
* On systems that use a {@code File.separatorChar} other then `{@code /}`
17+
* {@code File.separatorChar} can be used as a path separator. When
18+
* doing so when the scheme must be omitted: {@code path\to.feature}.
19+
* <em>It is recommended to use `{@code /}` as the path separator.</em>
20+
*
21+
* @see FeatureIdentifier
22+
* @see FeatureWithLines
23+
*/
24+
public class FeaturePath {
25+
26+
private FeaturePath() {
27+
28+
}
29+
30+
public static URI parse(String featureIdentifier) {
31+
if (nonStandardPathSeparatorInUse(featureIdentifier)) {
32+
String standardized = replaceNonStandardPathSeparator(featureIdentifier);
33+
return parseAssumeFileScheme(standardized);
34+
}
35+
36+
if (probablyURI(featureIdentifier)) {
37+
return parseProbableURI(featureIdentifier);
38+
}
39+
40+
return parseAssumeFileScheme(featureIdentifier);
41+
}
42+
43+
private static URI parseProbableURI(String featureIdentifier) {
44+
return URI.create(featureIdentifier);
45+
}
46+
47+
private static boolean probablyURI(String featureIdentifier) {
48+
return featureIdentifier.matches("^\\w+:.*$");
49+
}
50+
51+
52+
private static String replaceNonStandardPathSeparator(String featureIdentifier) {
53+
return featureIdentifier.replace(File.separatorChar, '/');
54+
}
55+
56+
private static boolean nonStandardPathSeparatorInUse(String featureIdentifier) {
57+
return File.separatorChar != '/' && featureIdentifier.contains(File.separator);
58+
}
59+
60+
private static URI parseAssumeFileScheme(String featureIdentifier) {
61+
File featureFile = new File(featureIdentifier);
62+
if (featureFile.isAbsolute()) {
63+
return featureFile.toURI();
64+
}
65+
66+
try {
67+
URI root = new File("").toURI();
68+
URI relative = root.relativize(featureFile.toURI());
69+
// Scheme is lost by relativize
70+
return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment());
71+
} catch (URISyntaxException e) {
72+
throw new IllegalArgumentException(e.getMessage(), e);
73+
}
74+
}
75+
}

core/src/main/java/io/cucumber/core/model/FeatureWithLines.java

+7-17
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
* @see FeatureIdentifier
2525
*/
2626
public class FeatureWithLines implements Serializable {
27-
private static final long serialVersionUID = 20190126L;
2827
private static final Pattern FEATURE_COLON_LINE_PATTERN = Pattern.compile("^(.*?):([\\d:]+)$");
28+
private static final long serialVersionUID = 20190126L;
2929
private static final String INVALID_PATH_MESSAGE = " is not valid. Try URI[:LINE]*";
3030

3131
private final URI uri;
@@ -37,26 +37,22 @@ private FeatureWithLines(URI uri, Collection<Integer> lines) {
3737
}
3838

3939
public static FeatureWithLines create(URI uri, Collection<Integer> lines) {
40-
return new FeatureWithLines(uri, lines);
40+
return new FeatureWithLines(FeatureIdentifier.parse(uri), lines);
4141
}
4242

4343
public static FeatureWithLines parse(String uri, Collection<Integer> lines) {
44-
return new FeatureWithLines(FeatureIdentifier.parse(uri), lines);
44+
return create(FeaturePath.parse(uri), lines);
4545
}
4646

4747
public static FeatureWithLines parse(String uri, Integer... lines) {
48-
return new FeatureWithLines(FeatureIdentifier.parse(uri), asList(lines));
48+
return parse(uri, asList(lines));
4949
}
5050

5151
public static FeatureWithLines parse(String featurePath) {
5252
Matcher matcher = FEATURE_COLON_LINE_PATTERN.matcher(featurePath);
5353

54-
try {
55-
if (!matcher.matches()) {
56-
return parseFeatureIdentifier(featurePath);
57-
}
58-
} catch (IllegalArgumentException e) {
59-
throw new IllegalArgumentException(featurePath + INVALID_PATH_MESSAGE, e);
54+
if (!matcher.matches()) {
55+
throw new IllegalArgumentException(featurePath + INVALID_PATH_MESSAGE);
6056
}
6157

6258
String uriGroup = matcher.group(1);
@@ -72,14 +68,8 @@ public static FeatureWithLines parse(String featurePath) {
7268
}
7369

7470
private static FeatureWithLines parseFeatureIdentifierAndLines(String uriGroup, String linesGroup) {
75-
URI uri = FeatureIdentifier.parse(uriGroup);
7671
List<Integer> lines = toInts(linesGroup.split(":"));
77-
return new FeatureWithLines(uri, lines);
78-
}
79-
80-
private static FeatureWithLines parseFeatureIdentifier(String pathName) {
81-
URI uri = FeatureIdentifier.parse(pathName);
82-
return new FeatureWithLines(uri, Collections.<Integer>emptyList());
72+
return parse(uriGroup, lines);
8373
}
8474

8575
private static List<Integer> toInts(String[] strings) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.cucumber.core.model;
2+
3+
import cucumber.runtime.CucumberException;
4+
import cucumber.runtime.io.Resource;
5+
import cucumber.runtime.io.ResourceLoader;
6+
import cucumber.util.FixJava;
7+
8+
import java.io.IOException;
9+
import java.io.InputStreamReader;
10+
import java.net.URI;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
16+
public class RerunLoader {
17+
private static final Pattern RERUN_PATH_SPECIFICATION = Pattern.compile("(?m:^| |)(.*?\\.feature(?:(?::\\d+)*))");
18+
19+
private final ResourceLoader resourceLoader;
20+
21+
public RerunLoader(ResourceLoader resourceLoader) {
22+
this.resourceLoader = resourceLoader;
23+
}
24+
25+
public List<FeatureWithLines> load(URI rerunPath) {
26+
Iterable<Resource> resources = resourceLoader.resources(rerunPath, null);
27+
28+
if(!resources.iterator().hasNext()){
29+
throw new CucumberException("Rerun file did not exist: " + rerunPath);
30+
}
31+
32+
List<FeatureWithLines> featurePaths = new ArrayList<>();
33+
for (Resource resource : resources) {
34+
String source = read(resource);
35+
if (!source.isEmpty()) {
36+
Matcher matcher = RERUN_PATH_SPECIFICATION.matcher(source);
37+
while (matcher.find()) {
38+
featurePaths.add(FeatureWithLines.parse(matcher.group(1)));
39+
}
40+
}
41+
}
42+
return featurePaths;
43+
}
44+
45+
private static String read(Resource resource) {
46+
try {
47+
return FixJava.readReader(new InputStreamReader(resource.getInputStream()));
48+
} catch (IOException e) {
49+
throw new CucumberException("Failed to read resource:" + resource.getPath(), e);
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)