diff --git a/CHANGELOG.md b/CHANGELOG.md index 8314de1380..4a5a6ff801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,44 +8,46 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] (In Git) ### Added - + * [Junit Platform] Support cucumber.filter.name ([#2065](https://github.com/cucumber/cucumber-jvm/issues/2065) M.P. Korstanje) + ### Changed - - [OpenEJB] Compiled at source level 8. + * [OpenEJB] Compiled at source level 8. ### Deprecated - - [Weld] Deprecate `cucumber-weld` ([#1763](https://github.com/cucumber/cucumber-jvm/issues/1763) M.P. Korstanje) - - Consider using cucumber-deltaspike instead -- [Needle] Deprecate `cucumber-needle` ([#1763](https://github.com/cucumber/cucumber-jvm/issues/1763) M.P. Korstanje) - - Consider using cucumber-deltaspike instead + * [Weld] Deprecate `cucumber-weld` ([#1763](https://github.com/cucumber/cucumber-jvm/issues/1763) M.P. Korstanje) + * Consider using cucumber-deltaspike instead + * [Needle] Deprecate `cucumber-needle` ([#1763](https://github.com/cucumber/cucumber-jvm/issues/1763) M.P. Korstanje) + * Consider using cucumber-deltaspike instead + ### Removed ### Fixed - - [Core] Improve error message when an unknown plugin is used ([#2053](https://github.com/cucumber/cucumber-jvm/issues/2053) M.P. Korstanje) + * [Core] Improve error message when an unknown plugin is used ([#2053](https://github.com/cucumber/cucumber-jvm/issues/2053) M.P. Korstanje) ## [6.2.2] (2020-07-09) ### Fixed - - [JUnit] Make duplicate pickle names unique ([#2045](https://github.com/cucumber/cucumber-jvm/issues/2045) M.P. Korstanje) + * [JUnit] Make duplicate pickle names unique ([#2045](https://github.com/cucumber/cucumber-jvm/issues/2045) M.P. Korstanje) ## [6.2.1] (2020-07-07) ### Fixed - - [Core] Follow symlinks when loading feature files ([#2043](https://github.com/cucumber/cucumber-jvm/issues/2043) Andrey Mukamolov) + * [Core] Follow symlinks when loading feature files ([#2043](https://github.com/cucumber/cucumber-jvm/issues/2043) Andrey Mukamolov) ## [6.2.0] (2020-07-02) ### Changed - - [Core] Upgrade to [Gherkin v14](https://github.com/cucumber/cucumber/blob/master/gherkin/CHANGELOG.md#1400---2020-06-27) + * [Core] Upgrade to [Gherkin v14](https://github.com/cucumber/cucumber/blob/master/gherkin/CHANGELOG.md#1400---2020-06-27) ### Fixed - - [Core] Render attachments in `html` formatter + * [Core] Render attachments in `html` formatter ## [6.1.2] (2020-06-25) ### Fixed - - [Core] Update `cucumber-expressions` to v10.2.1 - - Retain position of optional groups ([cucumber/#1076](https://github.com/cucumber/cucumber/pull/1076), [cucumber/#511](https://github.com/cucumber/cucumber/pull/511), [cucumber/#952](https://github.com/cucumber/cucumber/pull/952) M.P. Korstanje) - - [Core] Generate valid parameter names in snippets ([#2029](https://github.com/cucumber/cucumber-jvm/issues/2029) M.P. Korstanje) + * [Core] Update `cucumber-expressions` to v10.2.1 + * Retain position of optional groups ([cucumber/#1076](https://github.com/cucumber/cucumber/pull/1076), [cucumber/#511](https://github.com/cucumber/cucumber/pull/511), [cucumber/#952](https://github.com/cucumber/cucumber/pull/952) M.P. Korstanje) + * [Core] Generate valid parameter names in snippets ([#2029](https://github.com/cucumber/cucumber-jvm/issues/2029) M.P. Korstanje) ## [6.1.1] (2020-06-12) diff --git a/core/README.md b/core/README.md index 63d1b858ca..62f3b9bb02 100644 --- a/core/README.md +++ b/core/README.md @@ -15,26 +15,49 @@ Note that options provided by `@CucumberOptions` take precedence over the properties file and CLI arguments take precedence over all. Note that the `cucumber-junit-platform-engine` is provided with properties -by the Junit Platform rather then Cucumber. See +by the Junit Platform rather than Cucumber. See [junit-platform-engine Configuration Options](../junit-platform-engine#configuration-options) for more information. Supported properties are: ``` -cucumber.ansi-colors.disabled= # true or false. default: false -cucumber.execution.dry-run= # true or false. default: false -cucumber.execution.limit= # number of scenarios to execute (CLI only). +cucumber.ansi-colors.disabled= # true or false. default: false + +cucumber.execution.dry-run= # true or false. default: false + +cucumber.execution.limit= # number of scenarios to execute (CLI only). + cucumber.execution.order= # lexical, reverse, random or random:[seed] (CLI only). default: lexical + cucumber.execution.strict= # true or false. default: false. + cucumber.execution.wip= # true or false. default: false. -cucumber.features= # command separated paths to feature files. example: path/to/example.feature, path/to/other.feature -cucumber.filter.name= # regex. example: .*Hello.* -cucumber.filter.tags= # tag expression. example: @smoke and not @slow -cucumber.glue= # comma separated package names. example: com.example.glue -cucumber.plugin= # comma separated plugin strings. example: pretty, json:path/to/report.json -cucumber.object-factory= # object factory class name. example: com.example.MyObjectFactory -cucumber.snippet-type= # underscore or camelcase. default: underscore + # Fails if there any passing scenarios + # CLI only. + +cucumber.features= # command separated paths to feature files. + # example: path/to/example.feature, path/to/other.feature + +cucumber.filter.name= # a regular expression + # only scenarios with matching names are executed. + # example: ^Hello (World|Cucumber)$ + +cucumber.filter.tags= # a cucumber tag expression. + # only scenarios with matching tags are executed. + # example: @Cucumber and not (@Gherkin or @Zucchini) + +cucumber.glue= # comma separated package names. + # example: com.example.glue + +cucumber.plugin= # comma separated plugin strings. + # example: pretty, json:path/to/report.json + +cucumber.object-factory= # object factory class name. + # example: com.example.MyObjectFactory + +cucumber.snippet-type= # underscore or camelcase. + # default: underscore ``` Each property also has an `UPPER_CASE` and `snake_case` variant. For example diff --git a/core/src/main/java/io/cucumber/core/options/Constants.java b/core/src/main/java/io/cucumber/core/options/Constants.java index 99077158cc..1836da970e 100644 --- a/core/src/main/java/io/cucumber/core/options/Constants.java +++ b/core/src/main/java/io/cucumber/core/options/Constants.java @@ -91,17 +91,20 @@ public final class Constants { /** * Property name used to set name filter: {@value} *

