Skip to content

Commit

Permalink
[Core] Parse rerun file in RuntimeOptions
Browse files Browse the repository at this point in the history
Cucumber runs features on the feature path. The feature path can consist
of features, features with line filters or a rerun file containing
features with line filters.

The current implementation would pass the rerun file onwards as if
it were another feature file. The FeatureLoader would then deal with it.

This behaviour is undesirable for two reasons:

1. It comes as a surprise that the FeatureLoader also parses rerun
   files.
2. It comes as a surprise that the Filters also parses rerun files.
3. The rerun file and syntax are part of the command line runner. Other
   runners have different methods to rerun their own tests. As such it
   should be contained in RuntimeOptions and not leak into other parts
   of the core.

Moving rerun file parsing into CucumberOptions now allows the following
to be done:

1. Focus the the FeatureLoader on loading features only
2. Consolidate parsing resources in to GherkinFeatures and compiling
   GherkinFeature into pickles in a single FeatureParser that now
   produces a ready to use CucumberFeature object.
3. Reduce the responsibility of the FeatureBuilder to building a list of
   features.
  • Loading branch information
mpkorstanje committed Jan 5, 2019
1 parent 18a1863 commit 19cc359
Show file tree
Hide file tree
Showing 23 changed files with 471 additions and 642 deletions.
21 changes: 0 additions & 21 deletions core/src/main/java/cucumber/runtime/FeatureCompiler.java

This file was deleted.

7 changes: 2 additions & 5 deletions core/src/main/java/cucumber/runtime/Runtime.java
Expand Up @@ -11,7 +11,6 @@
import cucumber.runner.SingletonRunnerSupplier;
import cucumber.runner.ThreadLocalRunnerSupplier;
import cucumber.runtime.filter.Filters;
import cucumber.runtime.filter.RerunFilters;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.io.MultiLoader;
Expand Down Expand Up @@ -74,9 +73,8 @@ public void run() {
final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();
runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter);

