From 4c2b52547030f8c2294cea523dac39958242e243 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 08:02:21 +0100 Subject: [PATCH 01/14] feat: added UUID generator selection through SPI for https://github.com/cucumber/cucumber-jvm/issues/2698 --- CHANGELOG.md | 3 + cucumber-core/README.md | 20 +++ .../cucumber/core/cli/CommandlineOptions.java | 2 + .../core/eventbus/DefaultUuidGenerator.java | 14 +++ .../core/eventbus/FastUuidGenerator.java | 32 +++++ .../io/cucumber/core/eventbus/Options.java | 7 ++ .../cucumber/core/eventbus/UuidGenerator.java | 10 ++ .../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 | 115 ++++++++++++++++++ .../io.cucumber.core.eventbus.UuidGenerator | 2 + .../io/cucumber/core/options/USAGE.txt | 7 ++ .../eventbus/DefaultUuidGeneratorTest.java | 24 ++++ .../core/eventbus/FastUuidGeneratorTest.java | 24 ++++ .../options/CommandlineOptionsParserTest.java | 14 ++- .../core/options/CucumberOptions.java | 2 + .../CucumberOptionsAnnotationParserTest.java | 18 +++ .../core/options/CucumberPropertiesTest.java | 9 +- .../core/options/NoUuidGenerator.java | 20 +++ .../options/RuntimeOptionsBuilderTest.java | 21 ++++ .../core/runtime/FilteredClassLoader.java | 30 +++++ .../ObjectFactoryServiceLoaderTest.java | 28 ----- .../UuidGeneratorServiceLoaderTest.java | 92 ++++++++++++++ .../junit/platform/engine/Constants.java | 10 ++ .../engine/CucumberEngineOptions.java | 15 ++- .../io/cucumber/junit/CucumberOptions.java | 11 ++ .../junit/JUnitCucumberOptionsProvider.java | 5 + .../io/cucumber/junit/NoUuidGenerator.java | 20 +++ .../io/cucumber/testng/CucumberOptions.java | 11 ++ .../io/cucumber/testng/NoUuidGenerator.java | 20 +++ .../testng/TestNGCucumberOptionsProvider.java | 5 + 38 files changed, 650 insertions(+), 36 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.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/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/DefaultUuidGeneratorTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.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/runtime/FilteredClassLoader.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java 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/CHANGELOG.md b/CHANGELOG.md index 2ac08ee839..157e530655 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] Exposed UUID generator as SPI ([#2698](https://github.com/cucumber/cucumber-jvm/pull/???) 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/cucumber-core/README.md b/cucumber-core/README.md index cdd5a7824f..614df4c15f 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.DefaultUuidGenerator | Thread-safe, 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.FastUuidGenerator | Thread-safe, 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 `DefaultUuidGenerator` is used. + ## Plugin ## By implementing the Plugin interface classes can listen to execution events 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/DefaultUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java new file mode 100644 index 0000000000..99d0173988 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java @@ -0,0 +1,14 @@ +package io.cucumber.core.eventbus; + +import java.util.UUID; + +/** + * Default UUID generator. The generator is thread-safe and supports multi-jvm + * usage of Cucumber. + */ +public class DefaultUuidGenerator implements UuidGenerator { + @Override + public UUID get() { + return UUID.randomUUID(); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java new file mode 100644 index 0000000000..2914b0804c --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java @@ -0,0 +1,32 @@ +package io.cucumber.core.eventbus; + +import io.cucumber.core.exception.CucumberException; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Thread-safe 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 #DefaultUuidGenerator. If you use Cucumber in multi-JVM setup, you + * should use #DefaultUuidGenerator instead. + */ +public class FastUuidGenerator implements UuidGenerator { + private final AtomicLong counter = new AtomicLong(Long.MIN_VALUE); + + /** + * 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 FastUuidGenerator capacity. Please use the DefaultUuidGenerator instead."); + } + return new UUID(Thread.currentThread().getId(), 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/UuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java new file mode 100644 index 0000000000..596bd23c81 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java @@ -0,0 +1,10 @@ +package io.cucumber.core.eventbus; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * SPI (Service Provider Interface) to generate UUIDs. + */ +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..19f35ec7ab --- /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 cucumberObjectFactory) { + Class uuidGeneratorClass; + try { + uuidGeneratorClass = Class.forName(cucumberObjectFactory); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + String.format("Could not load UUID generator class for '%s'", cucumberObjectFactory), 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..92ec4befe4 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -0,0 +1,115 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.eventbus.DefaultUuidGenerator; +import io.cucumber.core.eventbus.Options; +import io.cucumber.core.eventbus.UuidGenerator; +import io.cucumber.core.exception.CucumberException; + +import java.util.Iterator; +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 DefaultUuidGenerator} 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(); + + // Find the first UUID generator + UuidGenerator uuidGenerator = null; + if (uuidGenerators.hasNext()) { + uuidGenerator = uuidGenerators.next(); + } + if (uuidGenerator == null) { + 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"); + } + + // look for the DefaultUuidGenerator + UuidGenerator extraUuidGenerator = null; + while (uuidGenerators.hasNext() && !(uuidGenerator instanceof DefaultUuidGenerator)) { + extraUuidGenerator = uuidGenerator; + uuidGenerator = uuidGenerators.next(); + } + + // Check if there are no other non-default object factories + if (!(uuidGenerator instanceof DefaultUuidGenerator)) { + throw new CucumberException(getMultipleUuidGeneratorLogMessage(uuidGenerator, extraUuidGenerator)); + } + + return uuidGenerator; + } + + 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(UuidGenerator... 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 may have included, for instance, cucumber-spring AND cucumber-guice as part\n" + + "of your dependencies. When this happens, Cucumber can't decide which to use.\n" + + "In order to enjoy dependency injection features, either remove the unnecessary\n" + + "dependencies from your classpath or use the `cucumber.uuid-generator` property\n" + + "or `@CucumberOptions(uuidGenerator=...)` to select one.\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..90ec5faa33 --- /dev/null +++ b/cucumber-core/src/main/resources/META-INF/services/io.cucumber.core.eventbus.UuidGenerator @@ -0,0 +1,2 @@ +io.cucumber.core.eventbus.DefaultUuidGenerator +io.cucumber.core.eventbus.FastUuidGenerator 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/DefaultUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/DefaultUuidGeneratorTest.java new file mode 100644 index 0000000000..0145d55972 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/DefaultUuidGeneratorTest.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 DefaultUuidGeneratorTest { + @Test + void generates_different_non_null_uuids() { + // Given + UuidGenerator generator = new DefaultUuidGenerator(); + 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/eventbus/FastUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java new file mode 100644 index 0000000000..1c9632133f --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.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 FastUuidGeneratorTest { + @Test + void generates_different_non_null_uuids() { + // Given + UuidGenerator generator = new FastUuidGenerator(); + 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..f0419f6c08 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.FastUuidGenerator; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.gherkin.Pickle; @@ -76,6 +77,15 @@ void testParseWithObjectFactoryArgument() { assertThat(options.getObjectFactoryClass(), Is.is(equalTo(TestObjectFactory.class))); } + @Test + void testParseWithUuidGeneratorArgument() { + RuntimeOptionsBuilder optionsBuilder = parser.parse("--uuid-generator", FastUuidGenerator.class.getName()); + assertNotNull(optionsBuilder); + RuntimeOptions options = optionsBuilder.build(); + assertNotNull(options); + assertThat(options.getUuidGeneratorClass(), Is.is(equalTo(FastUuidGenerator.class))); + } + @Test void has_version_from_properties_file() { parser.parse("--version"); @@ -84,7 +94,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 @@ -234,7 +244,7 @@ void creates_default_summary_printer_for_deprecated_default_summary_argument() { } private static Matcher plugin(final String pluginName) { - return new TypeSafeDiagnosingMatcher() { + return new TypeSafeDiagnosingMatcher<>() { @Override protected boolean matchesSafely(Plugin plugin, Description description) { description.appendValue(plugin.getClass().getName()); 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..bf7c78a997 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.FastUuidGenerator; +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(FastUuidGenerator.class)); + } + @CucumberOptions(snippets = SnippetType.CAMELCASE) private static class Snippets { // empty @@ -363,6 +372,11 @@ private static class ClassWithGlueAndExtraGlue { // empty } + @CucumberOptions(uuidGenerator = FastUuidGenerator.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..23e497bafc --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java @@ -0,0 +1,21 @@ +package io.cucumber.core.options; + +import io.cucumber.core.eventbus.FastUuidGenerator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RuntimeOptionsBuilderTest { + + @Test + void build() { + // Given + RuntimeOptionsBuilder builder = new RuntimeOptionsBuilder().setUuidGeneratorClass(FastUuidGenerator.class); + + // When + RuntimeOptions runtimeOptions = builder.build(); + + // Then + assertEquals(FastUuidGenerator.class, runtimeOptions.getUuidGeneratorClass()); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/runtime/FilteredClassLoader.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/FilteredClassLoader.java new file mode 100644 index 0000000000..14b0ab9ca3 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/FilteredClassLoader.java @@ -0,0 +1,30 @@ +package io.cucumber.core.runtime; + +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; + +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); + } + +} 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..63c9bd4a85 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 @@ -6,13 +6,6 @@ import io.cucumber.core.exception.CucumberException; 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.is; @@ -102,25 +95,4 @@ 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); - } - - } - } 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..be79209fd4 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -0,0 +1,92 @@ +package io.cucumber.core.runtime; + +import io.cucumber.core.eventbus.DefaultUuidGenerator; +import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.Options; +import io.cucumber.core.eventbus.UuidGenerator; +import io.cucumber.core.exception.CucumberException; +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.function.Supplier; + +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.*; + +class UuidGeneratorServiceLoaderTest { + + @Test + void shouldLoadDefaultUuidGeneratorService() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(DefaultUuidGenerator.class)); + } + + @Test + void shouldLoadSelectedUuidGeneratorService() { + Options options = () -> DefaultUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(DefaultUuidGenerator.class)); + } + + @Test + void shouldLoadSelectedFastUuidGeneratorService() { + Options options = () -> FastUuidGenerator.class; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + UuidGeneratorServiceLoaderTest.class::getClassLoader, + options); + assertThat(loader.loadUuidGenerator(), instanceOf(FastUuidGenerator.class)); + } + + @Test + void shouldThrowIfDefaultUuidGeneratorServiceCouldNotBeLoaded() { + Options options = () -> null; + Supplier classLoader = () -> new FilteredClassLoader( + "META-INF/services/io.cucumber.core.eventbus.UuidGenerator"); + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + classLoader, + 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")); + } + + @Test + void shouldThrowIfSelectedUuidGeneratorServiceCouldNotBeLoaded() { + + Options options = () -> NoSuchUuidGenerator.class; + Supplier classLoader = () -> new FilteredClassLoader( + "META-INF/services/io.cucumber.core.eventbus.UuidGenerator"); + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + classLoader, + options); + + CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); + assertThat(exception.getMessage(), is("" + + "Could not find UUID generator io.cucumber.core.runtime.UuidGeneratorServiceLoaderTest$NoSuchUuidGenerator.\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?")); + } + + static class NoSuchUuidGenerator implements UuidGenerator { + @Override + public UUID get() { + return null; + } + } + +} 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..faf5cb365d 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 + * #DefaultUuidGenerator are available on the classpath, the + * #DefaultUuidGenerator 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..543fd5a2b9 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; @@ -71,7 +75,7 @@ private Optional getPublishPlugin() { Optional fromEnabled = getPublishEnabledPlugin(); Optional plugin = Stream.of(fromToken, fromEnabled) - .flatMap(pluginOption -> pluginOption.map(Stream::of).orElseGet(Stream::empty)) + .flatMap(Optional::stream) .findFirst(); // With higher java version use ifPresentOrElse in plugins() @@ -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/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-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(); + } } } From 20e49b5f8e77ec28ac87b503883a7c0b098a72aa Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 08:32:36 +0100 Subject: [PATCH 02/14] feat: rephrased changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157e530655..0aeb6dbd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- [Core] Exposed UUID generator as SPI ([#2698](https://github.com/cucumber/cucumber-jvm/pull/???) Julien Kronegg) +- [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 From 50004a726a241f29f694c3eb1c375ee98f2121a0 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 08:34:34 +0100 Subject: [PATCH 03/14] fix: reverted bad cast --- .../io/cucumber/core/options/CommandlineOptionsParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f0419f6c08..faf5d76fd8 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 @@ -244,7 +244,7 @@ void creates_default_summary_printer_for_deprecated_default_summary_argument() { } private static Matcher plugin(final String pluginName) { - return new TypeSafeDiagnosingMatcher<>() { + return new TypeSafeDiagnosingMatcher() { @Override protected boolean matchesSafely(Plugin plugin, Description description) { description.appendValue(plugin.getClass().getName()); From be705f2a4a49fe9b5f21e7bb5a1511dfc9711124 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 09:54:16 +0100 Subject: [PATCH 04/14] fix: improved test coverage --- .../core/eventbus/FastUuidGeneratorTest.java | 22 ++++++++++++++++ .../engine/CucumberEngineOptions.java | 2 +- .../engine/CucumberEngineOptionsTest.java | 20 ++++++++++++++ .../JUnitCucumberOptionsProviderTest.java | 26 ++++++++++++++++++- .../TestNGCucumberOptionsProviderTest.java | 25 +++++++++++++++++- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java index 1c9632133f..9ddf0f7c42 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java @@ -1,9 +1,14 @@ 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 FastUuidGeneratorTest { @@ -21,4 +26,21 @@ void generates_different_non_null_uuids() { assertNotNull(uuid2); assertNotEquals(uuid1, uuid2); } + + @Test + void raises_exception_when_out_of_range() throws NoSuchFieldException, IllegalAccessException { + // Given + UuidGenerator generator = new FastUuidGenerator(); + Field counterField = FastUuidGenerator.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 FastUuidGenerator capacity")); + } + } 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 543fd5a2b9..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 @@ -75,7 +75,7 @@ private Optional getPublishPlugin() { Optional fromEnabled = getPublishEnabledPlugin(); Optional plugin = Stream.of(fromToken, fromEnabled) - .flatMap(Optional::stream) + .flatMap(pluginOption -> pluginOption.map(Stream::of).orElseGet(Stream::empty)) .findFirst(); // With higher java version use ifPresentOrElse in plugins() 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..035c916f82 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,5 +1,7 @@ package io.cucumber.junit.platform.engine; +import io.cucumber.core.backend.DefaultObjectFactory; +import io.cucumber.core.eventbus.FastUuidGenerator; import io.cucumber.core.plugin.Options; import io.cucumber.core.snippets.SnippetType; import org.junit.jupiter.api.Test; @@ -150,7 +152,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, + FastUuidGenerator.class.getName()); + + assertThat(new CucumberEngineOptions(configurationParameters).getUuidGeneratorClass(), + is(FastUuidGenerator.class)); + } } 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..8d3191b8a8 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,8 @@ package io.cucumber.junit; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.UuidGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,6 +23,7 @@ void setUp() { void testObjectFactoryWhenNotSpecified() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithDefault.class); + assertNotNull(options); assertNull(options.objectFactory()); } @@ -28,10 +31,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(FastUuidGenerator.class, options.uuidGenerator()); + } + @CucumberOptions() private static final class ClassWithDefault { @@ -42,6 +61,11 @@ private static final class ClassWithCustomObjectFactory { } + @CucumberOptions(uuidGenerator = FastUuidGenerator.class) + private static final class ClassWithCustomUuidGenerator { + + } + private static final class TestObjectFactory implements ObjectFactory { @Override 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..6728c8fa60 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.FastUuidGenerator; 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(FastUuidGenerator.class, options.uuidGenerator()); + } + @CucumberOptions() private static final class ClassWithDefault { @@ -42,6 +60,11 @@ private static final class ClassWithCustomObjectFactory { } + @CucumberOptions(uuidGenerator = FastUuidGenerator.class) + private static final class ClassWithCustomUuidGenerator { + + } + private static final class TestObjectFactory implements ObjectFactory { @Override From bbd3e8c2756affa73e3d954ceefe8f2a0ce8d1c9 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 10:04:39 +0100 Subject: [PATCH 05/14] fix: update semver 7.11.2-SNAPSHOT -> 7.12.0-SNAPSHOT --- compatibility/pom.xml | 2 +- cucumber-archetype/pom.xml | 2 +- cucumber-bom/pom.xml | 40 +++++++++---------- cucumber-cdi2/pom.xml | 2 +- cucumber-core/pom.xml | 2 +- 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 +- cucumber-junit/pom.xml | 2 +- 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 +- 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 +- 35 files changed, 55 insertions(+), 55 deletions(-) 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/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-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/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-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/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 From 7ed2939f03f8e766f58a6c93f4ca4ea78d90c969 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 14:43:05 +0100 Subject: [PATCH 06/14] fix: improved test coverage and corrected code according to PR comments --- .revapi/api-changes.json | 12 +++++ cucumber-core/README.md | 10 ++-- .../core/eventbus/DefaultUuidGenerator.java | 14 ----- .../core/eventbus/FastUuidGenerator.java | 32 ----------- .../eventbus/IncrementingUuidGenerator.java | 40 ++++++++++++++ .../core/eventbus/RandomUuidGenerator.java | 14 +++++ .../core/options/UuidGeneratorParser.java | 6 +-- .../runtime/UuidGeneratorServiceLoader.java | 13 ++--- .../io.cucumber.core.eventbus.UuidGenerator | 4 +- ...ava => IncrementingUuidGeneratorTest.java} | 26 +++++++-- ...Test.java => RandomUuidGeneratorTest.java} | 4 +- .../options/CommandlineOptionsParserTest.java | 7 +-- .../CucumberOptionsAnnotationParserTest.java | 6 +-- .../options/RuntimeOptionsBuilderTest.java | 7 +-- .../core/options/UuidGeneratorParserTest.java | 53 +++++++++++++++++++ .../UuidGeneratorServiceLoaderTest.java | 20 +++---- .../junit/platform/engine/Constants.java | 4 +- .../engine/CucumberEngineOptionsTest.java | 7 ++- .../JUnitCucumberOptionsProviderTest.java | 7 ++- .../TestNGCucumberOptionsProviderTest.java | 6 +-- 20 files changed, 191 insertions(+), 101 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java 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/RandomUuidGenerator.java rename cucumber-core/src/test/java/io/cucumber/core/eventbus/{FastUuidGeneratorTest.java => IncrementingUuidGeneratorTest.java} (56%) rename cucumber-core/src/test/java/io/cucumber/core/eventbus/{DefaultUuidGeneratorTest.java => RandomUuidGeneratorTest.java} (82%) create mode 100644 cucumber-core/src/test/java/io/cucumber/core/options/UuidGeneratorParserTest.java diff --git a/.revapi/api-changes.json b/.revapi/api-changes.json index 5bd2911d71..d31c24e69b 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" } ] } diff --git a/cucumber-core/README.md b/cucumber-core/README.md index 614df4c15f..691a541ca0 100644 --- a/cucumber-core/README.md +++ b/cucumber-core/README.md @@ -90,14 +90,14 @@ Cucumber emits events on an event bus in many cases: 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.DefaultUuidGenerator | Thread-safe, 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.FastUuidGenerator | Thread-safe, single-jvm | ~130 | Reports are generated on a single JVM | +| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example | +|-----------------------------------------------|-------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| io.cucumber.core.eventbus.RandomUuidGenerator | Thread-safe, 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, 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 `DefaultUuidGenerator` is used. +When not specified, the `RandomUuidGenerator` is used. ## Plugin ## diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java deleted file mode 100644 index 99d0173988..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/DefaultUuidGenerator.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.cucumber.core.eventbus; - -import java.util.UUID; - -/** - * Default UUID generator. The generator is thread-safe and supports multi-jvm - * usage of Cucumber. - */ -public class DefaultUuidGenerator implements UuidGenerator { - @Override - public UUID get() { - return UUID.randomUUID(); - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java deleted file mode 100644 index 2914b0804c..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/FastUuidGenerator.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.cucumber.core.eventbus; - -import io.cucumber.core.exception.CucumberException; - -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Thread-safe 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 #DefaultUuidGenerator. If you use Cucumber in multi-JVM setup, you - * should use #DefaultUuidGenerator instead. - */ -public class FastUuidGenerator implements UuidGenerator { - private final AtomicLong counter = new AtomicLong(Long.MIN_VALUE); - - /** - * 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 FastUuidGenerator capacity. Please use the DefaultUuidGenerator instead."); - } - return new UUID(Thread.currentThread().getId(), leastSigBits); - } -} 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/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/options/UuidGeneratorParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java index 19f35ec7ab..ecc633f211 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java +++ b/cucumber-core/src/main/java/io/cucumber/core/options/UuidGeneratorParser.java @@ -9,13 +9,13 @@ private UuidGeneratorParser() { } @SuppressWarnings("unchecked") - public static Class parseUuidGenerator(String cucumberObjectFactory) { + public static Class parseUuidGenerator(String cucumberUuidGenerator) { Class uuidGeneratorClass; try { - uuidGeneratorClass = Class.forName(cucumberObjectFactory); + uuidGeneratorClass = Class.forName(cucumberUuidGenerator); } catch (ClassNotFoundException e) { throw new IllegalArgumentException( - String.format("Could not load UUID generator class for '%s'", cucumberObjectFactory), e); + 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'", 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 index 92ec4befe4..07652dac0c 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -1,7 +1,7 @@ package io.cucumber.core.runtime; -import io.cucumber.core.eventbus.DefaultUuidGenerator; import io.cucumber.core.eventbus.Options; +import io.cucumber.core.eventbus.RandomUuidGenerator; import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; @@ -23,7 +23,7 @@ * exactly one {@code UuidGenerator} instance available that instance will be * used. *

