From 2caea1f4b15b9f43f0b38a90190b0b34082176b0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 21 Sep 2024 13:22:28 +0200 Subject: [PATCH] [Core] Support custom UUID generators in test runners With #2703 a faster UUID generator was introduced. And while the configuration options were added, they were not actually used by `cucumber-junit`, `cucumber-junit-platform-engine` and `cucumber-testng`. --- cucumber-core/README.md | 8 ++++--- .../eventbus/IncrementingUuidGenerator.java | 24 +++++++++---------- .../runtime/UuidGeneratorServiceLoader.java | 2 +- cucumber-junit-platform-engine/README.md | 4 ++++ .../CucumberEngineExecutionContext.java | 6 +++-- .../main/java/io/cucumber/junit/Cucumber.java | 9 ++++--- .../cucumber/testng/TestNGCucumberRunner.java | 7 +++--- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/cucumber-core/README.md b/cucumber-core/README.md index 050a49f067..3a973e6f27 100644 --- a/cucumber-core/README.md +++ b/cucumber-core/README.md @@ -53,7 +53,8 @@ cucumber.plugin= # comma separated plugin strings. cucumber.object-factory= # object factory class name. # example: com.example.MyObjectFactory -cucumber.uuid-generator= # UUID generator class name. +cucumber.uuid-generator # uuid generator class name of a registered service provider. + # default: io.cucumber.core.eventbus.RandomUuidGenerator # example: com.example.MyUuidGenerator cucumber.publish.enabled # true or false. default: false @@ -89,12 +90,13 @@ 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: +An event has a UUID. The UUID generator can be configured using the +`cucumber.uuid-generator` property: | UUID generator | Features | Performance [Millions UUID/second] | Typical usage example | |-----------------------------------------------------|-----------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | io.cucumber.core.eventbus.RandomUuidGenerator | Thread-safe, collision-free, multi-jvm | ~1 | Reports may be generated on different JVMs at the same time. A typical example would be one suite that tests against Firefox and another against Safari. The exact browser is configured through a property. These are then executed concurrently on different Gitlab runners. | -| io.cucumber.core.eventbus.IncrementingUuidGenerator | Thread-safe, collision-free, single-jvm | ~130 | Reports are generated on a single JVM | +| io.cucumber.core.eventbus.IncrementingUuidGenerator | Thread-safe, collision-free, single-jvm | ~130 | Reports are generated on a single JVM in a single execution of Cucumber. | The performance gain on real projects depends on the feature size. 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 index d403ece897..04bdd43644 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java +++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java @@ -10,20 +10,20 @@ * 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. - * - * Properties: - * - thread-safe - * - collision-free in the same classloader - * - almost collision-free in different classloaders / JVMs - * - UUIDs generated using the instances from the same classloader are sortable - * - * UUID version 8 (custom) / variant 2 ... - * + *

+ * Properties: - thread-safe - collision-free in the same classloader - almost + * collision-free in different classloaders / JVMs - UUIDs generated using the + * instances from the same classloader are sortable + *

+ * UUID + * version 8 (custom) / variant 2 + * + *

  * |       40 bits      |      8 bits    |  4 bits |    12 bits    |  2 bits | 62 bits |
  * | -------------------| -------------- | ------- | ------------- | ------- | ------- |
  * | LSBs of epoch-time | sessionCounter | version | classloaderId | variant | counter |
