Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Add faster UUID generator selectable through SPI #2703

Merged
merged 10 commits into from
Apr 6, 2023
24 changes: 24 additions & 0 deletions .revapi/api-changes.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@
"code": "java.method.finalMethodAddedToNonFinalClass",
"new": "method java.lang.Long io.cucumber.core.internal.com.fasterxml.jackson.databind.deser.std.StdDeserializer<T>::_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<? extends io.cucumber.core.eventbus.UuidGenerator> io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions::uuidGenerator()",
"justification": "Internal API"
},
{
"ignore": true,
"code": "java.method.addedToInterface",
"new": "method java.lang.Class<? extends io.cucumber.core.eventbus.UuidGenerator> io.cucumber.core.runner.Options::getUuidGeneratorClass()",
"justification": "Internal API"
}
]
}
Expand Down Expand Up @@ -331,6 +343,12 @@
"code": "java.method.defaultMethodAddedToInterface",
"new": "method java.util.Set<org.testng.ITestNGMethod> 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"
}
]
}
Expand Down Expand Up @@ -383,6 +401,12 @@
"new": "method int org.junit.platform.engine.ConfigurationParameters::size()",
"annotation": "@org.apiguardian.api.API(status = org.apiguardian.api.API.Status.DEPRECATED, since = \"1.9\")",
"justification": "API consumed from JUnit 5"
},
{
"ignore": true,
"code": "java.class.externalClassExposedInAPI",
"new": "interface io.cucumber.core.eventbus.UuidGenerator",
"justification": "Part of cucumber API"
}
]
}
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- [Core] Improved event bus performance using UUID generator selectable through SPI ([#2703](https://github.com/cucumber/cucumber-jvm/pull/2703) Julien Kronegg)

## [7.11.1] - 2023-01-27
### Added
- [Core] Warn when `cucumber.options` is used ([#2685](https://github.com/cucumber/cucumber-jvm/pull/2685) M.P. Korstanje)
Expand Down
2 changes: 1 addition & 1 deletion compatibility/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>cucumber-jvm</artifactId>
<groupId>io.cucumber</groupId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion cucumber-archetype/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</parent>

<artifactId>cucumber-archetype</artifactId>
Expand Down
40 changes: 20 additions & 20 deletions cucumber-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<artifactId>cucumber-jvm</artifactId>
<groupId>io.cucumber</groupId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
Expand Down Expand Up @@ -63,97 +63,97 @@
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-cdi2</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>datatable</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>datatable-matchers</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-deltaspike</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>docstring</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-gherkin</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-gherkin-messages</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-guice</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jakarta-cdi</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-openejb</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-plugin</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
Expand Down
2 changes: 1 addition & 1 deletion cucumber-cdi2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</parent>

<artifactId>cucumber-cdi2</artifactId>
Expand Down
20 changes: 20 additions & 0 deletions cucumber-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -79,6 +82,23 @@ They are respectively responsible for discovering glue classes, registering
step definitions, and creating instances of said glue classes. Backend and
object factory implementations are discovered via SPI.

## Event bus ##

Cucumber emits events on an event bus in many cases:
- during the feature file parsing
- when the test scenarios are executed

An event has a UUID. The UUID generator can be configured using the `cucumber.uuid-generator` property:

| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example |
|-----------------------------------------------------|-----------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| io.cucumber.core.eventbus.RandomUuidGenerator | Thread-safe, collision-free, multi-jvm | ~1 | Reports may be generated on different JVMs at the same time. A typical example would be one suite that tests against Firefox and another against Safari. The exact browser is configured through a property. These are then executed concurrently on different Gitlab runners. |
| io.cucumber.core.eventbus.IncrementingUuidGenerator | Thread-safe, collision-free, single-jvm | ~130 | Reports are generated on a single JVM |

The performance gain on real project depend on the feature size.

When not specified, the `RandomUuidGenerator` is used.

## Plugin ##

By implementing the Plugin interface classes can listen to execution events
Expand Down
2 changes: 1 addition & 1 deletion cucumber-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>7.11.2-SNAPSHOT</version>
<version>7.12.0-SNAPSHOT</version>
</parent>

<artifactId>cucumber-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
}
Original file line number Diff line number Diff line change
@@ -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.
jkronegg marked this conversation as resolved.
Show resolved Hide resolved
*/
public class IncrementingUuidGenerator implements UuidGenerator {
jkronegg marked this conversation as resolved.
Show resolved Hide resolved
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();
jkronegg marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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() {
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.cucumber.core.eventbus;

public interface Options {

Class<? extends UuidGenerator> getUuidGeneratorClass();

}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.cucumber.core.eventbus;

import org.apiguardian.api.API;

import java.util.UUID;
import java.util.function.Supplier;

/**
* SPI (Service Provider Interface) to generate UUIDs.
*/
@API(status = API.Status.STABLE)
public interface UuidGenerator extends Supplier<UUID> {
jkronegg marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -167,6 +169,9 @@ private RuntimeOptionsBuilder parse(List<String> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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
Expand Down
Loading