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] Read rerun files from directories #2710

Merged
merged 16 commits into from
Apr 13, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- [JUnit Platform Engine] Add constant for fixed.max-pool-size property ([#2713](https://github.com/cucumber/cucumber-jvm/pull/2713) M.P. Korstanje)
- [Core] Support directories containing exclusively rerun files using the `@path/to/rerun` syntax ([#2710](https://github.com/cucumber/cucumber-jvm/pull/2710) Daniel Whitney, M.P. Korstanje)
- [Core] Improved event bus performance using UUID generator selectable through SPI ([#2703](https://github.com/cucumber/cucumber-jvm/pull/2703) Julien Kronegg)

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions cucumber-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ cucumber.execution.wip= # true or false. default: false.
# Fails if there any passing scenarios
# CLI only.

cucumber.features= # comma separated paths to feature files.
# example: path/to/example.feature, path/to/other.feature
cucumber.features= # comma separated list of feature paths.
# format: [ PATH[.feature[:LINE]*] | URI[.feature[:LINE]*] | @PATH ]
# example: path/to/features, classpath:com/example/features, path/to/example.feature:42, @path/to/rerun.txt

cucumber.filter.name= # a regular expression
# only scenarios with matching names are executed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
* Identifies either a directory containing feature files, a specific feature or
* specific scenarios and examples (pickles) in a feature.
* <p>
* The syntax of a a feature with lines defined as either a {@link FeaturePath}
* or a {@link FeatureIdentifier} followed by a sequence of line numbers each
* 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
* preceded by a colon.
*/
public class FeatureWithLines implements Serializable {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.cucumber.core.options;

import io.cucumber.core.exception.CucumberException;
import io.cucumber.core.feature.FeatureWithLines;
import io.cucumber.core.feature.GluePath;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
Expand All @@ -17,8 +16,6 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -59,7 +56,6 @@
import static io.cucumber.core.cli.CommandlineOptions.WIP;
import static io.cucumber.core.cli.CommandlineOptions.WIP_SHORT;
import static io.cucumber.core.options.ObjectFactoryParser.parseObjectFactory;
import static io.cucumber.core.options.OptionsFileParser.parseFeatureWithLinesFile;
import static io.cucumber.core.options.UuidGeneratorParser.parseUuidGenerator;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -178,13 +174,9 @@ private RuntimeOptionsBuilder parse(List<String> args) {
exitCode = 1;
return parsedOptions;
} else if (!arg.isEmpty()) {
if (arg.startsWith("@")) {
Path rerunFile = Paths.get(arg.substring(1));
parsedOptions.addRerun(parseFeatureWithLinesFile(rerunFile));
} else {
FeatureWithLines featureWithLines = FeatureWithLines.parse(arg);
parsedOptions.addFeature(featureWithLines);
}
FeatureWithLinesOrRerunPath parsed = FeatureWithLinesOrRerunPath.parse(arg);
parsed.getFeaturesToRerun().ifPresent(parsedOptions::addRerun);
parsed.getFeatureWithLines().ifPresent(parsedOptions::addFeature);
}
}

Expand Down
32 changes: 18 additions & 14 deletions cucumber-core/src/main/java/io/cucumber/core/options/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,26 @@ public final class Constants {
public static final String WIP_PROPERTY_NAME = "cucumber.execution.wip";

/**
* Property name used to set feature location: {@value}
* Property name used to select features: {@value}
* <p>
* A comma separated list of:
* A comma separated list of feature paths. A feature path is constructed as
* {@code [ PATH[.feature[:LINE]*] | URI[.feature[:LINE]*] | @PATH ] }
* <p>
* Examples:
* <ul>
* <li>{@code path/to/dir} - Load the files with the extension ".feature"
* for the directory {@code path} and its sub directories.
* <li>{@code path/name.feature} - Load the feature file
* {@code path/name.feature} from the file system.</li>
* <li>{@code classpath:path/name.feature} - Load the feature file
* {@code path/name.feature} from the classpath.</li>
* <li>{@code path/name.feature:3:9} - Load the scenarios on line 3 and line
* 9 in the file {@code path/name.feature}.</li>
* <li>{@code @path/file} - Load {@code path/file} from the file system and
* parse feature paths.</li>
* <li>{@code @classpath:path/file} - Load {@code path/file} from the
* classpath and parse feature paths.</li>
* <li>{@code src/test/resources/features} -- All features in the
* {@code src/test/resources/features} directory</li>
* <li>{@code classpath:com/example/application} -- All features in the
* {@code com.example.application} package</li>
* <li>{@code in-memory:/features} -- All features in the {@code /features}
* directory on an in memory file system supported by
* {@link java.nio.file.FileSystems}</li>
* <li>{@code src/test/resources/features/example.feature:42} -- The
* scenario or example at line 42 in the example feature file</li>
* <li>{@code @target/rerun} -- All the scenarios in the files in the rerun
* directory</li>
* <li>{@code @target/rerun/RunCucumber.txt} -- All the scenarios in
* RunCucumber.txt file</li>
* </ul>
*
* @see io.cucumber.core.feature.FeatureWithLines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@
import io.cucumber.tagexpressions.TagExpressionException;
import io.cucumber.tagexpressions.TagExpressionParser;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;

import static io.cucumber.core.options.OptionsFileParser.parseFeatureWithLinesFile;
import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX;
import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -133,13 +130,9 @@ private void addGlue(CucumberOptions options, RuntimeOptionsBuilder args) {
private void addFeatures(CucumberOptions options, RuntimeOptionsBuilder args) {
if (options != null && options.features().length != 0) {
for (String feature : options.features()) {
if (feature.startsWith("@")) {
Path rerunFile = Paths.get(feature.substring(1));
args.addRerun(parseFeatureWithLinesFile(rerunFile));
} else {
FeatureWithLines featureWithLines = FeatureWithLines.parse(feature);
args.addFeature(featureWithLines);
}
FeatureWithLinesOrRerunPath parsed = FeatureWithLinesOrRerunPath.parse(feature);
parsed.getFeaturesToRerun().ifPresent(args::addRerun);
parsed.getFeatureWithLines().ifPresent(args::addFeature);
}
featuresSpecified = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package io.cucumber.core.options;

import io.cucumber.core.exception.CucumberException;
import io.cucumber.core.feature.FeatureWithLines;
import io.cucumber.core.feature.GluePath;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.tagexpressions.TagExpressionParser;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static io.cucumber.core.options.Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.EXECUTION_DRY_RUN_PROPERTY_NAME;
Expand All @@ -34,7 +30,6 @@
import static io.cucumber.core.options.Constants.SNIPPET_TYPE_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.UUID_GENERATOR_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.WIP_PROPERTY_NAME;
import static io.cucumber.core.options.OptionsFileParser.parseFeatureWithLinesFile;
import static java.util.Arrays.stream;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -72,16 +67,11 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) {

parseAll(properties,
FEATURES_PROPERTY_NAME,
splitAndThenFlatMap(CucumberPropertiesParser::parseFeatureFile),
builder::addFeature);

parseAll(properties,
// For historical reasons rerun files are also provided through the
// feature property. They are differentiated by prefixing the uri
// name with an `@` symbol.
FEATURES_PROPERTY_NAME,
splitAndThenFlatMap(CucumberPropertiesParser::parseRerunFile),
builder::addRerun);
splitAndMap(FeatureWithLinesOrRerunPath::parse),
parsed -> {
parsed.getFeaturesToRerun().ifPresent(builder::addRerun);
parsed.getFeatureWithLines().ifPresent(builder::addFeature);
});

parse(properties,
FILTER_NAME_PROPERTY_NAME,
Expand Down Expand Up @@ -178,21 +168,6 @@ private <T> void parseAll(
}
}

private static <T> Function<String, Collection<T>> splitAndThenFlatMap(Function<String, Stream<T>> parse) {
return combined -> stream(combined.split(","))
.map(String::trim)
.filter(part -> !part.isEmpty())
.flatMap(parse)
.collect(toList());
}

private static Stream<FeatureWithLines> parseFeatureFile(String property) {
if (property.startsWith("@")) {
return Stream.empty();
}
return Stream.of(FeatureWithLines.parse(property));
}

private static <T> Function<String, Collection<T>> splitAndMap(Function<String, T> parse) {
return combined -> stream(combined.split(","))
.map(String::trim)
Expand All @@ -201,12 +176,4 @@ private static <T> Function<String, Collection<T>> splitAndMap(Function<String,
.collect(toList());
}

private static Stream<Collection<FeatureWithLines>> parseRerunFile(String property) {
if (property.startsWith("@")) {
Path rerunFile = Paths.get(property.substring(1));
return Stream.of(parseFeatureWithLinesFile(rerunFile));
}
return Stream.empty();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.cucumber.core.options;

import io.cucumber.core.feature.FeatureWithLines;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Optional;

/**
* Identifies either:
* <li>
* <ul>
* a single rerun file,
* </ul>
* <ul>
* a directory of containing exclusively rerun files,
* </ul>
* <ul>
* a directory containing feature files,
* </ul>
* <ul>
* a specific feature,
* </ul>
* <ul>
* or specific scenarios and examples (pickles) in a feature
* </ul>
* </li>
* <p>
* The syntax is either a {@link FeatureWithLines} or an {@code @} followed by a
* {@link RerunPath}.
*/
class FeatureWithLinesOrRerunPath {

private final FeatureWithLines featureWithLines;
private final Collection<FeatureWithLines> featuresWithLinesToRerun;

FeatureWithLinesOrRerunPath(
FeatureWithLines featureWithLines, Collection<FeatureWithLines> featuresWithLinesToRerun
) {
this.featureWithLines = featureWithLines;
this.featuresWithLinesToRerun = featuresWithLinesToRerun;
}

static FeatureWithLinesOrRerunPath parse(String arg) {
if (arg.startsWith("@")) {
Path rerunFileOrDirectory = Paths.get(arg.substring(1));
return new FeatureWithLinesOrRerunPath(null, RerunPath.parse(rerunFileOrDirectory));
} else {
return new FeatureWithLinesOrRerunPath(FeatureWithLines.parse(arg), null);
}
}

Optional<Collection<FeatureWithLines>> getFeaturesToRerun() {
return Optional.ofNullable(featuresWithLinesToRerun);
}

Optional<FeatureWithLines> getFeatureWithLines() {
return Optional.ofNullable(featureWithLines);
}

}

This file was deleted.

Loading