- * Otherwise {@link DefaultUuidGenerator} with no dependency injection + * Otherwise {@link RandomUuidGenerator} with no dependency injection */ public final class UuidGeneratorServiceLoader { @@ -63,15 +63,15 @@ private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader loader, Class uuidGeneratorClass + ServiceLoader loader, + Class uuidGeneratorClass ) { for (UuidGenerator uuidGenerator : loader) { if (uuidGeneratorClass.equals(uuidGenerator.getClass())) { 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 index 90ec5faa33..c7c37e3f7b 100644 --- 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 @@ -1,2 +1,2 @@ -io.cucumber.core.eventbus.DefaultUuidGenerator -io.cucumber.core.eventbus.FastUuidGenerator +io.cucumber.core.eventbus.RandomUuidGenerator +io.cucumber.core.eventbus.IncrementingUuidGenerator diff --git a/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java similarity index 56% rename from cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java rename to cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java index 9ddf0f7c42..a3fab341f1 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/eventbus/FastUuidGeneratorTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java @@ -11,11 +11,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; -class FastUuidGeneratorTest { +class IncrementingUuidGeneratorTest { @Test void generates_different_non_null_uuids() { // Given - UuidGenerator generator = new FastUuidGenerator(); + UuidGenerator generator = new IncrementingUuidGenerator(); UUID uuid1 = generator.get(); // When @@ -30,8 +30,8 @@ void generates_different_non_null_uuids() { @Test void raises_exception_when_out_of_range() throws NoSuchFieldException, IllegalAccessException { // Given - UuidGenerator generator = new FastUuidGenerator(); - Field counterField = FastUuidGenerator.class.getDeclaredField("counter"); + 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); @@ -40,7 +40,23 @@ void raises_exception_when_out_of_range() throws NoSuchFieldException, IllegalAc CucumberException cucumberException = assertThrows(CucumberException.class, generator::get); // Then - assertThat(cucumberException.getMessage(), Matchers.containsString("Out of FastUuidGenerator capacity")); + 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/DefaultUuidGeneratorTest.java b/cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java similarity index 82% rename from cucumber-core/src/test/java/io/cucumber/core/eventbus/DefaultUuidGeneratorTest.java rename to cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java index 0145d55972..c578c04d1e 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/eventbus/DefaultUuidGeneratorTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/eventbus/RandomUuidGeneratorTest.java @@ -6,11 +6,11 @@ import static org.junit.jupiter.api.Assertions.*; -class DefaultUuidGeneratorTest { +class RandomUuidGeneratorTest { @Test void generates_different_non_null_uuids() { // Given - UuidGenerator generator = new DefaultUuidGenerator(); + UuidGenerator generator = new RandomUuidGenerator(); UUID uuid1 = generator.get(); // When 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 faf5d76fd8..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,7 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.gherkin.Pickle; @@ -79,11 +79,12 @@ void testParseWithObjectFactoryArgument() { @Test void testParseWithUuidGeneratorArgument() { - RuntimeOptionsBuilder optionsBuilder = parser.parse("--uuid-generator", FastUuidGenerator.class.getName()); + RuntimeOptionsBuilder optionsBuilder = parser.parse("--uuid-generator", + IncrementingUuidGenerator.class.getName()); assertNotNull(optionsBuilder); RuntimeOptions options = optionsBuilder.build(); assertNotNull(options); - assertThat(options.getUuidGeneratorClass(), Is.is(equalTo(FastUuidGenerator.class))); + assertThat(options.getUuidGeneratorClass(), Is.is(equalTo(IncrementingUuidGenerator.class))); } @Test 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 bf7c78a997..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,7 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.eventbus.UuidGenerator; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.plugin.HtmlFormatter; @@ -258,7 +258,7 @@ void cannot_create_with_glue_and_extra_glue() { void uuid_generator() { RuntimeOptions runtimeOptions = parser().parse(ClassWithUuidGenerator.class).build(); - assertThat(runtimeOptions.getUuidGeneratorClass(), is(FastUuidGenerator.class)); + assertThat(runtimeOptions.getUuidGeneratorClass(), is(IncrementingUuidGenerator.class)); } @CucumberOptions(snippets = SnippetType.CAMELCASE) @@ -372,7 +372,7 @@ private static class ClassWithGlueAndExtraGlue { // empty } - @CucumberOptions(uuidGenerator = FastUuidGenerator.class) + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) private static class ClassWithUuidGenerator extends ClassWithGlue { // empty } 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 index 23e497bafc..02bb028bbf 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/options/RuntimeOptionsBuilderTest.java @@ -1,6 +1,6 @@ package io.cucumber.core.options; -import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -10,12 +10,13 @@ class RuntimeOptionsBuilderTest { @Test void build() { // Given - RuntimeOptionsBuilder builder = new RuntimeOptionsBuilder().setUuidGeneratorClass(FastUuidGenerator.class); + RuntimeOptionsBuilder builder = new RuntimeOptionsBuilder() + .setUuidGeneratorClass(IncrementingUuidGenerator.class); // When RuntimeOptions runtimeOptions = builder.build(); // Then - assertEquals(FastUuidGenerator.class, runtimeOptions.getUuidGeneratorClass()); + 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/UuidGeneratorServiceLoaderTest.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java index be79209fd4..d04cb24502 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -1,8 +1,8 @@ package io.cucumber.core.runtime; -import io.cucumber.core.eventbus.DefaultUuidGenerator; -import io.cucumber.core.eventbus.FastUuidGenerator; +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.junit.jupiter.api.Test; @@ -18,30 +18,30 @@ class UuidGeneratorServiceLoaderTest { @Test - void shouldLoadDefaultUuidGeneratorService() { + void should_load_RandomUuidGenerator_by_default() { Options options = () -> null; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( UuidGeneratorServiceLoaderTest.class::getClassLoader, options); - assertThat(loader.loadUuidGenerator(), instanceOf(DefaultUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); } @Test - void shouldLoadSelectedUuidGeneratorService() { - Options options = () -> DefaultUuidGenerator.class; + void should_load_selected_UuidGenerator() { + Options options = () -> RandomUuidGenerator.class; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( UuidGeneratorServiceLoaderTest.class::getClassLoader, options); - assertThat(loader.loadUuidGenerator(), instanceOf(DefaultUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); } @Test - void shouldLoadSelectedFastUuidGeneratorService() { - Options options = () -> FastUuidGenerator.class; + void should_load_selected_IncrementingUuidGenerator() { + Options options = () -> IncrementingUuidGenerator.class; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( UuidGeneratorServiceLoaderTest.class::getClassLoader, options); - assertThat(loader.loadUuidGenerator(), instanceOf(FastUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(IncrementingUuidGenerator.class)); } @Test 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 faf5cb365d..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 @@ -177,8 +177,8 @@ public final class Constants { *

* 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 - * #DefaultUuidGenerator are available on the classpath, the - * #DefaultUuidGenerator will be used. + * #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; 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 035c916f82..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,12 +1,11 @@ package io.cucumber.junit.platform.engine; import io.cucumber.core.backend.DefaultObjectFactory; -import io.cucumber.core.eventbus.FastUuidGenerator; +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; @@ -168,9 +167,9 @@ void objectFactory() { void uuidGenerator() { ConfigurationParameters configurationParameters = new MapConfigurationParameters( Constants.UUID_GENERATOR_PROPERTY_NAME, - FastUuidGenerator.class.getName()); + IncrementingUuidGenerator.class.getName()); assertThat(new CucumberEngineOptions(configurationParameters).getUuidGeneratorClass(), - is(FastUuidGenerator.class)); + is(IncrementingUuidGenerator.class)); } } 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 8d3191b8a8..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,8 +1,7 @@ package io.cucumber.junit; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.eventbus.FastUuidGenerator; -import io.cucumber.core.eventbus.UuidGenerator; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,7 +47,7 @@ void testUuidGenerator() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithCustomUuidGenerator.class); assertNotNull(options); - assertEquals(FastUuidGenerator.class, options.uuidGenerator()); + assertEquals(IncrementingUuidGenerator.class, options.uuidGenerator()); } @CucumberOptions() @@ -61,7 +60,7 @@ private static final class ClassWithCustomObjectFactory { } - @CucumberOptions(uuidGenerator = FastUuidGenerator.class) + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) private static final class ClassWithCustomUuidGenerator { } 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 6728c8fa60..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,7 +1,7 @@ package io.cucumber.testng; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.eventbus.FastUuidGenerator; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -47,7 +47,7 @@ void testUuidGenerator() { io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider .getOptions(ClassWithCustomUuidGenerator.class); assertNotNull(options); - assertEquals(FastUuidGenerator.class, options.uuidGenerator()); + assertEquals(IncrementingUuidGenerator.class, options.uuidGenerator()); } @CucumberOptions() @@ -60,7 +60,7 @@ private static final class ClassWithCustomObjectFactory { } - @CucumberOptions(uuidGenerator = FastUuidGenerator.class) + @CucumberOptions(uuidGenerator = IncrementingUuidGenerator.class) private static final class ClassWithCustomUuidGenerator { } From 66acaf7dac1e6e0348269a42f9c161bef1401327 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 15:14:50 +0100 Subject: [PATCH 07/14] fix: improved test coverage and corrected code according to PR comments --- cucumber-core/README.md | 8 ++++---- .../java/io/cucumber/core/eventbus/UuidGenerator.java | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cucumber-core/README.md b/cucumber-core/README.md index 691a541ca0..6e07801ada 100644 --- a/cucumber-core/README.md +++ b/cucumber-core/README.md @@ -90,10 +90,10 @@ Cucumber emits events on an event bus in many cases: 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, 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, single-jvm | ~130 | Reports are generated on a single JVM | +| 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. 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 index 596bd23c81..6557576ee4 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java @@ -1,10 +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 { } From c709234b0730143bdef0b169efc1f26b068cd615 Mon Sep 17 00:00:00 2001 From: U117293 Date: Tue, 14 Mar 2023 15:41:08 +0100 Subject: [PATCH 08/14] fix: added revapi exceptions for junit and testng --- .revapi/api-changes.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.revapi/api-changes.json b/.revapi/api-changes.json index d31c24e69b..4355ce9dd1 100644 --- a/.revapi/api-changes.json +++ b/.revapi/api-changes.json @@ -343,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" } ] } @@ -395,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" } ] } From 7649203075ec55d4527a80cc28bca630164defcd Mon Sep 17 00:00:00 2001 From: U117293 Date: Wed, 15 Mar 2023 17:17:46 +0100 Subject: [PATCH 09/14] fix: added/rewrite test cases and corrected minor issue --- .../runtime/UuidGeneratorServiceLoader.java | 2 +- .../core/runtime/FilteredClassLoader.java | 30 ------ .../ObjectFactoryServiceLoaderTest.java | 73 +++++++++++++- .../runtime/ServiceLoaderTestClassLoader.java | 96 +++++++++++++++++++ .../UuidGeneratorServiceLoaderTest.java | 86 +++++++++++++++-- 5 files changed, 243 insertions(+), 44 deletions(-) delete mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/FilteredClassLoader.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/ServiceLoaderTestClassLoader.java 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 index 07652dac0c..a212009ed3 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -71,7 +71,7 @@ private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader 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); - } - -} 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 63c9bd4a85..8310b4c57d 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,7 +3,12 @@ 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.util.function.Supplier; @@ -36,8 +41,7 @@ void shouldLoadSelectedObjectFactoryService() { @Test void shouldThrowIfDefaultObjectFactoryServiceCouldNotBeLoaded() { Options options = () -> null; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.backend.ObjectFactory"); + Supplier classLoader = () -> new ServiceLoaderTestClassLoader(ObjectFactory.class); ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( classLoader, options); @@ -55,8 +59,7 @@ void shouldThrowIfDefaultObjectFactoryServiceCouldNotBeLoaded() { void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { Options options = () -> NoSuchObjectFactory.class; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.backend.ObjectFactory"); + Supplier classLoader = () -> new ServiceLoaderTestClassLoader(ObjectFactory.class); ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( classLoader, options); @@ -71,7 +74,67 @@ void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { "the classpath?")); } - static class NoSuchObjectFactory implements ObjectFactory { + /** + * Without ObjectFactory configuration and 2 available Object factories + * (including DefaultObjectFactory) => should raise error. + */ + @Test + @Disabled("the test does not pass so either we have a bug, either the test is wrong") + void should_raise_error_when_custom_metainf_contains_more_than_one_objectfactory_default_second_without_option() { + // Given + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + NoSuchObjectFactory.class, + DefaultObjectFactory.class), + options); + + // When + CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadObjectFactory); + + // Then + assertThat(cucumberException.getMessage(), + Matchers.containsString("More than one Cucumber ObjectFactory was found on the classpath")); + } + + /** + * Without ObjectFactory configuration and 2 available Object factories + * (including DefaultObjectFactory) => should raise error. + */ + @Test + @Disabled("the test does not pass so either we have a bug, either the test is wrong") + void should_raise_error_when_custom_metainf_contains_more_than_one_objectfactory_default_first_without_option() { + // Given + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + NoSuchObjectFactory.class), + options); + + // When + CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadObjectFactory); + + // Then + assertThat(cucumberException.getMessage(), + Matchers.containsString("More than one Cucumber ObjectFactory was found on the classpath")); + } + + /** + * Without object factory configuration and 1 available object factory (not + * DefaultObjectFactory) => should select the only available object factory. + */ + @Test + void should_select_available_generator_when_custom_metainf_contains_one_generator_without_option() { + io.cucumber.core.backend.Options options = () -> null; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + NoSuchObjectFactory.class), + options); + assertThat(loader.loadObjectFactory(), instanceOf(NoSuchObjectFactory.class)); + } + + public static class NoSuchObjectFactory implements ObjectFactory { @Override public boolean addClass(Class glueClass) { 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 index d04cb24502..67eb2bad51 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -5,10 +5,10 @@ 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 java.util.function.Supplier; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -47,10 +47,8 @@ void should_load_selected_IncrementingUuidGenerator() { @Test void shouldThrowIfDefaultUuidGeneratorServiceCouldNotBeLoaded() { Options options = () -> null; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.eventbus.UuidGenerator"); UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class), options); CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); @@ -66,10 +64,8 @@ void shouldThrowIfDefaultUuidGeneratorServiceCouldNotBeLoaded() { void shouldThrowIfSelectedUuidGeneratorServiceCouldNotBeLoaded() { Options options = () -> NoSuchUuidGenerator.class; - Supplier classLoader = () -> new FilteredClassLoader( - "META-INF/services/io.cucumber.core.eventbus.UuidGenerator"); UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class), options); CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); @@ -82,7 +78,81 @@ void shouldThrowIfSelectedUuidGeneratorServiceCouldNotBeLoaded() { "the classpath?")); } - static class NoSuchUuidGenerator implements UuidGenerator { + /** + * Without UUID generator configuration and 3 available generators + * (including RandomUuidGenerator as first) => should select the + * RandomUuidGenerator. + */ + @Test + void should_select_default_generator_when_custom_metainf_contains_default_generator_first_without_option() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + NoSuchUuidGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + } + + /** + * Without UUID generator configuration and 3 available generators + * (including RandomUuidGenerator as last) => should select the + * RandomUuidGenerator. + */ + @Test + void should_select_default_generator_when_custom_metainf_contains_default_generator_last_without_option() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + IncrementingUuidGenerator.class, + NoSuchUuidGenerator.class, + RandomUuidGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + } + + /** + * Without UUID generator configuration and 1 available generators (not + * including RandomUuidGenerator) => should select the only available + * UuidGenerator. + */ + @Test + void should_select_available_generator_when_custom_metainf_contains_one_generator_without_option() { + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + NoSuchUuidGenerator.class), + options); + assertThat(loader.loadUuidGenerator(), instanceOf(NoSuchUuidGenerator.class)); + } + + /** + * Without UUID generator configuration and 3 available generators (not + * including RandomUuidGenerator) => should raise error + */ + @Test + void should_raise_error_when_custom_metainf_contains_more_than_one_generator_without_default_generator_without_option() { + // Given + Options options = () -> null; + UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + IncrementingUuidGenerator.class, + NoSuchUuidGenerator.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")); + } + + public static class NoSuchUuidGenerator implements UuidGenerator { + public NoSuchUuidGenerator() { + } + @Override public UUID get() { return null; From 5e3e3a87f74a4fa95630d39e7d74e2e7c3475a92 Mon Sep 17 00:00:00 2001 From: U117293 Date: Fri, 17 Mar 2023 00:05:46 +0100 Subject: [PATCH 10/14] fix: corrected test cases and code according to PR comments --- .../runtime/UuidGeneratorServiceLoader.java | 84 ++++--- .../UuidGeneratorServiceLoaderTest.java | 218 +++++++++++++----- .../runtime/UuidGeneratorServiceLoaderTest.md | 16 ++ 3 files changed, 234 insertions(+), 84 deletions(-) create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md 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 index a212009ed3..5a9c099ec4 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -1,11 +1,14 @@ 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; @@ -48,13 +51,58 @@ UuidGenerator loadUuidGenerator() { private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader loader) { Iterator uuidGenerators = loader.iterator(); - - // Find the first UUID generator - UuidGenerator uuidGenerator = null; - if (uuidGenerators.hasNext()) { - uuidGenerator = uuidGenerators.next(); + /* + * Testcases | # | uuid-generator property | Available services | Result + * | + * |----|---------------------------|----------------------------------- + * --------------------------------------------------|------------------ + * ----------------------------------------------------------------| | 1 + * | undefined | none | exception, no generators available | | 2 | + * undefined | RandomUuidGenerator, IncrementingUuidGenerator | + * RandomUuidGenerator used | | 4 | undefined | RandomUuidGenerator, + * IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | | 6 + * | undefined | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator, YetAnotherGenerator | exception, cucumber couldn't + * decide multiple (non default) generators available | | 11 | undefined + * | OtherGenerator | OtherGenerator used | | 12 | undefined | + * IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | + */ + + // 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) { + // case 6 : we have multiple external generators, which is + // an error + throw new CucumberException(getMultipleUuidGeneratorLogMessage( + Arrays.asList(externalGenerator, uuidGenerator))); + } + externalGenerator = uuidGenerator; + } } - if (uuidGenerator == null) { + + // decide which generator to use + if (externalGenerator != null) { + // cases 4, 11, 12 : we have a single external generator + return externalGenerator; + } else if (randomGenerator != null) { + // case 2: we don't have any external generators, use random if + // available + return randomGenerator; + } else if (incrementingGenerator != null) { + // case 12: we don't have any external generators and no random, use + // incrementing if available + return incrementingGenerator; + } else { + // case 1: we don't have any generators at all, throw an error throw new CucumberException("" + "Could not find any UUID generator.\n" + "\n" + @@ -62,20 +110,6 @@ private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader uuidGenerators) { String factoryNames = Stream.of(uuidGenerators) .map(Object::getClass) .map(Class::getName) @@ -106,11 +140,9 @@ private static String getMultipleUuidGeneratorLogMessage(UuidGenerator... uuidGe "\n" + "Found: " + factoryNames + "\n" + "\n" + - "You may have included, for instance, cucumber-spring AND cucumber-guice as part\n" + - "of your dependencies. When this happens, Cucumber can't decide which to use.\n" + - "In order to enjoy dependency injection features, either remove the unnecessary\n" + - "dependencies from your classpath or use the `cucumber.uuid-generator` property\n" + - "or `@CucumberOptions(uuidGenerator=...)` to select one.\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/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java index 67eb2bad51..f96f57857f 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -15,10 +15,36 @@ 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 should_load_RandomUuidGenerator_by_default() { + void test_case_2() { Options options = () -> null; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( UuidGeneratorServiceLoaderTest.class::getClassLoader, @@ -26,8 +52,12 @@ void should_load_RandomUuidGenerator_by_default() { assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); } + /** + * | 3 | RandomUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator | RandomUuidGenerator used | + */ @Test - void should_load_selected_UuidGenerator() { + void test_case_3() { Options options = () -> RandomUuidGenerator.class; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( UuidGeneratorServiceLoaderTest.class::getClassLoader, @@ -35,118 +65,190 @@ void should_load_selected_UuidGenerator() { assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); } + /** + * | 4 | undefined | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator | OtherGenerator used | + */ @Test - void should_load_selected_IncrementingUuidGenerator() { - Options options = () -> IncrementingUuidGenerator.class; + void test_case_4() { + Options options = () -> null; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( - UuidGeneratorServiceLoaderTest.class::getClassLoader, + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class), options); - assertThat(loader.loadUuidGenerator(), instanceOf(IncrementingUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); } + /** + * | 4bis | undefined | OtherGenerator, RandomUuidGenerator, + * IncrementingUuidGenerator | OtherGenerator used | + */ @Test - void shouldThrowIfDefaultUuidGeneratorServiceCouldNotBeLoaded() { + void test_case_4_bis() { Options options = () -> null; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( - () -> new ServiceLoaderTestClassLoader(UuidGenerator.class), + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + OtherGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.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")); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); } + /** + * | 5 | RandomUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator, OtherGenerator | RandomUuidGenerator used | + */ @Test - void shouldThrowIfSelectedUuidGeneratorServiceCouldNotBeLoaded() { + 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)); + } - Options options = () -> NoSuchUuidGenerator.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), + () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, + IncrementingUuidGenerator.class, + OtherGenerator.class, + YetAnotherGenerator.class), options); - CucumberException exception = assertThrows(CucumberException.class, loader::loadUuidGenerator); - assertThat(exception.getMessage(), is("" + - "Could not find UUID generator io.cucumber.core.runtime.UuidGeneratorServiceLoaderTest$NoSuchUuidGenerator.\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?")); + // When + CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadUuidGenerator); + + // Then + assertThat(cucumberException.getMessage(), + Matchers.containsString("More than one Cucumber UuidGenerator was found on the classpath")); } /** - * Without UUID generator configuration and 3 available generators - * (including RandomUuidGenerator as first) => should select the - * RandomUuidGenerator. + * | 7 | OtherGenerator | RandomUuidGenerator, IncrementingUuidGenerator, + * OtherGenerator, YetAnotherGenerator | OtherGenerator used | */ @Test - void should_select_default_generator_when_custom_metainf_contains_default_generator_first_without_option() { - Options options = () -> null; + void test_case_7() { + Options options = () -> OtherGenerator.class; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, RandomUuidGenerator.class, IncrementingUuidGenerator.class, - NoSuchUuidGenerator.class), + OtherGenerator.class, + YetAnotherGenerator.class), options); - assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); } /** - * Without UUID generator configuration and 3 available generators - * (including RandomUuidGenerator as last) => should select the - * RandomUuidGenerator. + * | 8 | IncrementingUuidGenerator | RandomUuidGenerator, + * IncrementingUuidGenerator, OtherGenerator, YetAnotherGenerator | + * IncrementingUuidGenerator used | */ @Test - void should_select_default_generator_when_custom_metainf_contains_default_generator_last_without_option() { - Options options = () -> null; + void test_case_8() { + Options options = () -> IncrementingUuidGenerator.class; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, + RandomUuidGenerator.class, IncrementingUuidGenerator.class, - NoSuchUuidGenerator.class, - RandomUuidGenerator.class), + OtherGenerator.class, + YetAnotherGenerator.class), options); - assertThat(loader.loadUuidGenerator(), instanceOf(RandomUuidGenerator.class)); + 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)); } /** - * Without UUID generator configuration and 1 available generators (not - * including RandomUuidGenerator) => should select the only available - * UuidGenerator. + * | 10 | OtherGenerator | none | exception, generator OtherGenerator not + * available | */ @Test - void should_select_available_generator_when_custom_metainf_contains_one_generator_without_option() { + 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, - NoSuchUuidGenerator.class), + OtherGenerator.class), options); - assertThat(loader.loadUuidGenerator(), instanceOf(NoSuchUuidGenerator.class)); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); } /** - * Without UUID generator configuration and 3 available generators (not - * including RandomUuidGenerator) => should raise error + * | 12 | undefined | IncrementingUuidGenerator, OtherGenerator | + * OtherGenerator used | */ @Test - void should_raise_error_when_custom_metainf_contains_more_than_one_generator_without_default_generator_without_option() { - // Given + void test_case_12() { Options options = () -> null; UuidGeneratorServiceLoader loader = new UuidGeneratorServiceLoader( () -> new ServiceLoaderTestClassLoader(UuidGenerator.class, IncrementingUuidGenerator.class, - NoSuchUuidGenerator.class), + OtherGenerator.class), options); - // When - CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadUuidGenerator); + assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); + } - // Then - assertThat(cucumberException.getMessage(), - Matchers.containsString("More than one Cucumber UuidGenerator was found on the classpath")); + public static class OtherGenerator implements UuidGenerator { + @Override + public UUID get() { + return null; + } + } + + public static class YetAnotherGenerator implements UuidGenerator { + @Override + public UUID get() { + return null; + } } public static class NoSuchUuidGenerator implements UuidGenerator { 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..1aa66725f0 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md @@ -0,0 +1,16 @@ +# 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 | From 6fe1ca54fe2f03abd1e59692ef866be0827c1791 Mon Sep 17 00:00:00 2001 From: U117293 Date: Fri, 17 Mar 2023 00:19:11 +0100 Subject: [PATCH 11/14] fix: cleanup ugly code comments --- .../runtime/UuidGeneratorServiceLoader.java | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) 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 index 5a9c099ec4..eab1200f31 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -51,22 +51,6 @@ UuidGenerator loadUuidGenerator() { private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader loader) { Iterator uuidGenerators = loader.iterator(); - /* - * Testcases | # | uuid-generator property | Available services | Result - * | - * |----|---------------------------|----------------------------------- - * --------------------------------------------------|------------------ - * ----------------------------------------------------------------| | 1 - * | undefined | none | exception, no generators available | | 2 | - * undefined | RandomUuidGenerator, IncrementingUuidGenerator | - * RandomUuidGenerator used | | 4 | undefined | RandomUuidGenerator, - * IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | | 6 - * | undefined | RandomUuidGenerator, IncrementingUuidGenerator, - * OtherGenerator, YetAnotherGenerator | exception, cucumber couldn't - * decide multiple (non default) generators available | | 11 | undefined - * | OtherGenerator | OtherGenerator used | | 12 | undefined | - * IncrementingUuidGenerator, OtherGenerator | OtherGenerator used | - */ // categorize the UUID generators (random, incrementing or external) UuidGenerator randomGenerator = null; @@ -80,8 +64,7 @@ private static UuidGenerator loadSingleUuidGeneratorOrDefault(ServiceLoader Date: Fri, 17 Mar 2023 15:00:20 +0100 Subject: [PATCH 12/14] feat: improved test coverage --- .../ObjectFactoryServiceLoaderTest.java | 158 ++++++++++++------ .../runtime/ObjectFactoryServiceLoaderTest.md | 18 ++ 2 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 cucumber-core/src/test/java/io/cucumber/core/runtime/ObjectFactoryServiceLoaderTest.md 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 8310b4c57d..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 @@ -13,13 +13,40 @@ 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; @@ -29,6 +56,9 @@ void shouldLoadDefaultObjectFactoryService() { assertThat(loader.loadObjectFactory(), instanceOf(DefaultObjectFactory.class)); } + /** + * Test case #3 + */ @Test void shouldLoadSelectedObjectFactoryService() { Options options = () -> DefaultObjectFactory.class; @@ -38,103 +68,120 @@ void shouldLoadSelectedObjectFactoryService() { assertThat(loader.loadObjectFactory(), instanceOf(DefaultObjectFactory.class)); } + /** + * Test-case #4 + */ @Test - void shouldThrowIfDefaultObjectFactoryServiceCouldNotBeLoaded() { - Options options = () -> null; - Supplier classLoader = () -> new ServiceLoaderTestClassLoader(ObjectFactory.class); + void test_case_4() { + io.cucumber.core.backend.Options options = () -> null; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + DefaultObjectFactory.class, + OtherFactory.class), 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")); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); } + /** + * Test-case #4 bis (reverse order) + */ @Test - void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { - - Options options = () -> NoSuchObjectFactory.class; - Supplier classLoader = () -> new ServiceLoaderTestClassLoader(ObjectFactory.class); + void test_case_4_with_services_in_reverse_order() { + io.cucumber.core.backend.Options options = () -> null; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( - classLoader, + () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, + OtherFactory.class, + DefaultObjectFactory.class), options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); + } - CucumberException exception = assertThrows(CucumberException.class, loader::loadObjectFactory); - assertThat(exception.getMessage(), is("" + - "Could not find object factory io.cucumber.core.runtime.ObjectFactoryServiceLoaderTest$NoSuchObjectFactory.\n" - + - "\n" + - "Cucumber uses SPI to discover object factory implementations.\n" + - "Has the class been registered with SPI and is it available on\n" + - "the classpath?")); + /** + * 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)); } /** - * Without ObjectFactory configuration and 2 available Object factories - * (including DefaultObjectFactory) => should raise error. + * Test case #6 */ @Test - @Disabled("the test does not pass so either we have a bug, either the test is wrong") - void should_raise_error_when_custom_metainf_contains_more_than_one_objectfactory_default_second_without_option() { + void test_case_6() { // Given - io.cucumber.core.backend.Options options = () -> null; + Options options = () -> null; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, - NoSuchObjectFactory.class, - DefaultObjectFactory.class), + DefaultObjectFactory.class, + OtherFactory.class, + YetAnotherFactory.class), options); // When - CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadObjectFactory); + CucumberException exception = assertThrows(CucumberException.class, loader::loadObjectFactory); // Then - assertThat(cucumberException.getMessage(), - Matchers.containsString("More than one Cucumber ObjectFactory was found on the classpath")); + assertThat(exception.getMessage(), + containsString("More than one Cucumber ObjectFactory was found on the classpath")); } /** - * Without ObjectFactory configuration and 2 available Object factories - * (including DefaultObjectFactory) => should raise error. + * Test-case #7 */ @Test - @Disabled("the test does not pass so either we have a bug, either the test is wrong") - void should_raise_error_when_custom_metainf_contains_more_than_one_objectfactory_default_first_without_option() { - // Given - io.cucumber.core.backend.Options options = () -> null; + void test_case_7() { + io.cucumber.core.backend.Options options = () -> OtherFactory.class; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, DefaultObjectFactory.class, - NoSuchObjectFactory.class), + OtherFactory.class, + YetAnotherFactory.class), options); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); + } - // When - CucumberException cucumberException = assertThrows(CucumberException.class, loader::loadObjectFactory); + /** + * Test case #8 + */ + @Test + void shouldThrowIfSelectedObjectFactoryServiceCouldNotBeLoaded() { - // Then - assertThat(cucumberException.getMessage(), - Matchers.containsString("More than one Cucumber ObjectFactory was found on the classpath")); + Options options = () -> OtherFactory.class; + ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( + () -> 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$OtherFactory.\n" + + + "\n" + + "Cucumber uses SPI to discover object factory implementations.\n" + + "Has the class been registered with SPI and is it available on\n" + + "the classpath?")); } /** - * Without object factory configuration and 1 available object factory (not - * DefaultObjectFactory) => should select the only available object factory. + * Test-case #9 */ @Test - void should_select_available_generator_when_custom_metainf_contains_one_generator_without_option() { + void test_case_9() { io.cucumber.core.backend.Options options = () -> null; ObjectFactoryServiceLoader loader = new ObjectFactoryServiceLoader( () -> new ServiceLoaderTestClassLoader(ObjectFactory.class, - NoSuchObjectFactory.class), + OtherFactory.class), options); - assertThat(loader.loadObjectFactory(), instanceOf(NoSuchObjectFactory.class)); + assertThat(loader.loadObjectFactory(), instanceOf(OtherFactory.class)); } - public static class NoSuchObjectFactory implements ObjectFactory { + public static class FakeObjectFactory implements ObjectFactory { @Override public boolean addClass(Class glueClass) { @@ -158,4 +205,9 @@ public void stop() { } + 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 From 6d3b720950902e871e622c45696029f44f849f8e Mon Sep 17 00:00:00 2001 From: U117293 Date: Fri, 17 Mar 2023 15:53:53 +0100 Subject: [PATCH 13/14] feat: improved test coverage --- .../UuidGeneratorServiceLoaderTest.java | 24 ++++++++------- .../runtime/UuidGeneratorServiceLoaderTest.md | 29 ++++++++++--------- 2 files changed, 29 insertions(+), 24 deletions(-) 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 index f96f57857f..5843bbeedc 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.java @@ -237,24 +237,28 @@ void test_case_12() { assertThat(loader.loadUuidGenerator(), instanceOf(OtherGenerator.class)); } - public static class OtherGenerator implements UuidGenerator { - @Override - public UUID get() { - return null; - } + /** + * | 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 YetAnotherGenerator implements UuidGenerator { + public static class OtherGenerator implements UuidGenerator { @Override public UUID get() { return null; } } - public static class NoSuchUuidGenerator implements UuidGenerator { - public NoSuchUuidGenerator() { - } - + 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 index 1aa66725f0..34cad9beb1 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md +++ b/cucumber-core/src/test/java/io/cucumber/core/runtime/UuidGeneratorServiceLoaderTest.md @@ -1,16 +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 | +| # | 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 | From 4c2727aea21b0ae9959571f5306a523716309f1b Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Fri, 17 Mar 2023 17:01:28 +0100 Subject: [PATCH 14/14] squash! feat: added UUID generator selection through SPI for https://github.com/cucumber/cucumber-jvm/issues/2698 --- .../io/cucumber/core/runtime/UuidGeneratorServiceLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index eab1200f31..d8fbb2baf2 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java @@ -122,7 +122,7 @@ private static String getMultipleUuidGeneratorLogMessage(List uui "\n" + "Found: " + factoryNames + "\n" + "\n" + - "You can either remove the unnecessary SPI dependencies from your classpath \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"; }