From c0aed755fe3b2bed815bf62dcd57164c16be2a4c Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 08:02:21 +0100 Subject: [PATCH] feat: added UUID generator selection through SPI for https://github.com/cucumber/cucumber-jvm/issues/2698 --- .revapi/api-changes.json | 24 ++ CHANGELOG.md | 3 + compatibility/pom.xml | 2 +- cucumber-archetype/pom.xml | 2 +- cucumber-bom/pom.xml | 40 +-- cucumber-cdi2/pom.xml | 2 +- cucumber-core/README.md | 20 ++ cucumber-core/pom.xml | 2 +- .../cucumber/core/cli/CommandlineOptions.java | 2 + .../eventbus/IncrementingUuidGenerator.java | 40 +++ .../io/cucumber/core/eventbus/Options.java | 7 + .../core/eventbus/RandomUuidGenerator.java | 14 + .../cucumber/core/eventbus/UuidGenerator.java | 13 + .../options/CommandlineOptionsParser.java | 5 + .../io/cucumber/core/options/Constants.java | 9 + .../CucumberOptionsAnnotationParser.java | 10 + .../options/CucumberPropertiesParser.java | 6 + .../cucumber/core/options/RuntimeOptions.java | 14 +- .../core/options/RuntimeOptionsBuilder.java | 11 + .../core/options/UuidGeneratorParser.java | 27 ++ .../java/io/cucumber/core/runner/Options.java | 3 + .../io/cucumber/core/runtime/Runtime.java | 10 +- .../runtime/UuidGeneratorServiceLoader.java | 130 +++++++++ .../io.cucumber.core.eventbus.UuidGenerator | 2 + .../io/cucumber/core/options/USAGE.txt | 7 + .../IncrementingUuidGeneratorTest.java | 62 ++++ .../eventbus/RandomUuidGeneratorTest.java | 24 ++ .../options/CommandlineOptionsParserTest.java | 13 +- .../core/options/CucumberOptions.java | 2 + .../CucumberOptionsAnnotationParserTest.java | 18 ++ .../core/options/CucumberPropertiesTest.java | 9 +- .../core/options/NoUuidGenerator.java | 20 ++ .../options/RuntimeOptionsBuilderTest.java | 22 ++ .../core/options/UuidGeneratorParserTest.java | 53 ++++ .../ObjectFactoryServiceLoaderTest.java | 171 ++++++++--- .../runtime/ObjectFactoryServiceLoaderTest.md | 18 ++ .../runtime/ServiceLoaderTestClassLoader.java | 96 +++++++ .../UuidGeneratorServiceLoaderTest.java | 268 ++++++++++++++++++ .../runtime/UuidGeneratorServiceLoaderTest.md | 17 ++ cucumber-deltaspike/pom.xml | 2 +- cucumber-gherkin-messages/pom.xml | 2 +- cucumber-gherkin/pom.xml | 2 +- cucumber-guice/pom.xml | 2 +- cucumber-jakarta-cdi/pom.xml | 2 +- cucumber-jakarta-openejb/pom.xml | 2 +- cucumber-java/pom.xml | 2 +- cucumber-java8/pom.xml | 2 +- cucumber-junit-platform-engine/pom.xml | 2 +- .../junit/platform/engine/Constants.java | 10 + .../engine/CucumberEngineOptions.java | 13 +- .../engine/CucumberEngineOptionsTest.java | 21 +- cucumber-junit/pom.xml | 2 +- .../io/cucumber/junit/CucumberOptions.java | 11 + .../junit/JUnitCucumberOptionsProvider.java | 5 + .../io/cucumber/junit/NoUuidGenerator.java | 20 ++ .../JUnitCucumberOptionsProviderTest.java | 25 +- cucumber-kotlin-java8/pom.xml | 2 +- cucumber-openejb/pom.xml | 2 +- cucumber-picocontainer/pom.xml | 2 +- cucumber-plugin/pom.xml | 2 +- cucumber-spring/pom.xml | 2 +- cucumber-testng/pom.xml | 2 +- .../io/cucumber/testng/CucumberOptions.java | 11 + .../io/cucumber/testng/NoUuidGenerator.java | 20 ++ .../testng/TestNGCucumberOptionsProvider.java | 5 + .../TestNGCucumberOptionsProviderTest.java | 25 +- datatable-matchers/pom.xml | 2 +- datatable/pom.xml | 2 +- docstring/pom.xml | 2 +- examples/calculator-java-cli/pom.xml | 2 +- examples/calculator-java-junit4/pom.xml | 2 +- examples/calculator-java-junit5/pom.xml | 2 +- examples/calculator-java-testng/pom.xml | 2 +- examples/calculator-java8-cli/pom.xml | 2 +- examples/pom.xml | 2 +- examples/spring-java-junit5/pom.xml | 2 +- examples/wicket-java-junit4/pom.xml | 2 +- .../wicket-java-junit4/wicket-main/pom.xml | 2 +- .../wicket-java-junit4/wicket-test/pom.xml | 2 +- pom.xml | 4 +- 80 files changed, 1320 insertions(+), 106 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java create mode 100644 cucumber-core/src/main/resources/META-INF/services/io.cucumber.core.eventbus.UuidGenerator create mode 100644 cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/options/NoUuidGenerator.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/options/UuidGeneratorParserTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.md create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/ServiceLoaderTestClassLoader.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md create mode 100644 cucumber-junit/src/main/java/io/cucumber/junit/NoUuidGenerator.java create mode 100644 cucumber-testng/src/main/java/io/cucumber/testng/NoUuidGenerator.java diff --git a/.revapi/api-changes.json b/.revapi/api-changes.json index 5bd2911d71..4355ce9dd1 100644 --- a/.revapi/api-changes.json +++ b/.revapi/api-changes.json @@ -181,6 +181,18 @@ "code": "java.method.finalMethodAddedToNonFinalClass", "new": "method java.lang.Long io.cucumber.core.internal.com.fasterxml.jackson.databind.deser.std.StdDeserializer::_parseLong(io.cucumber.core.internal.com.fasterxml.jackson.databind.DeserializationContext, java.lang.String) throws java.io.IOException", "justification": "Internal API" + }, + { + "ignore": true, + "code": "java.method.addedToInterface", + "new": "method java.lang.Class io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions::uuidGenerator()", + "justification": "Internal API" + }, + { + "ignore": true, + "code": "java.method.addedToInterface", + "new": "method java.lang.Class io.cucumber.core.runner.Options::getUuidGeneratorClass()", + "justification": "Internal API" } ] } @@ -331,6 +343,12 @@ "code": "java.method.defaultMethodAddedToInterface", "new": "method java.util.Set org.testng.ITestNGMethod::upstreamDependencies()", "justification": "Third party api change" + }, + { + "ignore": true, + "code": "java.class.externalClassExposedInAPI", + "new": "interface io.cucumber.core.eventbus.UuidGenerator", + "justification": "Part of cucumber API" } ] } @@ -383,6 +401,12 @@ "new": "method int org.junit.platform.engine.ConfigurationParameters::size()", "annotation": "@org.apiguardian.api.API(status = org.apiguardian.api.API.Status.DEPRECATED, since = \"1.9\")", "justification": "API consumed from JUnit 5" + }, + { + "ignore": true, + "code": "java.class.externalClassExposedInAPI", + "new": "interface io.cucumber.core.eventbus.UuidGenerator", + "justification": "Part of cucumber API" } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac08ee839..0aeb6dbd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- [Core] Improved event bus performance using UUID generator selectable through SPI ([#2703](https://github.com/cucumber/cucumber-jvm/pull/2703) Julien Kronegg) + ## [7.11.1] - 2023-01-27 ### Added - [Core] Warn when `cucumber.options` is used ([#2685](https://github.com/cucumber/cucumber-jvm/pull/2685) M.P. Korstanje) diff --git a/compatibility/pom.xml b/compatibility/pom.xml index 9d62f76276..48fd6e00f0 100644 --- a/compatibility/pom.xml +++ b/compatibility/pom.xml @@ -4,7 +4,7 @@ cucumber-jvm io.cucumber - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT 4.0.0 diff --git a/cucumber-archetype/pom.xml b/cucumber-archetype/pom.xml index ea65416237..28f7cea336 100644 --- a/cucumber-archetype/pom.xml +++ b/cucumber-archetype/pom.xml @@ -6,7 +6,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-archetype diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index 78654cf628..f19b258c37 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -3,7 +3,7 @@ cucumber-jvm io.cucumber - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT 4.0.0 pom @@ -63,97 +63,97 @@ io.cucumber cucumber-cdi2 - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-core - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber datatable - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber datatable-matchers - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-deltaspike - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber docstring - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-gherkin - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-gherkin-messages - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-guice - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-jakarta-cdi - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-java - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-java8 - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-junit - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-junit-platform-engine - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-openejb - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-picocontainer - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-plugin - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-spring - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT io.cucumber cucumber-testng - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT diff --git a/cucumber-cdi2/pom.xml b/cucumber-cdi2/pom.xml index 24debd11b4..257cb96af0 100644 --- a/cucumber-cdi2/pom.xml +++ b/cucumber-cdi2/pom.xml @@ -14,7 +14,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-cdi2 diff --git a/cucumber-core/README.md b/cucumber-core/README.md index cdd5a7824f..6e07801ada 100644 --- a/cucumber-core/README.md +++ b/cucumber-core/README.md @@ -52,6 +52,9 @@ cucumber.plugin= # comma separated plugin strings. cucumber.object-factory= # object factory class name. # example: com.example.MyObjectFactory +cucumber.uuid-generator= # UUID generator class name. + # example: com.example.MyUuidGenerator + cucumber.publish.enabled # true or false. default: false # enable publishing of test results @@ -79,6 +82,23 @@ They are respectively responsible for discovering glue classes, registering step definitions, and creating instances of said glue classes. Backend and object factory implementations are discovered via SPI. +## Event bus ## + +Cucumber emits events on an event bus in many cases: +- during the feature file parsing +- when the test scenarios are executed + +An event has a UUID. The UUID generator can be configured using the `cucumber.uuid-generator` property: + +| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example | +|-----------------------------------------------------|-----------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| io.cucumber.core.eventbus.RandomUuidGenerator | Thread-safe, collision-free, multi-jvm | ~1 | Reports may be generated on different JVMs at the same time. A typical example would be one suite that tests against Firefox and another against Safari. The exact browser is configured through a property. These are then executed concurrently on different Gitlab runners. | +| io.cucumber.core.eventbus.IncrementingUuidGenerator | Thread-safe, collision-free, single-jvm | ~130 | Reports are generated on a single JVM | + +The performance gain on real project depend on the feature size. + +When not specified, the `RandomUuidGenerator` is used. + ## Plugin ## By implementing the Plugin interface classes can listen to execution events diff --git a/cucumber-core/pom.xml b/cucumber-core/pom.xml index bc7aaac6f8..d246466ee0 100644 --- a/cucumber-core/pom.xml +++ b/cucumber-core/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-core diff --git a/cucumber-core/src/main/java/io/cucumber/core/cli/CommandlineOptions.java b/cucumber-core/src/main/java/io/cucumber/core/cli/CommandlineOptions.java index 229697b122..3d9ea17d7d 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/cli/CommandlineOptions.java +++ b/cucumber-core/src/main/java/io/cucumber/core/cli/CommandlineOptions.java @@ -66,6 +66,8 @@ public final class CommandlineOptions { public static final String OBJECT_FACTORY = "--object-factory"; + public static final String UUID_GENERATOR = "--uuid-generator"; + private CommandlineOptions() { } } diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java new file mode 100644 index 0000000000..35fa782e79 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java @@ -0,0 +1,40 @@ +package io.cucumber.core.eventbus; + +import io.cucumber.core.exception.CucumberException; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Thread-safe and collision-free UUID generator for single JVM. This is a + * sequence generator and each instance has its own counter. This generator is + * about 100 times faster than #RandomUuidGenerator. If you use Cucumber in + * multi-JVM setup, you should use #RandomUuidGenerator instead. Note that the + * UUID version and variant is not guaranteed to be stable. + */ +public class IncrementingUuidGenerator implements UuidGenerator { + private static final AtomicLong sessionCounter = new AtomicLong(Long.MIN_VALUE); + + private final long sessionId; + private final AtomicLong counter = new AtomicLong(Long.MIN_VALUE); + + public IncrementingUuidGenerator() { + sessionId = sessionCounter.incrementAndGet(); + } + + /** + * Generate a new UUID. Will throw an exception when out of capacity. + * + * @return a non-null UUID + * @throws CucumberException when out of capacity + */ + @Override + public UUID get() { + long leastSigBits = counter.incrementAndGet(); + if (leastSigBits == Long.MAX_VALUE) { + throw new CucumberException( + "Out of IncrementingUuidGenerator capacity. Please use the RandomUuidGenerator instead."); + } + return new UUID(sessionId, leastSigBits); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java new file mode 100644 index 0000000000..b14ef7a05e --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java @@ -0,0 +1,7 @@ +package io.cucumber.core.eventbus; + +public interface Options { + + Class getUuidGeneratorClass(); + +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java new file mode 100644 index 0000000000..6ecdf84ac2 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java @@ -0,0 +1,14 @@ +package io.cucumber.core.eventbus; + +import java.util.UUID; + +/** + * UUID generator based on random numbers. The generator is thread-safe and + * supports multi-jvm usage of Cucumber. + */ +public class RandomUuidGenerator implements UuidGenerator { + @Override + public UUID get() { + return UUID.randomUUID(); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java new file mode 100644 index 0000000000..6557576ee4 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java @@ -0,0 +1,13 @@ +package io.cucumber.core.eventbus; + +import org.apiguardian.api.API; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * SPI (Service Provider Interface) to generate UUIDs. + */ +@API(status = API.Status.STABLE) +public interface UuidGenerator extends Supplier { +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java index ffa3502a1f..88db18e236 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java @@ -53,12 +53,14 @@ import static io.cucumber.core.cli.CommandlineOptions.TAGS; import static io.cucumber.core.cli.CommandlineOptions.TAGS_SHORT; import static io.cucumber.core.cli.CommandlineOptions.THREADS; +import static io.cucumber.core.cli.CommandlineOptions.UUID_GENERATOR; import static io.cucumber.core.cli.CommandlineOptions.VERSION; import static io.cucumber.core.cli.CommandlineOptions.VERSION_SHORT; 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; import static java.util.stream.Collectors.joining; @@ -167,6 +169,9 @@ private RuntimeOptionsBuilder parse(List args) { } else if (arg.equals(OBJECT_FACTORY)) { String objectFactoryClassName = removeArgFor(arg, args); parsedOptions.setObjectFactoryClass(parseObjectFactory(objectFactoryClassName)); + } else if (arg.equals(UUID_GENERATOR)) { + String uuidGeneratorClassName = removeArgFor(arg, args); + parsedOptions.setUuidGeneratorClass(parseUuidGenerator(uuidGeneratorClassName)); } else if (arg.startsWith("-")) { out.println("Unknown option: " + arg); printUsage(); diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java b/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java index dc88f3c281..8681e83d91 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/Constants.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.runtime.ObjectFactoryServiceLoader; +import io.cucumber.core.runtime.UuidGeneratorServiceLoader; public final class Constants { @@ -118,6 +119,14 @@ public final class Constants { */ public static final String OBJECT_FACTORY_PROPERTY_NAME = "cucumber.object-factory"; + /** + * Property name used to select a specific UUID generator implementation: + * {@value} + * + * @see UuidGeneratorServiceLoader + */ + public static final String UUID_GENERATOR_PROPERTY_NAME = "cucumber.uuid-generator"; + /** * Property name formerly used to pass command line options to Cucumber: * {@value} This property is no longer read by Cucumber. Please use any of diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java index 684af34c2d..576ac6ff07 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.feature.GluePath; @@ -45,6 +46,7 @@ public RuntimeOptionsBuilder parse(Class clazz) { addGlue(options, args); addFeatures(options, args); addObjectFactory(options, args); + addUuidGenerator(options, args); } } @@ -149,6 +151,12 @@ private void addObjectFactory(CucumberOptions options, RuntimeOptionsBuilder arg } } + private void addUuidGenerator(CucumberOptions options, RuntimeOptionsBuilder args) { + if (options.uuidGenerator() != null) { + args.setUuidGeneratorClass(options.uuidGenerator()); + } + } + private void addDefaultFeaturePathIfNoFeaturePathIsSpecified(RuntimeOptionsBuilder args, Class clazz) { if (!featuresSpecified) { String packageName = packagePath(clazz); @@ -208,6 +216,8 @@ public interface CucumberOptions { Class objectFactory(); + Class uuidGenerator(); + } } diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java index db2aab247f..d4bb7d2aaf 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java @@ -32,6 +32,7 @@ import static io.cucumber.core.options.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME; import static io.cucumber.core.options.Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME; 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; @@ -102,6 +103,11 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) { ObjectFactoryParser::parseObjectFactory, builder::setObjectFactoryClass); + parse(properties, + UUID_GENERATOR_PROPERTY_NAME, + UuidGeneratorParser::parseUuidGenerator, + builder::setUuidGeneratorClass); + parse(properties, OPTIONS_PROPERTY_NAME, identity(), diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java index 0c0490db5a..4db93daac4 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.order.PickleOrder; import io.cucumber.core.order.StandardPickleOrders; @@ -33,7 +34,8 @@ public final class RuntimeOptions implements io.cucumber.core.runner.Options, io.cucumber.core.plugin.Options, io.cucumber.core.filter.Options, - io.cucumber.core.backend.Options { + io.cucumber.core.backend.Options, + io.cucumber.core.eventbus.Options { private final List glue = new ArrayList<>(); private final List tagExpressions = new ArrayList<>(); @@ -48,6 +50,7 @@ public final class RuntimeOptions implements private PickleOrder pickleOrder = StandardPickleOrders.lexicalUriOrder(); private int count = 0; private Class objectFactoryClass; + private Class uuidGeneratorClass; private String publishToken; private boolean publish; private boolean publishQuiet; @@ -158,6 +161,15 @@ void setObjectFactoryClass(Class objectFactoryClass) { this.objectFactoryClass = objectFactoryClass; } + @Override + public Class getUuidGeneratorClass() { + return uuidGeneratorClass; + } + + void setUuidGeneratorClass(Class uuidGeneratorClass) { + this.uuidGeneratorClass = uuidGeneratorClass; + } + void setSnippetType(SnippetType snippetType) { this.snippetType = snippetType; } diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java index db7bddafa5..58b6792bc5 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.order.PickleOrder; @@ -31,6 +32,7 @@ public final class RuntimeOptionsBuilder { private PickleOrder parsedPickleOrder = null; private Integer parsedCount = null; private Class parsedObjectFactoryClass = null; + private Class parsedUuidGeneratorClass = null; private Boolean addDefaultSummaryPrinter = null; private boolean addDefaultGlueIfAbsent; private boolean addDefaultFeaturePathIfAbsent; @@ -132,6 +134,10 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) { runtimeOptions.setObjectFactoryClass(parsedObjectFactoryClass); } + if (parsedUuidGeneratorClass != null) { + runtimeOptions.setUuidGeneratorClass(parsedUuidGeneratorClass); + } + if (addDefaultSummaryPrinter != null && addDefaultSummaryPrinter) { runtimeOptions.addDefaultSummaryPrinter(); } @@ -239,6 +245,11 @@ public RuntimeOptionsBuilder setObjectFactoryClass(Class uuidGeneratorClass) { + this.parsedUuidGeneratorClass = uuidGeneratorClass; + return this; + } + public RuntimeOptionsBuilder setPublishToken(String token) { this.parsedPublishToken = token; return this; diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java new file mode 100644 index 0000000000..ecc633f211 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java @@ -0,0 +1,27 @@ +package io.cucumber.core.options; + +import io.cucumber.core.eventbus.UuidGenerator; + +public final class UuidGeneratorParser { + + private UuidGeneratorParser() { + + } + + @SuppressWarnings("unchecked") + public static Class parseUuidGenerator(String cucumberUuidGenerator) { + Class uuidGeneratorClass; + try { + uuidGeneratorClass = Class.forName(cucumberUuidGenerator); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + String.format("Could not load UUID generator class for '%s'", cucumberUuidGenerator), e); + } + if (!UuidGenerator.class.isAssignableFrom(uuidGeneratorClass)) { + throw new IllegalArgumentException(String.format("UUID generator class '%s' was not a subclass of '%s'", + uuidGeneratorClass, UuidGenerator.class)); + } + return (Class) uuidGeneratorClass; + } + +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java b/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java index a1094523c5..0fe0ffb807 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java @@ -1,6 +1,7 @@ package io.cucumber.core.runner; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.snippets.SnippetType; import java.net.URI; @@ -16,4 +17,6 @@ public interface Options { Class getObjectFactoryClass(); + Class getUuidGeneratorClass(); + } diff --git a/cucumber-core/src/main/java/io/cucumber/core/runtime/Runtime.java b/cucumber-core/src/main/java/io/cucumber/core/runtime/Runtime.java index 1a7b92c23f..8fba565bcb 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/Runtime.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/Runtime.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -138,7 +137,7 @@ public byte exitStatus() { public static class Builder { - private EventBus eventBus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); + private EventBus eventBus; private Supplier classLoader = ClassLoaders::getDefaultClassLoader; private RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); private BackendSupplier backendSupplier; @@ -197,6 +196,13 @@ public Runtime build() { final ExitStatus exitStatus = new ExitStatus(runtimeOptions); plugins.addPlugin(exitStatus); + if (this.eventBus == null) { + final UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader( + classLoader, + runtimeOptions); + this.eventBus = new TimeServiceEventBus(Clock.systemUTC(), + uuidGeneratorServiceLoader.loadUuidGenerator()); + } final EventBus eventBus = synchronize(this.eventBus); if (runtimeOptions.isMultiThreaded()) { diff --git a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java new file mode 100644 index 0000000000..d8fbb2baf2 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -0,0 +1,130 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.eventbus.Options; +import io.cucumber.core.eventbus.RandomUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; +import io.cucumber.core.exception.CucumberException; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * Loads an instance of {@link UuidGenerator} using the {@link ServiceLoader} + * mechanism. + *

