Skip to content

Commit 8029e93

Browse files
authored
[Core] Support custom UUID generators in test runners (#2926)
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`.
1 parent c6c2d07 commit 8029e93

File tree

13 files changed

+96
-40
lines changed

13 files changed

+96
-40
lines changed

.revapi/api-changes.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@
6969
}
7070
}
7171
],
72+
"7.20.0": [
73+
{
74+
"extension": "revapi.differences",
75+
"id": "intentional-api-changes",
76+
"ignore": true,
77+
"configuration": {
78+
"differences": [
79+
{
80+
"ignore": true,
81+
"code": "java.method.visibilityIncreased",
82+
"old": "method io.cucumber.core.eventbus.UuidGenerator io.cucumber.core.runtime.UuidGeneratorServiceLoader::loadUuidGenerator()",
83+
"new": "method io.cucumber.core.eventbus.UuidGenerator io.cucumber.core.runtime.UuidGeneratorServiceLoader::loadUuidGenerator()",
84+
"oldVisibility": "package",
85+
"newVisibility": "public",
86+
"justification": "Expose internal API to other internal components"
87+
}
88+
]
89+
}
90+
}
91+
],
7292
"internal": [
7393
{
7494
"extension": "revapi.differences",

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Added
14+
- [JUnit Platform Engine] Enable use of custom UUID generators ([#2926](https://github.com/cucumber/cucumber-jvm/pull/2926) M.P. Korstanje)
15+
- [JUnit] Enable use of custom UUID generators ([#2926](https://github.com/cucumber/cucumber-jvm/pull/2926) M.P. Korstanje)
16+
- [TestNG] Enable use of custom UUID generators ([#2926](https://github.com/cucumber/cucumber-jvm/pull/2926) M.P. Korstanje)
17+
18+
### Fixed
19+
- [Core] Use custom UUID generators for hooks ([#2926](https://github.com/cucumber/cucumber-jvm/pull/2926) M.P. Korstanje)
20+
1321

1422
## [7.19.0] - 2024-09-19
1523
### Changed

cucumber-core/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ cucumber.plugin= # comma separated plugin strings.
5353
cucumber.object-factory= # object factory class name.
5454
# example: com.example.MyObjectFactory
5555
56-
cucumber.uuid-generator= # UUID generator class name.
56+
cucumber.uuid-generator # uuid generator class name of a registered service provider.
57+
# default: io.cucumber.core.eventbus.RandomUuidGenerator
5758
# example: com.example.MyUuidGenerator
5859
5960
cucumber.publish.enabled # true or false. default: false
@@ -89,12 +90,13 @@ Cucumber emits events on an event bus in many cases:
8990
- during the feature file parsing
9091
- when the test scenarios are executed
9192

92-
An event has a UUID. The UUID generator can be configured using the `cucumber.uuid-generator` property:
93+
An event has a UUID. The UUID generator can be configured using the
94+
`cucumber.uuid-generator` property:
9395

9496
| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example |
9597
|-----------------------------------------------------|-----------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
9698
| 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. |
97-
| io.cucumber.core.eventbus.IncrementingUuidGenerator | Thread-safe, collision-free, single-jvm | ~130 | Reports are generated on a single JVM |
99+
| 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. |
98100

99101
The performance gain on real projects depends on the feature size.
100102

cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@
1010
* Thread-safe and collision-free UUID generator for single JVM. This is a
1111
* sequence generator and each instance has its own counter. This generator is
1212
* about 100 times faster than #RandomUuidGenerator.
13-
*
14-
* Properties:
15-
* - thread-safe
16-
* - collision-free in the same classloader
17-
* - almost collision-free in different classloaders / JVMs
18-
* - UUIDs generated using the instances from the same classloader are sortable
19-
*
20-
* UUID version 8 (custom) / variant 2 <a href=
21-
* "https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-8">...</a>
22-
* <!-- @formatter:off -->
13+
* <p>
14+
* Properties: - thread-safe - collision-free in the same classloader - almost
15+
* collision-free in different classloaders / JVMs - UUIDs generated using the
16+
* instances from the same classloader are sortable
17+
* <p>
18+
* <a href=
19+
* "https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-8">UUID
20+
* version 8 (custom) / variant 2 </a>
21+
*
22+
* <pre>
2323
* | 40 bits | 8 bits | 4 bits | 12 bits | 2 bits | 62 bits |
2424
* | -------------------| -------------- | ------- | ------------- | ------- | ------- |
2525
* | LSBs of epoch-time | sessionCounter | version | classloaderId | variant | counter |
26-
* <!-- @formatter:on -->
26+
* </pre>
2727
*/
2828
public class IncrementingUuidGenerator implements UuidGenerator {
2929
/**
@@ -84,7 +84,7 @@ public class IncrementingUuidGenerator implements UuidGenerator {
8484
* classloaderId which produces about 1% collision rate on the
8585
* classloaderId, and thus can have UUID collision if the epoch-time,
8686
* session counter and counter have the same values).
87-
*
87+
*
8888
* @param classloaderId the new classloaderId (only the least significant 12
8989
* bits are used)
9090
* @see IncrementingUuidGenerator#classloaderId

cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,25 +105,25 @@ public void addStepDefinition(StepDefinition stepDefinition) {
105105

106106
@Override
107107
public void addBeforeHook(HookDefinition hookDefinition) {
108-
beforeHooks.add(CoreHookDefinition.create(hookDefinition));
108+
beforeHooks.add(CoreHookDefinition.create(hookDefinition, bus::generateId));
109109
beforeHooks.sort(HOOK_ORDER_ASCENDING);
110110
}
111111

112112
@Override
113113
public void addAfterHook(HookDefinition hookDefinition) {
114-
afterHooks.add(CoreHookDefinition.create(hookDefinition));
114+
afterHooks.add(CoreHookDefinition.create(hookDefinition, bus::generateId));
115115
afterHooks.sort(HOOK_ORDER_ASCENDING);
116116
}
117117

118118
@Override
119119
public void addBeforeStepHook(HookDefinition hookDefinition) {
120-
beforeStepHooks.add(CoreHookDefinition.create(hookDefinition));
120+
beforeStepHooks.add(CoreHookDefinition.create(hookDefinition, bus::generateId));
121121
beforeStepHooks.sort(HOOK_ORDER_ASCENDING);
122122
}
123123

124124
@Override
125125
public void addAfterStepHook(HookDefinition hookDefinition) {
126-
afterStepHooks.add(CoreHookDefinition.create(hookDefinition));
126+
afterStepHooks.add(CoreHookDefinition.create(hookDefinition, bus::generateId));
127127
afterStepHooks.sort(HOOK_ORDER_ASCENDING);
128128
}
129129

cucumber-core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.List;
1212
import java.util.Optional;
1313
import java.util.UUID;
14+
import java.util.function.Supplier;
1415

1516
import static java.util.Objects.requireNonNull;
1617

@@ -33,13 +34,13 @@ private CoreHookDefinition(UUID id, HookDefinition delegate) {
3334
}
3435
}
3536

36-
static CoreHookDefinition create(HookDefinition hookDefinition) {
37+
static CoreHookDefinition create(HookDefinition hookDefinition, Supplier<UUID> uuidGenerator) {
3738
// Ideally we would avoid this by keeping the scenario scoped
3839
// glue in a different bucket from the globally scoped glue.
3940
if (hookDefinition instanceof ScenarioScoped) {
40-
return new ScenarioScopedCoreHookDefinition(hookDefinition);
41+
return new ScenarioScopedCoreHookDefinition(uuidGenerator.get(), hookDefinition);
4142
}
42-
return new CoreHookDefinition(UUID.randomUUID(), hookDefinition);
43+
return new CoreHookDefinition(uuidGenerator.get(), hookDefinition);
4344
}
4445

4546
void execute(TestCaseState scenario) {
@@ -72,8 +73,8 @@ String getTagExpression() {
7273

7374
static class ScenarioScopedCoreHookDefinition extends CoreHookDefinition implements ScenarioScoped {
7475

75-
private ScenarioScopedCoreHookDefinition(HookDefinition delegate) {
76-
super(UUID.randomUUID(), delegate);
76+
private ScenarioScopedCoreHookDefinition(UUID id, HookDefinition delegate) {
77+
super(id, delegate);
7778
}
7879

7980
@Override

cucumber-core/src/main/java/io/cucumber/core/runtime/UuidGeneratorServiceLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public UuidGeneratorServiceLoader(Supplier<ClassLoader> classLoaderSupplier, Opt
3838
this.options = requireNonNull(options);
3939
}
4040

41-
UuidGenerator loadUuidGenerator() {
41+
public UuidGenerator loadUuidGenerator() {
4242
Class<? extends UuidGenerator> objectFactoryClass = options.getUuidGeneratorClass();
4343
ClassLoader classLoader = classLoaderSupplier.get();
4444
ServiceLoader<UuidGenerator> loader = ServiceLoader.load(UuidGenerator.class, classLoader);

cucumber-junit-platform-engine/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@ cucumber.junit-platform.naming-strategy.long.example-name= # number or pickl
373373
374374
cucumber.plugin= # comma separated plugin strings.
375375
# example: pretty, json:path/to/report.json
376+
377+
cucumber.uuid-generator # uuid generator class name of a registered service provider.
378+
# default: io.cucumber.core.eventbus.RandomUuidGenerator
379+
# example: com.example.MyUuidGenerator
376380
377381
cucumber.object-factory= # object factory class name.
378382
# example: com.example.MyObjectFactory

cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
import io.cucumber.core.runtime.ThreadLocalObjectFactorySupplier;
2020
import io.cucumber.core.runtime.ThreadLocalRunnerSupplier;
2121
import io.cucumber.core.runtime.TimeServiceEventBus;
22+
import io.cucumber.core.runtime.UuidGeneratorServiceLoader;
2223
import org.apiguardian.api.API;
2324
import org.junit.platform.engine.ConfigurationParameters;
2425
import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;
2526

2627
import java.time.Clock;
27-
import java.util.UUID;
2828
import java.util.function.Supplier;
2929

3030
import static io.cucumber.core.runtime.SynchronizedEventBus.synchronize;
@@ -48,8 +48,10 @@ CucumberEngineOptions getOptions() {
4848

4949
private CucumberExecutionContext createCucumberExecutionContext() {
5050
Supplier<ClassLoader> classLoader = CucumberEngineExecutionContext.class::getClassLoader;
51+
UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, options);
52+
EventBus bus = synchronize(
53+
new TimeServiceEventBus(Clock.systemUTC(), uuidGeneratorServiceLoader.loadUuidGenerator()));
5154
ObjectFactoryServiceLoader objectFactoryServiceLoader = new ObjectFactoryServiceLoader(classLoader, options);
52-
EventBus bus = synchronize(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID));
5355
Plugins plugins = new Plugins(new PluginFactory(), options);
5456
ExitStatus exitStatus = new ExitStatus(options);
5557
plugins.addPlugin(exitStatus);

cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/FeatureResolver.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.cucumber.junit.platform.engine;
22

3+
import io.cucumber.core.eventbus.UuidGenerator;
34
import io.cucumber.core.feature.FeatureIdentifier;
45
import io.cucumber.core.feature.FeatureParser;
56
import io.cucumber.core.feature.FeatureWithLines;
@@ -9,6 +10,7 @@
910
import io.cucumber.core.logging.LoggerFactory;
1011
import io.cucumber.core.resource.ClassLoaders;
1112
import io.cucumber.core.resource.ResourceScanner;
13+
import io.cucumber.core.runtime.UuidGeneratorServiceLoader;
1214
import io.cucumber.junit.platform.engine.NodeDescriptor.ExamplesDescriptor;
1315
import io.cucumber.junit.platform.engine.NodeDescriptor.PickleDescriptor;
1416
import io.cucumber.junit.platform.engine.NodeDescriptor.RuleDescriptor;
@@ -28,8 +30,8 @@
2830

2931
import java.net.URI;
3032
import java.util.List;
31-
import java.util.UUID;
3233
import java.util.function.Predicate;
34+
import java.util.function.Supplier;
3335
import java.util.stream.Stream;
3436

3537
import static java.util.Comparator.comparing;
@@ -38,11 +40,7 @@ final class FeatureResolver {
3840

3941
private static final Logger log = LoggerFactory.getLogger(FeatureResolver.class);
4042

41-
private final CachingFeatureParser featureParser = new CachingFeatureParser(new FeatureParser(UUID::randomUUID));
42-
private final ResourceScanner<Feature> featureScanner = new ResourceScanner<>(
43-
ClassLoaders::getDefaultClassLoader,
44-
FeatureIdentifier::isFeature,
45-
featureParser::parseResource);
43+
private final ResourceScanner<Feature> featureScanner;
4644

4745
private final CucumberEngineDescriptor engineDescriptor;
4846
private final Predicate<String> packageFilter;
@@ -56,7 +54,21 @@ private FeatureResolver(
5654
this.parameters = parameters;
5755
this.engineDescriptor = engineDescriptor;
5856
this.packageFilter = packageFilter;
59-
this.namingStrategy = new CucumberEngineOptions(parameters).namingStrategy();
57+
CucumberEngineOptions options = new CucumberEngineOptions(parameters);
58+
this.namingStrategy = options.namingStrategy();
59+
CachingFeatureParser featureParser = createFeatureParser(options);
60+
this.featureScanner = new ResourceScanner<>(
61+
ClassLoaders::getDefaultClassLoader,
62+
FeatureIdentifier::isFeature,
63+
featureParser::parseResource);
64+
}
65+
66+
private static CachingFeatureParser createFeatureParser(CucumberEngineOptions options) {
67+
Supplier<ClassLoader> classLoader = FeatureResolver.class::getClassLoader;
68+
UuidGeneratorServiceLoader uuidGeneratorServiceLoader = new UuidGeneratorServiceLoader(classLoader, options);
69+
UuidGenerator uuidGenerator = uuidGeneratorServiceLoader.loadUuidGenerator();
70+
FeatureParser featureParser = new FeatureParser(uuidGenerator::generateId);
71+
return new CachingFeatureParser(featureParser);
6072
}
6173

6274
static FeatureResolver create(

0 commit comments

Comments
 (0)