final FeatureCompiler compiler = new FeatureCompiler();
for (CucumberFeature feature : features) {
for (final PickleEvent pickleEvent : compiler.compileFeature(feature)) {
for (final PickleEvent pickleEvent : feature.getPickles()) {
if (filters.matchesFilters(pickleEvent)) {
executor.execute(new Runnable() {
@Override
Expand Down Expand Up @@ -207,8 +205,7 @@ public Runtime build() {
? this.featureSupplier
: new FeaturePathFeatureSupplier(featureLoader, this.runtimeOptions);

final RerunFilters rerunFilters = new RerunFilters(this.runtimeOptions, featureLoader);
final Filters filters = new Filters(this.runtimeOptions, rerunFilters);
final Filters filters = new Filters(this.runtimeOptions);
return new Runtime(plugins, this.runtimeOptions, eventBus, filters, runnerSupplier, featureSupplier, executor);
}
}
Expand Down
92 changes: 78 additions & 14 deletions core/src/main/java/cucumber/runtime/RuntimeOptions.java
@@ -1,26 +1,34 @@
package cucumber.runtime;

import cucumber.api.SnippetType;
import io.cucumber.core.options.FeatureOptions;
import io.cucumber.core.options.FilterOptions;
import io.cucumber.core.options.PluginOptions;
import io.cucumber.core.options.RunnerOptions;
import io.cucumber.datatable.DataTable;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.Resource;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.model.PathWithLines;
import cucumber.util.Encoding;
import cucumber.util.FixJava;
import cucumber.util.Mapper;
import gherkin.GherkinDialect;
import gherkin.GherkinDialectProvider;
import gherkin.IGherkinDialectProvider;
import io.cucumber.core.options.FeatureOptions;
import io.cucumber.core.options.FilterOptions;
import io.cucumber.core.options.PluginOptions;
import io.cucumber.core.options.RunnerOptions;
import io.cucumber.datatable.DataTable;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cucumber.util.FixJava.join;
Expand All @@ -46,14 +54,16 @@ public String map(String keyword) {
return keyword.replaceAll("[\\s',!]", "");
}
};

private static final Pattern RERUN_PATH_SPECIFICATION = Pattern.compile("(?m:^| |)(.*?\\.feature(?:(?::\\d+)*))");
private final List<String> glue = new ArrayList<String>();
private final List<String> tagFilters = new ArrayList<String>();
private final List<Pattern> nameFilters = new ArrayList<Pattern>();
private final Map<String, List<Long>> lineFilters = new HashMap<String, List<Long>>();
private final List<String> featurePaths = new ArrayList<String>();

private final List<String> junitOptions = new ArrayList<String>();
private final ResourceLoader resourceLoader;
private final char fileSeparatorChar;
private boolean dryRun;
private boolean strict = false;
private boolean monochrome = false;
Expand Down Expand Up @@ -89,7 +99,17 @@ public RuntimeOptions(List<String> argv) {
}

public RuntimeOptions(Env env, List<String> argv) {
argv = new ArrayList<String>(argv); // in case the one passed in is unmodifiable.
this(new MultiLoader(RuntimeOptions.class.getClassLoader()), env, argv);
}

public RuntimeOptions(ResourceLoader resourceLoader, Env env, List<String> argv) {
this(File.separatorChar, resourceLoader, env, argv);
}

RuntimeOptions(char fileSeparatorChar, ResourceLoader resourceLoader, Env env, List<String> argv) {
this.fileSeparatorChar = fileSeparatorChar;
this.resourceLoader = resourceLoader;
argv = new ArrayList<>(argv); // in case the one passed in is unmodifiable.
parse(argv);

String cucumberOptionsFromEnv = env.get("cucumber.options");
Expand Down Expand Up @@ -143,7 +163,7 @@ private void parse(List<String> args) {
}
} else if (arg.equals("--glue") || arg.equals("-g")) {
String gluePath = args.remove(0);
parsedGlue.add(gluePath);
parsedGlue.add(parseGlue(gluePath));
} else if (arg.equals("--tags") || arg.equals("-t")) {
parsedTagFilters.add(args.remove(0));
} else if (arg.equals("--plugin") || arg.equals("--add-plugin") || arg.equals("-p")) {
Expand All @@ -169,11 +189,18 @@ private void parse(List<String> args) {
printUsage();
throw new CucumberException("Unknown option: " + arg);
} else {
PathWithLines pathWithLines = new PathWithLines(arg);
parsedFeaturePaths.add(pathWithLines.path);
if (!pathWithLines.lines.isEmpty()) {
String key = pathWithLines.path.replace("classpath:", "");
addLineFilters(parsedLineFilters, key, pathWithLines.lines);
List<PathWithLines> paths;
if (arg.startsWith("@")) {
paths = loadRerunFile(arg.substring(1));
} else {
paths = parsePathWithLines(arg);
}
for (PathWithLines pathWithLines : paths) {
parsedFeaturePaths.add(pathWithLines.path);
if (!pathWithLines.lines.isEmpty()) {
String key = pathWithLines.path.replace("classpath:", "");
addLineFilters(parsedLineFilters, key, pathWithLines.lines);
}
}
}
}
Expand Down Expand Up @@ -216,13 +243,46 @@ private void addLineFilters(Map<String, List<Long>> parsedLineFilters, String ke

private boolean haveLineFilters(List<String> parsedFeaturePaths) {
for (String pathName : parsedFeaturePaths) {
if (pathName.startsWith("@") || PathWithLines.hasLineFilters(pathName)) {
if (PathWithLines.hasLineFilters(pathName)) {
return true;
}
}
return false;
}

private List<PathWithLines> loadRerunFile(String rerunPath) {
List<PathWithLines> featurePaths = new ArrayList<>();
Iterable<Resource> resources = resourceLoader.resources(rerunPath, null);
for (Resource resource : resources) {
String source = read(resource);
if (!source.isEmpty()) {
Matcher matcher = RERUN_PATH_SPECIFICATION.matcher(source);
while (matcher.find()) {
featurePaths.addAll(parsePathWithLines(matcher.group(1)));
}
}
}
return featurePaths;
}

private List<PathWithLines> parsePathWithLines(String pathWithLineFilter) {
String normalizedPath = convertFileSeparatorToForwardSlash(pathWithLineFilter);
PathWithLines pathWithLines = new PathWithLines(normalizedPath);
return Collections.singletonList(pathWithLines);
}

private static String read(Resource resource) {
try {
return Encoding.readFile(resource);
} catch (IOException e) {
throw new CucumberException("Failed to read resource:" + resource.getPath(), e);
}
}

private String parseGlue(String gluePath) {
return convertFileSeparatorToForwardSlash(gluePath);
}

private void printUsage() {
loadUsageTextIfNeeded();
System.out.println(usageText);
Expand Down Expand Up @@ -408,4 +468,8 @@ public void updateNameList(List<String> nameList) {
}
}

private String convertFileSeparatorToForwardSlash(String path) {
return path.replace(fileSeparatorChar, '/');
}

}
22 changes: 3 additions & 19 deletions core/src/main/java/cucumber/runtime/filter/Filters.java
Expand Up @@ -11,15 +11,11 @@
public class Filters {

private final List<PicklePredicate> filters;
private final FilterOptions runtimeOptions;
private final RerunFilters rerunFilters;

public Filters(FilterOptions filterOPtions, RerunFilters rerunFilters) {
this.runtimeOptions = filterOPtions;
this.rerunFilters = rerunFilters;
public Filters(FilterOptions filterOPtions) {

filters = new ArrayList<PicklePredicate>();
List<String> tagFilters = this.runtimeOptions.getTagFilters();
filters = new ArrayList<>();
List<String> tagFilters = filterOPtions.getTagFilters();
if (!tagFilters.isEmpty()) {
this.filters.add(new TagPredicate(tagFilters));
}
Expand All @@ -28,10 +24,6 @@ public Filters(FilterOptions filterOPtions, RerunFilters rerunFilters) {
this.filters.add(new NamePredicate(nameFilters));
}
Map<String, List<Long>> lineFilters = filterOPtions.getLineFilters();
Map<String, List<Long>> rerunlineFilters = rerunFilters.processRerunFiles();
for (Map.Entry<String,List<Long>> line: rerunlineFilters.entrySet()) {
addLineFilters(lineFilters, line.getKey(), line.getValue());
}
if (!lineFilters.isEmpty()) {
this.filters.add(new LinePredicate(lineFilters));
}
Expand All @@ -46,12 +38,4 @@ public boolean matchesFilters(PickleEvent pickleEvent) {
return true;
}

private void addLineFilters(Map<String, List<Long>> parsedLineFilters, String key, List<Long> lines) {
if (parsedLineFilters.containsKey(key)) {
parsedLineFilters.get(key).addAll(lines);
} else {
parsedLineFilters.put(key, lines);
}
}

}
41 changes: 0 additions & 41 deletions core/src/main/java/cucumber/runtime/filter/RerunFilters.java

This file was deleted.

38 changes: 33 additions & 5 deletions core/src/main/java/cucumber/runtime/model/CucumberFeature.java
Expand Up @@ -3,21 +3,32 @@
import cucumber.api.event.TestSourceRead;
import cucumber.runner.EventBus;
import gherkin.ast.GherkinDocument;
import gherkin.events.PickleEvent;

import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

public class CucumberFeature implements Serializable {
private static final long serialVersionUID = 1L;
public class CucumberFeature {
private final String uri;
private final List<PickleEvent> pickles;
private GherkinDocument gherkinDocument;
private String gherkinSource;


public CucumberFeature(GherkinDocument gherkinDocument, String uri, String gherkinSource) {
public CucumberFeature(GherkinDocument gherkinDocument, String uri, String gherkinSource, List<PickleEvent> pickles) {
this.gherkinDocument = gherkinDocument;
this.uri = uri;
this.gherkinSource = gherkinSource;
this.pickles = pickles;
}

public List<PickleEvent> getPickles() {
return pickles;
}

public String getName() {
return gherkinDocument.getFeature().getName();
}

public GherkinDocument getGherkinFeature() {
Expand All @@ -29,7 +40,24 @@ public String getUri() {
}

public void sendTestSourceRead(EventBus bus) {
bus.send(new TestSourceRead(bus.getTime(), uri, gherkinSource));
bus.send(new TestSourceRead(bus.getTime(), getUri(), gherkinSource));
}

String getSource() {
return gherkinSource;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CucumberFeature that = (CucumberFeature) o;
return uri.equals(that.uri);
}

@Override
public int hashCode() {
return Objects.hash(uri);
}

public static class CucumberFeatureUriComparator implements Comparator<CucumberFeature> {
Expand Down

0 comments on commit 19cc359

Please sign in to comment.