- * 
+ * 
*/ public class IncrementingUuidGenerator implements UuidGenerator { /** @@ -84,7 +84,7 @@ public class IncrementingUuidGenerator implements UuidGenerator { * classloaderId which produces about 1% collision rate on the * classloaderId, and thus can have UUID collision if the epoch-time, * session counter and counter have the same values). - * + * * @param classloaderId the new classloaderId (only the least significant 12 * bits are used) * @see IncrementingUuidGenerator#classloaderId 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 d8fbb2baf2..30248fbf1d 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 @@ -38,7 +38,7 @@ public UuidGeneratorServiceLoader(Supplier classLoaderSupplier, Opt this.options = requireNonNull(options); } - UuidGenerator loadUuidGenerator() { + public UuidGenerator loadUuidGenerator() { Class objectFactoryClass = options.getUuidGeneratorClass(); ClassLoader classLoader = classLoaderSupplier.get(); ServiceLoader loader = ServiceLoader.load(UuidGenerator.class, classLoader); diff --git a/cucumber-junit-platform-engine/README.md b/cucumber-junit-platform-engine/README.md index e9a55b93cf..8e880f2ebf 100644 --- a/cucumber-junit-platform-engine/README.md +++ b/cucumber-junit-platform-engine/README.md @@ -373,6 +373,10 @@ cucumber.junit-platform.naming-strategy.long.example-name= # number or pickl cucumber.plugin= # comma separated plugin strings. # example: pretty, json:path/to/report.json + +cucumber.uuid-generator # uuid generator class name of a registered service provider. + # default: io.cucumber.core.eventbus.RandomUuidGenerator + # example: com.example.MyUuidGenerator cucumber.object-factory= # object factory class name. # example: com.example.MyObjectFactory diff --git a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java index d8a5307c81..447b98d279 100644 --- a/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java +++ b/cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java @@ -19,12 +19,12 @@ import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runtime.UuidGeneratorServiceLoader; import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; import java.time.Clock; -import java.util.UUID; import java.util.function.Supplier; import static io.cucumber.core.runtime.SynchronizedEventBus.synchronize; @@ -48,8 +48,10 @@ CucumberEngineOptions getOptions() { private CucumberExecutionContext createCucumberExecutionContext() { Supplier classLoader = CucumberEngineExecutionContext.class::getClassLoader; + UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, options); + EventBus bus = synchronize( + new TimeServiceEventBus(Clock.systemUTC(), uuidGeneratorServiceLoader.loadUuidGenerator())); ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(classLoader, options); - EventBus bus = synchronize(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID)); Plugins plugins = new Plugins(new PluginFactory(), options); ExitStatus exitStatus = new ExitStatus(options); plugins.addPlugin(exitStatus); diff --git a/cucumber-junit/src/main/java/io/cucumber/junit/Cucumber.java b/cucumber-junit/src/main/java/io/cucumber/junit/Cucumber.java index f67376e141..d849d3124a 100644 --- a/cucumber-junit/src/main/java/io/cucumber/junit/Cucumber.java +++ b/cucumber-junit/src/main/java/io/cucumber/junit/Cucumber.java @@ -23,6 +23,7 @@ import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runtime.UuidGeneratorServiceLoader; import org.apiguardian.api.API; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -38,7 +39,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; @@ -146,11 +146,14 @@ public Cucumber(Class clazz) throws InitializationError { .parse(CucumberProperties.fromSystemProperties()) .build(junitEnvironmentOptions); - this.bus = synchronize(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID)); + Supplier classLoader = ClassLoaders::getDefaultClassLoader; + UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, + runtimeOptions); + this.bus = synchronize( + new TimeServiceEventBus(Clock.systemUTC(), uuidGeneratorServiceLoader.loadUuidGenerator())); // Parse the features early. Don't proceed when there are lexer errors FeatureParser parser = new FeatureParser(bus::generateId); - Supplier classLoader = ClassLoaders::getDefaultClassLoader; FeaturePathFeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(classLoader, runtimeOptions, parser); List features = featureSupplier.get(); diff --git a/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java b/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java index 586095fb83..fde8876ba8 100644 --- a/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java +++ b/cucumber-testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java @@ -23,11 +23,11 @@ import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier; import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.core.runtime.UuidGeneratorServiceLoader; import org.apiguardian.api.API; import java.time.Clock; import java.util.List; -import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; @@ -98,9 +98,10 @@ public TestNGCucumberRunner(Class clazz, CucumberPropertiesProvider propertie .enablePublishPlugin() .build(environmentOptions); - EventBus bus = synchronize(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID)); - Supplier classLoader = ClassLoaders::getDefaultClassLoader; + UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, runtimeOptions); + EventBus bus = synchronize(new TimeServiceEventBus(Clock.systemUTC(), uuidGeneratorServiceLoader.loadUuidGenerator())); + FeatureParser parser = new FeatureParser(bus::generateId); FeaturePathFeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(classLoader, runtimeOptions, parser);