- * Filters features based on the provided regex pattern. + * Filters scenarios by name based on the provided regex pattern e.g: + * {@code ^Hello (World|Cucumber)$}. Scenarios that do not match the + * expression are not executed. *

- * By default no features are filtered + * By default all scenarios are executed */ public static final String FILTER_NAME_PROPERTY_NAME = "cucumber.filter.name"; + /** * Property name used to set tag filter: {@value} *

- * Filters scenarios based on the provided tag expression e.g: - * {@code @Integration and not @Ignored}. Scenarios that do not match the - * expression are not executed. + * Filters scenarios by tag based on the provided tag expression e.g: + * {@code @Cucumber and not (@Gherkin or @Zucchini)}. Scenarios that do not + * match the expression are not executed. *

* By default all scenarios are executed */ diff --git a/core/src/main/resources/io/cucumber/core/options/USAGE.txt b/core/src/main/resources/io/cucumber/core/options/USAGE.txt index 776164f982..a653cb6241 100644 --- a/core/src/main/resources/io/cucumber/core/options/USAGE.txt +++ b/core/src/main/resources/io/cucumber/core/options/USAGE.txt @@ -113,18 +113,41 @@ Supported properties are: ``` cucumber.ansi-colors.disabled= # true or false. default: false + cucumber.execution.dry-run= # true or false. default: false + cucumber.execution.limit= # number of scenarios to execute (CLI only). + cucumber.execution.order= # lexical, reverse, random or random:[seed] (CLI only). default: lexical + cucumber.execution.strict= # true or false. default: false. + cucumber.execution.wip= # true or false. default: false. -cucumber.features= # command separated paths to feature files. example: path/to/example.feature, path/to/other.feature -cucumber.filter.name= # regex. example: .*Hello.* -cucumber.filter.tags= # tag expression. example: @smoke and not @slow -cucumber.glue= # comma separated package names. example: com.example.glue -cucumber.plugin= # comma separated plugin strings. example: pretty, json:path/to/report.json -cucumber.object-factory= # object factory class name. example: com.example.MyObjectFactory -cucumber.snippet-type= # underscore or camelcase. default: underscore + # Fails if there any passing scenarios + # CLI only. + +cucumber.features= # command separated paths to feature files. + # example: path/to/example.feature, path/to/other.feature + +cucumber.filter.name= # a regular expression + # only scenarios with matching names are executed. + # example: ^Hello (World|Cucumber)$ + +cucumber.filter.tags= # a cucumber tag expression. + # only scenarios with matching tags are executed. + # example: @Cucumber and not (@Gherkin or @Zucchini) + +cucumber.glue= # comma separated package names. + # example: com.example.glue + +cucumber.plugin= # comma separated plugin strings. + # example: pretty, json:path/to/report.json + +cucumber.object-factory= # object factory class name. + # example: com.example.MyObjectFactory + +cucumber.snippet-type= # underscore or camelcase. + # default: underscore ``` Each property also has an `UPPER_CASE` and `snake_case` variant. For example diff --git a/junit-platform-engine/README.md b/junit-platform-engine/README.md index 524a495b14..a08b2912ed 100644 --- a/junit-platform-engine/README.md +++ b/junit-platform-engine/README.md @@ -170,49 +170,53 @@ Note: The `@` is not included. ## Configuration Options ## Cucumber receives its configuration from the JUnit platform. To see how these -can be supplied see the JUnit documentation [4.5. Configuration Parameters](https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params). +can be supplied; see the JUnit documentation [4.5. Configuration Parameters](https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params). For documentation see [Constants](src/main/java/io/cucumber/junit/platform/engine/Constants.java). ``` cucumber.ansi-colors.disabled= # true or false. default: false +cucumber.filter.name= # a regular expression + # only scenarios with matching names are executed. + # example: ^Hello (World|Cucumber)$ + cucumber.filter.tags= # a cucumber tag expression. - # only matching scenarios are executed. - # example: @integration and not @disabled - + # only scenarios with matching tags are executed. + # example: @Cucumber and not (@Gherkin or @Zucchini) + cucumber.glue= # comma separated package names. # example: com.example.glue - + cucumber.plugin= # comma separated plugin strings. # example: pretty, json:path/to/report.json - + cucumber.object-factory= # object factory class name. # example: com.example.MyObjectFactory - + cucumber.snippet-type= # underscore or camelcase. # default: underscore - + cucumber.execution.dry-run= # true or false. # default: false - + cucumber.execution.parallel.enabled= # true or false. # default: false - + cucumber.execution.parallel.config.strategy= # dynamic, fixed or custom. # default: dynamic - + cucumber.execution.parallel.config.fixed.parallelism= # positive integer. # example: 4 - + cucumber.execution.parallel.config.dynamic.factor= # positive double. # default: 1.0 - + cucumber.execution.parallel.config.custom.class= # class name. # example: com.example.MyCustomParallelStrategy cucumber.execution.exclusive-resources..read-write= # a comma seperated list of strings # example: resource-a, resource-b - + cucumber.execution.exclusive-resources..read= # a comma seperated list of strings # example: resource-a, resource-b diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index eb1af348c5..e69e56d49d 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -28,22 +28,36 @@ public final class Constants { * By default, dry-run is disabled */ public static final String EXECUTION_DRY_RUN_PROPERTY_NAME = io.cucumber.core.options.Constants.EXECUTION_DRY_RUN_PROPERTY_NAME; + /** * Tag replacement pattern for the exclusive resource templates: {@value} * * @see #EXECUTION_EXCLUSIVE_RESOURCES_READ_WRITE_TEMPLATE */ public static final String EXECUTION_EXCLUSIVE_RESOURCES_TAG_TEMPLATE_VARIABLE = ""; + + /** + * Property name used to set name filter: {@value} + *