+ * Will load an instance of the class provided by + * {@link Options#getUuidGeneratorClass()}. If + * {@link Options#getUuidGeneratorClass()} does not provide a class, if there is + * exactly one {@code UuidGenerator} instance available that instance will be + * used. + *

+ * Otherwise {@link RandomUuidGenerator} with no dependency injection + */ +public final class UuidGeneratorServiceLoader { + + private final Supplier classLoaderSupplier; + private final Options options; + + public UuidGeneratorServiceLoader(Supplier classLoaderSupplier, Options options) { + this.classLoaderSupplier = requireNonNull(classLoaderSupplier); + this.options = requireNonNull(options); + } + + UuidGenerator loadUuidGenerator() { + Class objectFactoryClass = options.getUuidGeneratorClass(); + ClassLoader classLoader = classLoaderSupplier.get(); + ServiceLoader loader = ServiceLoader.load(UuidGenerator.class, classLoader); + if (objectFactoryClass == null) { + return loadSingleUuidGeneratorOrDefault(loader); + } + + return loadSelectedUuidGenerator(loader, objectFactoryClass); + } + + private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader loader) { + Iterator uuidGenerators = loader.iterator(); + + // categorize the UUID generators (random, incrementing or external) + UuidGenerator randomGenerator = null; + UuidGenerator incrementingGenerator = null; + UuidGenerator externalGenerator = null; + while (uuidGenerators.hasNext()) { + UuidGenerator uuidGenerator = uuidGenerators.next(); + if (uuidGenerator instanceof RandomUuidGenerator) { + randomGenerator = uuidGenerator; + } else if (uuidGenerator instanceof IncrementingUuidGenerator) { + incrementingGenerator = uuidGenerator; + } else { + if (externalGenerator != null) { + // we have multiple external generators, which is an error + throw new CucumberException(getMultipleUuidGeneratorLogMessage( + Arrays.asList(externalGenerator, uuidGenerator))); + } + externalGenerator = uuidGenerator; + } + } + + // decide which generator to use + if (externalGenerator != null) { + // we have a single external generator + return externalGenerator; + } else if (randomGenerator != null) { + // we don't have any external generators, use random if available + return randomGenerator; + } else if (incrementingGenerator != null) { + // we don't have any external generators and no random, use + // incrementing if available + return incrementingGenerator; + } else { + // we don't have any generators at all, throw an error + throw new CucumberException("" + + "Could not find any UUID generator.\n" + + "\n" + + "Cucumber uses SPI to discover UUID generator implementations.\n" + + "This typically happens when using shaded jars. Make sure\n" + + "to merge all SPI definitions in META-INF/services correctly"); + } + } + + private static UuidGenerator loadSelectedUuidGenerator( + ServiceLoader loader, + Class uuidGeneratorClass + ) { + for (UuidGenerator uuidGenerator : loader) { + if (uuidGeneratorClass.equals(uuidGenerator.getClass())) { + return uuidGenerator; + } + } + + throw new CucumberException("" + + "Could not find UUID generator " + uuidGeneratorClass.getName() + ".\n" + + "\n" + + "Cucumber uses SPI to discover UUID generator implementations.\n" + + "Has the class been registered with SPI and is it available on\n" + + "the classpath?"); + } + + private static String getMultipleUuidGeneratorLogMessage(List uuidGenerators) { + String factoryNames = Stream.of(uuidGenerators) + .map(Object::getClass) + .map(Class::getName) + .collect(Collectors.joining(", ")); + + return "More than one Cucumber UuidGenerator was found on the classpath\n" + + "\n" + + "Found: " + factoryNames + "\n" + + "\n" + + "You can either remove the unnecessary SPI dependencies from your classpath\n" + + "or use the `cucumber.uuid-generator` property\n" + + "or `@CucumberOptions(uuidGenerator=...)` to select one UUID generator.\n"; + } + +} diff --git a/cucumber-core/src/main/resources/META-INF/services/io.cucumber.core.eventbus.UuidGenerator b/cucumber-core/src/main/resources/META-INF/services/io.cucumber.core.eventbus.UuidGenerator new file mode 100644 index 0000000000..c7c37e3f7b --- /dev/null +++ b/cucumber-core/src/main/resources/META-INF/services/io.cucumber.core.eventbus.UuidGenerator @@ -0,0 +1,2 @@ +io.cucumber.core.eventbus.RandomUuidGenerator +io.cucumber.core.eventbus.IncrementingUuidGenerator diff --git a/cucumber-core/src/main/resources/io/cucumber/core/options/USAGE.txt b/cucumber-core/src/main/resources/io/cucumber/core/options/USAGE.txt index 58a57584b1..17a05569ba 100644 --- a/cucumber-core/src/main/resources/io/cucumber/core/options/USAGE.txt +++ b/cucumber-core/src/main/resources/io/cucumber/core/options/USAGE.txt @@ -73,6 +73,13 @@ Options: be specified in: META-INF/services/io.cucumber.core.backend.ObjectFactory + --uuid-generator CLASSNAME Uses the class specified by CLASSNAME + as UUID generator. Be aware that the + class is loaded through a service + loader and therefore also needs to + be specified in: + META-INF/services/io.cucumber.core.eventbus.UuidGenerator + Feature path examples: When no feature path is provided cucumber will scan the classpath root diff --git a/cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java new file mode 100644 index 0000000000..a3fab341f1 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java @@ -0,0 +1,62 @@ +package io.cucumber.core.eventbus; + +import io.cucumber.core.exception.CucumberException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class IncrementingUuidGeneratorTest { + @Test + void generates_different_non_null_uuids() { + // Given + UuidGenerator generator = new IncrementingUuidGenerator(); + UUID uuid1 = generator.get(); + + // When + UUID uuid2 = generator.get(); + + // Then + assertNotNull(uuid1); + assertNotNull(uuid2); + assertNotEquals(uuid1, uuid2); + } + + @Test + void raises_exception_when_out_of_range() throws NoSuchFieldException, IllegalAccessException { + // Given + UuidGenerator generator = new IncrementingUuidGenerator(); + Field counterField = IncrementingUuidGenerator.class.getDeclaredField("counter"); + counterField.setAccessible(true); + AtomicLong counter = (AtomicLong) counterField.get(generator); + counter.set(Long.MAX_VALUE - 1); + + // When + CucumberException cucumberException = assertThrows(CucumberException.class, generator::get); + + // Then + assertThat(cucumberException.getMessage(), + Matchers.containsString("Out of IncrementingUuidGenerator capacity")); + } + + @Test + void same_thread_generates_different_UuidGenerators() { + // Given + UuidGenerator generator1 = new IncrementingUuidGenerator(); + UuidGenerator generator2 = new IncrementingUuidGenerator(); + + // When + UUID uuid1 = generator1.get(); + UUID uuid2 = generator2.get(); + + // Then + assertNotNull(uuid1); + assertNotNull(uuid2); + assertNotEquals(uuid1, uuid2); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java new file mode 100644 index 0000000000..c578c04d1e --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java @@ -0,0 +1,24 @@ +package io.cucumber.core.eventbus; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class RandomUuidGeneratorTest { + @Test + void generates_different_non_null_uuids() { + // Given + UuidGenerator generator = new RandomUuidGenerator(); + UUID uuid1 = generator.get(); + + // When + UUID uuid2 = generator.get(); + + // Then + assertNotNull(uuid1); + assertNotNull(uuid2); + assertNotEquals(uuid1, uuid2); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/CommandlineOptionsParserTest.java b/cucumber-core/src/test/java/io/cucumber/core/options/CommandlineOptionsParserTest.java index 74824c90aa..5d31229ae3 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/options/CommandlineOptionsParserTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/options/CommandlineOptionsParserTest.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.gherkin.Pickle; @@ -76,6 +77,16 @@ void testParseWithObjectFactoryArgument() { assertThat(options.getObjectFactoryClass(), Is.is(equalTo(TestObjectFactory.class))); } + @Test + void testParseWithUuidGeneratorArgument() { + RuntimeOptionsBuilder optionsBuilder = parser.parse("--uuid-generator", + IncrementingUuidGenerator.class.getName()); + assertNotNull(optionsBuilder); + RuntimeOptions options = optionsBuilder.build(); + assertNotNull(options); + assertThat(options.getUuidGeneratorClass(), Is.is(equalTo(IncrementingUuidGenerator.class))); + } + @Test void has_version_from_properties_file() { parser.parse("--version"); @@ -84,7 +95,7 @@ void has_version_from_properties_file() { } private String output() { - return new String(out.toByteArray(), StandardCharsets.UTF_8); + return out.toString(StandardCharsets.UTF_8); } @Test diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptions.java b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptions.java index 267ac37c55..cf4457c7f2 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptions.java +++ b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptions.java @@ -33,6 +33,8 @@ Class objectFactory() default NoObjectFactory.class; + Class uuidGenerator() default NoUuidGenerator.class; + String[] junit() default {}; } diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java index 43fb536e0b..e078ce47e2 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java @@ -1,6 +1,8 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.plugin.HtmlFormatter; import io.cucumber.core.plugin.NoPublishFormatter; @@ -252,6 +254,13 @@ void cannot_create_with_glue_and_extra_glue() { is(equalTo("glue and extraGlue cannot be specified at the same time"))); } + @Test + void uuid_generator() { + RuntimeOptions runtimeOptions = parser().parse(ClassWithUuidGenerator.class).build(); + + assertThat(runtimeOptions.getUuidGeneratorClass(), is(IncrementingUuidGenerator.class)); + } + @CucumberOptions(snippets = SnippetType.CAMELCASE) private static class Snippets { // empty @@ -363,6 +372,11 @@ private static class ClassWithGlueAndExtraGlue { // empty } + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) + private static class ClassWithUuidGenerator extends ClassWithGlue { + // empty + } + private static class CoreCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { private final CucumberOptions annotation; @@ -426,6 +440,10 @@ public Class objectFactory() { return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); } + @Override + public Class uuidGenerator() { + return (annotation.uuidGenerator() == NoUuidGenerator.class) ? null : annotation.uuidGenerator(); + } } private static class CoreCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java index a4f6087b76..85c5ee9385 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/options/CucumberPropertiesTest.java @@ -16,7 +16,14 @@ class CucumberPropertiesTest { @Test void looks_up_value_from_environment() { - assertThat(CucumberProperties.fromEnvironment().get("PATH"), is(notNullValue())); + Map properties = CucumberProperties.fromEnvironment(); + String path = properties.get("PATH"); + if (path == null) { + // on some Windows flavors, the PATH environment variable is named + // "Path" + path = properties.get("Path"); + } + assertThat(path, is(notNullValue())); } @Test diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/NoUuidGenerator.java b/cucumber-core/src/test/java/io/cucumber/core/options/NoUuidGenerator.java new file mode 100644 index 0000000000..a8ddb653b0 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/options/NoUuidGenerator.java @@ -0,0 +1,20 @@ +package io.cucumber.core.options; + +import io.cucumber.core.eventbus.UuidGenerator; + +import java.util.UUID; + +/** + * This UUID generator does nothing. It is solely needed for marking purposes. + */ +final class NoUuidGenerator implements UuidGenerator { + + private NoUuidGenerator() { + // No need for instantiation + } + + @Override + public UUID get() { + return null; + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java b/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java new file mode 100644 index 0000000000..02bb028bbf --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java @@ -0,0 +1,22 @@ +package io.cucumber.core.options; + +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RuntimeOptionsBuilderTest { + + @Test + void build() { + // Given + RuntimeOptionsBuilder builder = new RuntimeOptionsBuilder() + .setUuidGeneratorClass(IncrementingUuidGenerator.class); + + // When + RuntimeOptions runtimeOptions = builder.build(); + + // Then + assertEquals(IncrementingUuidGenerator.class, runtimeOptions.getUuidGeneratorClass()); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/options/UuidGeneratorParserTest.java b/cucumber-core/src/test/java/io/cucumber/core/options/UuidGeneratorParserTest.java new file mode 100644 index 0000000000..012a2ec87c --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/options/UuidGeneratorParserTest.java @@ -0,0 +1,53 @@ +package io.cucumber.core.options; + +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.eventbus.RandomUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class UuidGeneratorParserTest { + + @Test + void parseUuidGenerator_IncrementingUuidGenerator() { + // When + Class uuidGeneratorClass = UuidGeneratorParser + .parseUuidGenerator(IncrementingUuidGenerator.class.getName()); + + // Then + assertEquals(IncrementingUuidGenerator.class, uuidGeneratorClass); + } + + @Test + void parseUuidGenerator_RandomUuidGenerator() { + // When + Class uuidGeneratorClass = UuidGeneratorParser + .parseUuidGenerator(RandomUuidGenerator.class.getName()); + + // Then + assertEquals(RandomUuidGenerator.class, uuidGeneratorClass); + } + + @Test + void parseUuidGenerator_not_a_generator() { + // When + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> UuidGeneratorParser.parseUuidGenerator(String.class.getName())); + + // Then + assertThat(exception.getMessage(), Matchers.containsString("not a subclass")); + } + + @Test + void parseUuidGenerator_not_a_class() { + // When + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> UuidGeneratorParser.parseUuidGenerator("java.lang.NonExistingClassName")); + + // Then + assertThat(exception.getMessage(), Matchers.containsString("Could not load UUID generator class")); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.java index f6f5920a80..14580387cd 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.java @@ -3,25 +3,50 @@ import io.cucumber.core.backend.DefaultObjectFactory; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.backend.Options; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.eventbus.RandomUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; import java.util.function.Supplier; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; +/** + * @see test-cases description + */ class ObjectFactoryServiceLoaderTest { + /** + * Test case #1 + */ + @Test + void shouldThrowIfDefaultObjectFactoryServiceCouldNotBeLoaded() { + Options options = () -> null; + Supplier classLoader = () -> new ServiceLoaderTestClassLoader(ObjectFactory.class); + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + classLoader, + options); + + CucumberException exception = assertThrows(CucumberException.class, loader::loadObjectFactory); + assertThat(exception.getMessage(), is("" + + "Could not find any object factory.\n" + + "\n" + + "Cucumber uses SPI to discover object factory implementations.\n" + + "This typically happens when using shaded jars. Make sure\n" + + "to merge all SPI definitions in META-INF/services correctly")); + } + + /** + * Test case #2 + */ @Test void shouldLoadDefaultObjectFactoryService() { Options options = () -> null; @@ -31,6 +56,9 @@ void shouldLoadDefaultObjectFactoryService() { assertThat(loader.loadObjectFactory(), instanceOf(DefaultObjectFactory.class)); } + /** + * Test case #3 + */ @Test void shouldLoadSelectedObjectFactoryService() { Options options = () -> DefaultObjectFactory.class; @@ -40,37 +68,99 @@ void shouldLoadSelectedObjectFactoryService() { assertThat(loader.loadObjectFactory(), instanceOf(DefaultObjectFactory.class)); } + /** + * Test-case #4 + */ @Test - void shouldThrowIfDefaultObjectFactoryServiceCouldNotBeLoaded() { + void test_case_4() { + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + OtherFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); + } + + /** + * Test-case #4 bis (reverse order) + */ + @Test + void test_case_4_with_services_in_reverse_order() { + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + OtherFactory.class, + DefaultObjectFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); + } + + /** + * Test-case #5 + */ + @Test + void test_case_5() { + io.cucumber.core.backend.Options options = () -> DefaultObjectFactory.class; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + OtherFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(DefaultObjectFactory.class)); + } + + /** + * Test case #6 + */ + @Test + void test_case_6() { + // Given Options options = () -> null; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.backend.ObjectFactory"); ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + OtherFactory.class, + YetAnotherFactory.class), options); + // When CucumberException exception = assertThrows(CucumberException.class, loader::loadObjectFactory); - assertThat(exception.getMessage(), is("" + - "Could not find any object factory.\n" + - "\n" + - "Cucumber uses SPI to discover object factory implementations.\n" + - "This typically happens when using shaded jars. Make sure\n" + - "to merge all SPI definitions in META-INF/services correctly")); + + // Then + assertThat(exception.getMessage(), + containsString("More than one Cucumber ObjectFactory was found on the classpath")); + } + + /** + * Test-case #7 + */ + @Test + void test_case_7() { + io.cucumber.core.backend.Options options = () -> OtherFactory.class; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + OtherFactory.class, + YetAnotherFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); } + /** + * Test case #8 + */ @Test void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { - Options options = () -> NoSuchObjectFactory.class; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.backend.ObjectFactory"); + Options options = () -> OtherFactory.class; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class), options); CucumberException exception = assertThrows(CucumberException.class, loader::loadObjectFactory); assertThat(exception.getMessage(), is("" + - "Could not find object factory io.cucumber.core.runtime.ObjectFactoryServiceLoaderTest$NoSuchObjectFactory.\n" + "Could not find object factory io.cucumber.core.runtime.ObjectFactoryServiceLoaderTest$OtherFactory.\n" + "\n" + "Cucumber uses SPI to discover object factory implementations.\n" + @@ -78,7 +168,20 @@ void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { "the classpath?")); } - static class NoSuchObjectFactory implements ObjectFactory { + /** + * Test-case #9 + */ + @Test + void test_case_9() { + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + OtherFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); + } + + public static class FakeObjectFactory implements ObjectFactory { @Override public boolean addClass(Class glueClass) { @@ -102,25 +205,9 @@ public void stop() { } - private static class FilteredClassLoader extends URLClassLoader { - - private final Collection filteredResources; - - public FilteredClassLoader(String... filteredResources) { - super(new URL[0], FilteredClassLoader.class.getClassLoader()); - this.filteredResources = Arrays.asList(filteredResources); - } - - @Override - public Enumeration getResources(String name) throws IOException { - for (String filteredResource : filteredResources) { - if (name.equals(filteredResource)) { - return Collections.emptyEnumeration(); - } - } - return super.getResources(name); - } - + public static class OtherFactory extends FakeObjectFactory { } + public static class YetAnotherFactory extends FakeObjectFactory { + } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.md b/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.md new file mode 100644 index 0000000000..e5155b8b28 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.md @@ -0,0 +1,18 @@ +# Testcases for `ObjectFactoryServiceLoader` + +| # | object-factory property | Available services | Result | +|---|-------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------| +| 1 | undefined | none | exception, no generators available | +| 2 | undefined | DefaultObjectFactory | DefaultObjectFactory used | +| 3 | DefaultObjectFactory | DefaultObjectFactory | DefaultObjectFactory used | +| 4 | undefined | DefaultObjectFactory, OtherFactory | OtherFactory used | +| 5 | DefaultObjectFactory | DefaultObjectFactory, OtherFactory | DefaultObjectFactory used | +| 6 | undefined | DefaultObjectFactory, OtherFactory, YetAnotherFactory | exception, cucumber couldn't decide multiple (non default) generators available | +| 7 | OtherFactory | DefaultObjectFactory, OtherFactory, YetAnotherFactory | OtherFactory used | +| 8 | OtherFactory | DefaultObjectFactory | exception, class not found through SPI | +| 9 | undefined | OtherFactory | OtherFactory used | + +Essentially this means that +* (2) Cucumber works by default +* (4) When adding a custom implementation to the class path it is used automatically +* When cucumber should not guess (5) or can not guess (7), the property is used to force a choice diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/ServiceLoaderTestClassLoader.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/ServiceLoaderTestClassLoader.java new file mode 100644 index 0000000000..bfedc5a0d6 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/ServiceLoaderTestClassLoader.java @@ -0,0 +1,96 @@ +package io.cucumber.core.runtime; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.Collections; +import java.util.Enumeration; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Testing classloader for ServiceLoader. This classloader overrides the + * META-INF/services/ file with a custom definition. + */ +public class ServiceLoaderTestClassLoader extends URLClassLoader { + Class metaInfInterface; + Class[] implementingClasses; + + /** + * Constructs a classloader which has no + * META-INF/services/. + * + * @param metaInfInterface ServiceLoader interface + */ + public ServiceLoaderTestClassLoader(Class metaInfInterface) { + this(metaInfInterface, (Class[]) null); + } + + /** + * Constructs a fake META-INF/services/ file which + * contains the provided array of classes. When the implementingClasses + * array is null, the META-INF file will not be constructed. The classes + * from implementingClasses are not required to implement the + * metaInfInterface. + * + * @param metaInfInterface ServiceLoader interface + * @param implementingClasses potential subclasses of the ServiceLoader + * metaInfInterface + */ + public ServiceLoaderTestClassLoader(Class metaInfInterface, Class... implementingClasses) { + super(new URL[0], metaInfInterface.getClassLoader()); + if (!metaInfInterface.isInterface()) { + throw new IllegalArgumentException("the META-INF service " + metaInfInterface + " should be an interface"); + } + this.metaInfInterface = metaInfInterface; + this.implementingClasses = implementingClasses; + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (name.equals("META-INF/services/" + metaInfInterface.getName())) { + if (implementingClasses == null) { + return Collections.emptyEnumeration(); + } + URL url = new URL("foo", "bar", 99, "/foobar", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) { + return new URLConnection(u) { + @Override + public void connect() { + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(Stream.of(implementingClasses) + .map(Class::getName) + .collect(Collectors.joining("\n")) + .getBytes()); + } + }; + } + }); + + return new Enumeration() { + boolean hasNext = true; + + @Override + public boolean hasMoreElements() { + return hasNext; + } + + @Override + public URL nextElement() { + hasNext = false; + return url; + } + }; + } + return super.getResources(name); + } + +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java new file mode 100644 index 0000000000..5843bbeedc --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -0,0 +1,268 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.eventbus.Options; +import io.cucumber.core.eventbus.RandomUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; +import io.cucumber.core.exception.CucumberException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @see test-cases description + */ +class UuidGeneratorServiceLoaderTest { + + /** + * | 1 | undefined | none | exception, no generators available | + */ + @Test + void test_case_1() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class), + options); + + CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); + assertThat(exception.getMessage(), is("" + + "Could not find any UUID generator.\n" + + "\n" + + "Cucumber uses SPI to discover UUID generator implementations.\n" + + "This typically happens when using shaded jars. Make sure\n" + + "to merge all SPI definitions in META-INF/services correctly")); + } + + /** + * | 2 | undefined | RandomUuidGenerator, IncrementingUuidGenerator | + * RandomUuidGenerator used | + */ + @Test + void test_case_2() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + } + + /** + * | 3 | RandomUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator | RandomUuidGenerator used | + */ + @Test + void test_case_3() { + Options options = () -> RandomUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + } + + /** + * | 4 | undefined | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator | OtherGenerator used | + */ + @Test + void test_case_4() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } + + /** + * | 4bis | undefined | OtherGenerator, RandomUuidGenerator, + * IncrementingUuidGenerator | OtherGenerator used | + */ + @Test + void test_case_4_bis() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + OtherGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } + + /** + * | 5 | RandomUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator, OtherGenerator | RandomUuidGenerator used | + */ + @Test + void test_case_5() { + Options options = () -> RandomUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + } + + /** + * | 6 | undefined | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator, YetAnotherGenerator | exception, cucumber couldn't decide + * multiple (non default) generators available | + */ + @Test + void test_case_6() { + // Given + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class, + YetAnotherGenerator.class), + options); + + // When + CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadUuidGenerator); + + // Then + assertThat(cucumberException.getMessage(), + Matchers.containsString("More than one Cucumber UuidGenerator was found on the classpath")); + } + + /** + * | 7 | OtherGenerator | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator, YetAnotherGenerator | OtherGenerator used | + */ + @Test + void test_case_7() { + Options options = () -> OtherGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class, + YetAnotherGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } + + /** + * | 8 | IncrementingUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator, OtherGenerator, YetAnotherGenerator | + * IncrementingUuidGenerator used | + */ + @Test + void test_case_8() { + Options options = () -> IncrementingUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class, + YetAnotherGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(IncrementingUuidGenerator.class)); + } + + /** + * | 9 | IncrementingUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator | IncrementingUuidGenerator used | + */ + @Test + void test_case_9() { + Options options = () -> IncrementingUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(IncrementingUuidGenerator.class)); + } + + /** + * | 10 | OtherGenerator | none | exception, generator OtherGenerator not + * available | + */ + @Test + void test_case_10() { + + Options options = () -> OtherGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class), + options); + + CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); + assertThat(exception.getMessage(), is("" + + "Could not find UUID generator io.cucumber.core.runtime.UuidGeneratorServiceLoaderTest$OtherGenerator.\n" + + + "\n" + + "Cucumber uses SPI to discover UUID generator implementations.\n" + + "Has the class been registered with SPI and is it available on\n" + + "the classpath?")); + } + + /** + * | 11 | undefined | OtherGenerator | OtherGenerator used | + */ + @Test + void test_case_11() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + OtherGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } + + /** + * | 12 | undefined | IncrementingUuidGenerator, OtherGenerator | + * OtherGenerator used | + */ + @Test + void test_case_12() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class), + options); + + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } + + /** + * | 13 | undefined | IncrementingUuidGenerator | IncrementingUuidGenerator + * used | + */ + @Test + void test_case_13() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + IncrementingUuidGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(IncrementingUuidGenerator.class)); + } + + public static class OtherGenerator implements UuidGenerator { + @Override + public UUID get() { + return null; + } + } + + public static class YetAnotherGenerator implements UuidGenerator { + @Override + public UUID get() { + return null; + } + } + +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md new file mode 100644 index 0000000000..34cad9beb1 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md @@ -0,0 +1,17 @@ +# Testcases for `UuidGeneratorServiceLoader` + +| # | uuid-generator property | Available services | Result | +|-----|---------------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------| +| 1 | undefined | none | exception, no generators available | +| 2 | undefined | RandomUuidGenerator, IncrementingUuidGenerator | RandomUuidGenerator used | +| 3 | RandomUuidGenerator | RandomUuidGenerator, IncrementingUuidGenerator | RandomUuidGenerator used | +| 4 | undefined | RandomUuidGenerator, IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | +| 5 | RandomUuidGenerator | RandomUuidGenerator, IncrementingUuidGenerator, OtherGenerator | RandomUuidGenerator used | +| 6 | undefined | RandomUuidGenerator, IncrementingUuidGenerator, OtherGenerator, YetAnotherGenerator | exception, cucumber couldn't decide multiple (non default) generators available | +| 7 | OtherGenerator | RandomUuidGenerator, IncrementingUuidGenerator, OtherGenerator, YetAnotherGenerator | OtherGenerator used | +| 8 | IncrementingUuidGenerator | RandomUuidGenerator, IncrementingUuidGenerator, OtherGenerator, YetAnotherGenerator | IncrementingUuidGenerator used | +| 9 | IncrementingUuidGenerator | RandomUuidGenerator, IncrementingUuidGenerator | IncrementingUuidGenerator used | +| 10 | OtherGenerator | none | exception, generator OtherGenerator not available | +| 11 | undefined | OtherGenerator | OtherGenerator used | +| 12 | undefined | IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | +| 13 | undefined | IncrementingUuidGenerator | IncrementingUuidGenerator used | diff --git a/cucumber-deltaspike/pom.xml b/cucumber-deltaspike/pom.xml index 0885894bf7..148b653ee3 100644 --- a/cucumber-deltaspike/pom.xml +++ b/cucumber-deltaspike/pom.xml @@ -5,7 +5,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-deltaspike diff --git a/cucumber-gherkin-messages/pom.xml b/cucumber-gherkin-messages/pom.xml index d2f80cc903..1011cdc84f 100644 --- a/cucumber-gherkin-messages/pom.xml +++ b/cucumber-gherkin-messages/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT diff --git a/cucumber-gherkin/pom.xml b/cucumber-gherkin/pom.xml index 2e5f8b9b3c..bc428c332b 100644 --- a/cucumber-gherkin/pom.xml +++ b/cucumber-gherkin/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT diff --git a/cucumber-guice/pom.xml b/cucumber-guice/pom.xml index 9918cfbca1..ae605315c8 100644 --- a/cucumber-guice/pom.xml +++ b/cucumber-guice/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-guice diff --git a/cucumber-jakarta-cdi/pom.xml b/cucumber-jakarta-cdi/pom.xml index 7896b6ac27..103a00208a 100644 --- a/cucumber-jakarta-cdi/pom.xml +++ b/cucumber-jakarta-cdi/pom.xml @@ -16,7 +16,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-jakarta-cdi diff --git a/cucumber-jakarta-openejb/pom.xml b/cucumber-jakarta-openejb/pom.xml index e8ef2c342d..50e41f64a6 100644 --- a/cucumber-jakarta-openejb/pom.xml +++ b/cucumber-jakarta-openejb/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-jakarta-openejb diff --git a/cucumber-java/pom.xml b/cucumber-java/pom.xml index c91c4f70d7..f540d9a468 100644 --- a/cucumber-java/pom.xml +++ b/cucumber-java/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-java diff --git a/cucumber-java8/pom.xml b/cucumber-java8/pom.xml index f4e9d1eea4..d168bb549c 100644 --- a/cucumber-java8/pom.xml +++ b/cucumber-java8/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-java8 diff --git a/cucumber-junit-platform-engine/pom.xml b/cucumber-junit-platform-engine/pom.xml index 709748aa3e..27e6e0438a 100644 --- a/cucumber-junit-platform-engine/pom.xml +++ b/cucumber-junit-platform-engine/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-junit-platform-engine diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java index 233d6909a5..aed3d7a449 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java @@ -172,6 +172,16 @@ public final class Constants { */ public static final String OBJECT_FACTORY_PROPERTY_NAME = io.cucumber.core.options.Constants.OBJECT_FACTORY_PROPERTY_NAME; + /** + * Property name to select custom UUID generator implementation: {@value} + *

+ * By default, if a single UUID generator is available on the class path + * that object factory will be used, or more than one UUID generator and the + * #RandomUuidGenerator are available on the classpath, the + * #RandomUuidGenerator will be used. + */ + public static final String UUID_GENERATOR_PROPERTY_NAME = io.cucumber.core.options.Constants.UUID_GENERATOR_PROPERTY_NAME; + /** * Property name to control naming convention for generated snippets: * {@value} diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java index c72a2af41d..09d7a40f8a 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineOptions.java @@ -1,11 +1,13 @@ package io.cucumber.junit.platform.engine; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.feature.GluePath; import io.cucumber.core.options.ObjectFactoryParser; import io.cucumber.core.options.PluginOption; import io.cucumber.core.options.SnippetTypeParser; +import io.cucumber.core.options.UuidGeneratorParser; import io.cucumber.core.plugin.NoPublishFormatter; import io.cucumber.core.plugin.PublishFormatter; import io.cucumber.core.snippets.SnippetType; @@ -39,11 +41,13 @@ import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.SNIPPET_TYPE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.UUID_GENERATOR_PROPERTY_NAME; class CucumberEngineOptions implements io.cucumber.core.plugin.Options, io.cucumber.core.runner.Options, - io.cucumber.core.backend.Options { + io.cucumber.core.backend.Options, + io.cucumber.core.eventbus.Options { private final ConfigurationParameters configurationParameters; @@ -156,6 +160,13 @@ public Class getObjectFactoryClass() { .orElse(null); } + @Override + public Class getUuidGeneratorClass() { + return configurationParameters + .get(UUID_GENERATOR_PROPERTY_NAME, UuidGeneratorParser::parseUuidGenerator) + .orElse(null); + } + boolean isParallelExecutionEnabled() { return configurationParameters .getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME) diff --git a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index 3f1bdaf915..e1260daf2d 100644 --- a/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -1,10 +1,11 @@ package io.cucumber.junit.platform.engine; +import io.cucumber.core.backend.DefaultObjectFactory; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.plugin.Options; import io.cucumber.core.snippets.SnippetType; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.support.hierarchical.Node; import java.net.URI; @@ -150,7 +151,25 @@ void isParallelExecutionEnabled() { ConfigurationParameters absent = new MapConfigurationParameters( "some key", "some value"); assertFalse(new CucumberEngineOptions(absent).isParallelExecutionEnabled()); + } + + @Test + void objectFactory() { + ConfigurationParameters configurationParameters = new MapConfigurationParameters( + Constants.OBJECT_FACTORY_PROPERTY_NAME, + DefaultObjectFactory.class.getName()); + assertThat(new CucumberEngineOptions(configurationParameters).getObjectFactoryClass(), + is(DefaultObjectFactory.class)); } + @Test + void uuidGenerator() { + ConfigurationParameters configurationParameters = new MapConfigurationParameters( + Constants.UUID_GENERATOR_PROPERTY_NAME, + IncrementingUuidGenerator.class.getName()); + + assertThat(new CucumberEngineOptions(configurationParameters).getUuidGeneratorClass(), + is(IncrementingUuidGenerator.class)); + } } diff --git a/cucumber-junit/pom.xml b/cucumber-junit/pom.xml index ad37bb09fc..8142b58a0c 100644 --- a/cucumber-junit/pom.xml +++ b/cucumber-junit/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-junit diff --git a/cucumber-junit/src/main/java/io/cucumber/junit/CucumberOptions.java b/cucumber-junit/src/main/java/io/cucumber/junit/CucumberOptions.java index 455470cb1e..98a3094f30 100644 --- a/cucumber-junit/src/main/java/io/cucumber/junit/CucumberOptions.java +++ b/cucumber-junit/src/main/java/io/cucumber/junit/CucumberOptions.java @@ -149,6 +149,17 @@ */ Class objectFactory() default NoObjectFactory.class; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified + * here. A custom ObjectFactory might be needed when more granular control + * is needed over the dependency injection mechanism. + * + * @return an {@link io.cucumber.core.backend.ObjectFactory} implementation + */ + Class uuidGenerator() default NoUuidGenerator.class; + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/cucumber-junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java b/cucumber-junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java index ce7206026c..eafd59b962 100644 --- a/cucumber-junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java +++ b/cucumber-junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.junit; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; @@ -102,6 +103,10 @@ public Class objectFactory() { return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); } + @Override + public Class uuidGenerator() { + return (annotation.uuidGenerator() == NoUuidGenerator.class) ? null : annotation.uuidGenerator(); + } } } diff --git a/cucumber-junit/src/main/java/io/cucumber/junit/NoUuidGenerator.java b/cucumber-junit/src/main/java/io/cucumber/junit/NoUuidGenerator.java new file mode 100644 index 0000000000..91f40e3fd7 --- /dev/null +++ b/cucumber-junit/src/main/java/io/cucumber/junit/NoUuidGenerator.java @@ -0,0 +1,20 @@ +package io.cucumber.junit; + +import io.cucumber.core.eventbus.UuidGenerator; + +import java.util.UUID; + +/** + * This UUID generator does nothing. It is solely needed for marking purposes. + */ +final class NoUuidGenerator implements UuidGenerator { + + private NoUuidGenerator() { + // No need for instantiation + } + + @Override + public UUID get() { + return null; + } +} diff --git a/cucumber-junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java b/cucumber-junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java index 43b84959fb..515f554ec2 100644 --- a/cucumber-junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java +++ b/cucumber-junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java @@ -1,6 +1,7 @@ package io.cucumber.junit; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,6 +22,7 @@ void setUp() { void testObjectFactoryWhenNotSpecified() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithDefault.class); + assertNotNull(options); assertNull(options.objectFactory()); } @@ -28,10 +30,26 @@ void testObjectFactoryWhenNotSpecified() { void testObjectFactory() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithCustomObjectFactory.class); - assertNotNull(options.objectFactory()); + assertNotNull(options); assertEquals(TestObjectFactory.class, options.objectFactory()); } + @Test + void testUuidGeneratorWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider + .getOptions(ClassWithDefault.class); + assertNotNull(options); + assertNull(options.uuidGenerator()); + } + + @Test + void testUuidGenerator() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider + .getOptions(ClassWithCustomUuidGenerator.class); + assertNotNull(options); + assertEquals(IncrementingUuidGenerator.class, options.uuidGenerator()); + } + @CucumberOptions() private static final class ClassWithDefault { @@ -42,6 +60,11 @@ private static final class ClassWithCustomObjectFactory { } + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) + private static final class ClassWithCustomUuidGenerator { + + } + private static final class TestObjectFactory implements ObjectFactory { @Override diff --git a/cucumber-kotlin-java8/pom.xml b/cucumber-kotlin-java8/pom.xml index b0e979f1d6..78575066df 100644 --- a/cucumber-kotlin-java8/pom.xml +++ b/cucumber-kotlin-java8/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-kotlin-java8 diff --git a/cucumber-openejb/pom.xml b/cucumber-openejb/pom.xml index 3935e8a197..b4078bc381 100644 --- a/cucumber-openejb/pom.xml +++ b/cucumber-openejb/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-openejb diff --git a/cucumber-picocontainer/pom.xml b/cucumber-picocontainer/pom.xml index 4ccc244368..269332be47 100644 --- a/cucumber-picocontainer/pom.xml +++ b/cucumber-picocontainer/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-picocontainer diff --git a/cucumber-plugin/pom.xml b/cucumber-plugin/pom.xml index f573fdeea9..1f58288b92 100644 --- a/cucumber-plugin/pom.xml +++ b/cucumber-plugin/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-plugin diff --git a/cucumber-spring/pom.xml b/cucumber-spring/pom.xml index 6f208692f9..84ede415c0 100644 --- a/cucumber-spring/pom.xml +++ b/cucumber-spring/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-spring diff --git a/cucumber-testng/pom.xml b/cucumber-testng/pom.xml index a023bcbc9d..00d83fa394 100644 --- a/cucumber-testng/pom.xml +++ b/cucumber-testng/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT cucumber-testng diff --git a/cucumber-testng/src/main/java/io/cucumber/testng/CucumberOptions.java b/cucumber-testng/src/main/java/io/cucumber/testng/CucumberOptions.java index 84d6abd366..9b7f9fbbea 100644 --- a/cucumber-testng/src/main/java/io/cucumber/testng/CucumberOptions.java +++ b/cucumber-testng/src/main/java/io/cucumber/testng/CucumberOptions.java @@ -122,6 +122,17 @@ */ Class objectFactory() default NoObjectFactory.class; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified + * here. A custom ObjectFactory might be needed when more granular control + * is needed over the dependency injection mechanism. + * + * @return an {@link io.cucumber.core.backend.ObjectFactory} implementation + */ + Class uuidGenerator() default NoUuidGenerator.class; + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/cucumber-testng/src/main/java/io/cucumber/testng/NoUuidGenerator.java b/cucumber-testng/src/main/java/io/cucumber/testng/NoUuidGenerator.java new file mode 100644 index 0000000000..25a4b6759b --- /dev/null +++ b/cucumber-testng/src/main/java/io/cucumber/testng/NoUuidGenerator.java @@ -0,0 +1,20 @@ +package io.cucumber.testng; + +import io.cucumber.core.eventbus.UuidGenerator; + +import java.util.UUID; + +/** + * This UUID generator does nothing. It is solely needed for marking purposes. + */ +final class NoUuidGenerator implements UuidGenerator { + + private NoUuidGenerator() { + // No need for instantiation + } + + @Override + public UUID get() { + return null; + } +} diff --git a/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java b/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java index ed8218f537..52d1b81a1a 100644 --- a/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java +++ b/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.testng; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; @@ -102,6 +103,10 @@ public Class objectFactory() { return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); } + @Override + public Class uuidGenerator() { + return (annotation.uuidGenerator() == NoUuidGenerator.class) ? null : annotation.uuidGenerator(); + } } } diff --git a/cucumber-testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java b/cucumber-testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java index c7b4ccc8c2..40045bd841 100644 --- a/cucumber-testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java +++ b/cucumber-testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java @@ -1,6 +1,7 @@ package io.cucumber.testng; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -21,6 +22,7 @@ void setUp() { void testObjectFactoryWhenNotSpecified() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithDefault.class); + assertNotNull(options); assertNull(options.objectFactory()); } @@ -28,10 +30,26 @@ void testObjectFactoryWhenNotSpecified() { void testObjectFactory() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithCustomObjectFactory.class); - assertNotNull(options.objectFactory()); + assertNotNull(options); assertEquals(TestObjectFactory.class, options.objectFactory()); } + @Test + void testUuidGeneratorWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider + .getOptions(ClassWithDefault.class); + assertNotNull(options); + assertNull(options.uuidGenerator()); + } + + @Test + void testUuidGenerator() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider + .getOptions(ClassWithCustomUuidGenerator.class); + assertNotNull(options); + assertEquals(IncrementingUuidGenerator.class, options.uuidGenerator()); + } + @CucumberOptions() private static final class ClassWithDefault { @@ -42,6 +60,11 @@ private static final class ClassWithCustomObjectFactory { } + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) + private static final class ClassWithCustomUuidGenerator { + + } + private static final class TestObjectFactory implements ObjectFactory { @Override diff --git a/datatable-matchers/pom.xml b/datatable-matchers/pom.xml index 659b66be80..1aedc0c903 100644 --- a/datatable-matchers/pom.xml +++ b/datatable-matchers/pom.xml @@ -5,7 +5,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT datatable-matchers diff --git a/datatable/pom.xml b/datatable/pom.xml index 315a1fefc0..dceb876af8 100644 --- a/datatable/pom.xml +++ b/datatable/pom.xml @@ -5,7 +5,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT datatable diff --git a/docstring/pom.xml b/docstring/pom.xml index 26c12cb1cc..7606517d44 100644 --- a/docstring/pom.xml +++ b/docstring/pom.xml @@ -3,7 +3,7 @@ cucumber-jvm io.cucumber - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT 4.0.0 diff --git a/examples/calculator-java-cli/pom.xml b/examples/calculator-java-cli/pom.xml index 0bba7e28e1..82c51bd6e5 100644 --- a/examples/calculator-java-cli/pom.xml +++ b/examples/calculator-java-cli/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT calculator-java-cli diff --git a/examples/calculator-java-junit4/pom.xml b/examples/calculator-java-junit4/pom.xml index dff7ea4334..0adaf774d2 100644 --- a/examples/calculator-java-junit4/pom.xml +++ b/examples/calculator-java-junit4/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT calculator-java-junit4 diff --git a/examples/calculator-java-junit5/pom.xml b/examples/calculator-java-junit5/pom.xml index d70df8cbd5..ca450299e3 100644 --- a/examples/calculator-java-junit5/pom.xml +++ b/examples/calculator-java-junit5/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT calculator-java-junit5 diff --git a/examples/calculator-java-testng/pom.xml b/examples/calculator-java-testng/pom.xml index 768e375ffc..46d5f22a6b 100644 --- a/examples/calculator-java-testng/pom.xml +++ b/examples/calculator-java-testng/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT calculator-java-testng diff --git a/examples/calculator-java8-cli/pom.xml b/examples/calculator-java8-cli/pom.xml index e941f496ac..c6c8bb3df3 100644 --- a/examples/calculator-java8-cli/pom.xml +++ b/examples/calculator-java8-cli/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT calculator-java8-cli diff --git a/examples/pom.xml b/examples/pom.xml index 0e16a909a6..08f2bed077 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -4,7 +4,7 @@ io.cucumber cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT examples diff --git a/examples/spring-java-junit5/pom.xml b/examples/spring-java-junit5/pom.xml index 21026bdc1c..f9b23501c7 100644 --- a/examples/spring-java-junit5/pom.xml +++ b/examples/spring-java-junit5/pom.xml @@ -4,7 +4,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT spring-java-junit5 diff --git a/examples/wicket-java-junit4/pom.xml b/examples/wicket-java-junit4/pom.xml index bd2b005cca..b00a8c9311 100644 --- a/examples/wicket-java-junit4/pom.xml +++ b/examples/wicket-java-junit4/pom.xml @@ -3,7 +3,7 @@ io.cucumber examples - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT wicket-java-junit4 pom diff --git a/examples/wicket-java-junit4/wicket-main/pom.xml b/examples/wicket-java-junit4/wicket-main/pom.xml index c072b47901..e2c331d77c 100644 --- a/examples/wicket-java-junit4/wicket-main/pom.xml +++ b/examples/wicket-java-junit4/wicket-main/pom.xml @@ -3,7 +3,7 @@ io.cucumber wicket-java-junit4 - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT wicket-main Examples: Wicket application diff --git a/examples/wicket-java-junit4/wicket-test/pom.xml b/examples/wicket-java-junit4/wicket-test/pom.xml index bd2aa7ce1f..5e42ba5ac0 100644 --- a/examples/wicket-java-junit4/wicket-test/pom.xml +++ b/examples/wicket-java-junit4/wicket-test/pom.xml @@ -3,7 +3,7 @@ io.cucumber wicket-java-junit4 - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT wicket-test Examples: Wicket application tested with Selenium diff --git a/pom.xml b/pom.xml index cbc45d574b..96cf0ed92a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cucumber-jvm - 7.11.2-SNAPSHOT + 7.12.0-SNAPSHOT pom Cucumber-JVM Cucumber for the JVM @@ -18,7 +18,7 @@ 1.8 8 - 1674814830 + 1678784521 scm:git:git://github.com/cucumber/cucumber-jvm.git