+ * Filters features by name based on the provided regex pattern e.g: + * {@code ^Hello (World|Cucumber)$}. Scenarios that do not match the + * expression are not executed. + *

+ * By default all scenarios are executed + */ + public static final String FILTER_NAME_PROPERTY_NAME = io.cucumber.core.options.Constants.FILTER_NAME_PROPERTY_NAME; + /** * Property name used to set tag filter: {@value} *

- * Filters scenarios based on the provided tag expression e.g: - * {@code @Integration and not @Ignored}. Scenarios that did not match the - * expression will be rendered by JUnit as skipped. + * Filters scenarios by tag based on the provided tag expression e.g: + * {@code @Cucumber and not (@Gherkin or @Zucchini)}. Scenarios that do not + * match the expression are not executed. *

* By default all scenarios are executed */ public static final String FILTER_TAGS_PROPERTY_NAME = io.cucumber.core.options.Constants.FILTER_TAGS_PROPERTY_NAME; + /** * Property name to set the glue path: {@value} *

@@ -53,6 +67,7 @@ public final class Constants { * @see io.cucumber.core.feature.GluePath */ public static final String GLUE_PROPERTY_NAME = io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME; + /** * Property name to enable plugins: {@value} *

@@ -76,6 +91,7 @@ public final class Constants { * registration of 3rd party plugins. */ public static final String PLUGIN_PROPERTY_NAME = io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME; + /** * Property name to select custom object factory implementation: {@value} *

@@ -83,6 +99,7 @@ public final class Constants { * that object factory will be used. */ public static final String OBJECT_FACTORY_PROPERTY_NAME = io.cucumber.core.options.Constants.OBJECT_FACTORY_PROPERTY_NAME; + /** * Property name to control naming convention for generated snippets: * {@value} @@ -92,6 +109,7 @@ public final class Constants { * By defaults are generated using the under score naming convention. */ public static final String SNIPPET_TYPE_PROPERTY_NAME = io.cucumber.core.options.Constants.SNIPPET_TYPE_PROPERTY_NAME; + /** * Property name used to enable parallel test execution: {@value} *

@@ -102,6 +120,7 @@ public final class Constants { static final String EXECUTION_EXCLUSIVE_RESOURCES_PREFIX = "cucumber.execution.exclusive-resources."; static final String READ_WRITE_SUFFIX = ".read-write"; + /** * Property template used to describe a mapping of tags to exclusive * resources: {@value} @@ -131,6 +150,7 @@ public final class Constants { public static final String EXECUTION_EXCLUSIVE_RESOURCES_READ_WRITE_TEMPLATE = EXECUTION_EXCLUSIVE_RESOURCES_PREFIX + EXECUTION_EXCLUSIVE_RESOURCES_TAG_TEMPLATE_VARIABLE + READ_WRITE_SUFFIX; static final String READ_SUFFIX = ".read"; + /** * Property template used to describe a mapping of tags to exclusive * resources: {@value} @@ -143,6 +163,7 @@ public final class Constants { + EXECUTION_EXCLUSIVE_RESOURCES_TAG_TEMPLATE_VARIABLE + READ_SUFFIX; static final String PARALLEL_CONFIG_PREFIX = "cucumber.execution.parallel.config."; + /** * Property name used to determine the desired configuration strategy: * {@value} diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index 0986c61ce0..c93e13f9ce 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -14,11 +14,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME_PREFIX; import static io.cucumber.junit.platform.engine.Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.EXECUTION_DRY_RUN_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME; @@ -59,11 +62,12 @@ public boolean isWip() { return false; } - public Expression tagFilter() { - return TagExpressionParser - .parse(configurationParameters - .get(FILTER_TAGS_PROPERTY_NAME) - .orElse("")); + Optional tagFilter() { + return configurationParameters.get(FILTER_TAGS_PROPERTY_NAME, TagExpressionParser::parse); + } + + Optional nameFilter() { + return configurationParameters.get(FILTER_NAME_PROPERTY_NAME, Pattern::compile); } @Override diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java index ce11e861ee..16c9f2aaca 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/PickleDescriptor.java @@ -2,7 +2,6 @@ import io.cucumber.core.gherkin.Pickle; import io.cucumber.core.resource.ClasspathSupport; -import io.cucumber.tagexpressions.Expression; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; @@ -16,7 +15,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; @@ -67,13 +65,32 @@ public Type getType() { @Override public SkipResult shouldBeSkipped(CucumberEngineExecutionContext context) { - List tags = pickleEvent.getTags(); - Expression expression = context.getOptions().tagFilter(); - if (expression.evaluate(tags)) { - return SkipResult.doNotSkip(); - } - return SkipResult - .skip("'" + Constants.FILTER_TAGS_PROPERTY_NAME + "=" + expression + "' did not match this scenario"); + return Stream.of(shouldBeSkippedByTagFilter(context), shouldBeSkippedByNameFilter(context)) + .flatMap(skipResult -> skipResult.map(Stream::of).orElseGet(Stream::empty)) + .filter(SkipResult::isSkipped) + .findFirst() + .orElseGet(SkipResult::doNotSkip); + } + + private Optional shouldBeSkippedByTagFilter(CucumberEngineExecutionContext context) { + return context.getOptions().tagFilter().map(expression -> { + if (expression.evaluate(pickleEvent.getTags())) { + return SkipResult.doNotSkip(); + } + return SkipResult + .skip( + "'" + Constants.FILTER_TAGS_PROPERTY_NAME + "=" + expression + "' did not match this scenario"); + }); + } + + private Optional shouldBeSkippedByNameFilter(CucumberEngineExecutionContext context) { + return context.getOptions().nameFilter().map(pattern -> { + if (pattern.matcher(pickleEvent.getName()).matches()) { + return SkipResult.doNotSkip(); + } + return SkipResult + .skip("'" + Constants.FILTER_NAME_PROPERTY_NAME + "=" + pattern + "' did not match this scenario"); + }); } @Override diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java index a34fd56287..bee9d0bb2a 100644 --- a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberTestEngineTest.java @@ -9,9 +9,12 @@ import org.junit.platform.engine.UniqueId; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Event; import java.util.Optional; +import java.util.stream.Stream; +import static io.cucumber.junit.platform.engine.Constants.FILTER_NAME_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FILTER_TAGS_PROPERTY_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -52,20 +55,37 @@ void selectAndExecuteSingleScenario() { } @Test - void selectAndSkipDisabledScenario() { + void selectAndSkipDisabledScenarioByTags() { EngineExecutionResults result = EngineTestKit.engine("cucumber") .configurationParameter(FILTER_TAGS_PROPERTY_NAME, "@Integration and not @Disabled") - .selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/disabled.feature")) + .selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature")) + .execute(); + assertEquals(1, result.testEvents().count()); + assertEquals(1, result.testEvents().skipped().count()); + assertEquals( + Optional.of( + "'cucumber.filter.tags=( @Integration and not ( @Disabled ) )' did not match this scenario"), + result.testEvents() + .skipped() + .map(Event::getPayload) + .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)) + .findFirst()); + } + + @Test + void selectAndSkipDisabledScenarioByName() { + EngineExecutionResults result = EngineTestKit.engine("cucumber") + .configurationParameter(FILTER_NAME_PROPERTY_NAME, "^Nothing$") + .selectors(selectFile("src/test/resources/io/cucumber/junit/platform/engine/single.feature")) .execute(); assertEquals(1, result.testEvents().count()); assertEquals(1, result.testEvents().skipped().count()); assertEquals( - Optional.of("'cucumber.filter.tags=( @Integration and not ( @Disabled ) )' did not match this scenario"), + Optional.of("'cucumber.filter.name=^Nothing$' did not match this scenario"), result.testEvents() .skipped() - .map(event -> event.getPayload().get()) // replace with - // flatMap when - // above java 9 + .map(Event::getPayload) + .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)) .findFirst()); }