Skip to content

Commit 1798016

Browse files
committed
[Core] Share object factory between backends
By sharing the object factory between different backends it becomes possible to use the same test context in different languages. This is very useful when mixing Kotlin and Java, or Java and Java 8. This also requires that the backend no longer manages the object factory life-cycle. To end a container and lookup have been extracted from the object factory. Closes #1117.
1 parent 0f44408 commit 1798016

36 files changed

+274
-156
lines changed

core/src/main/java/io/cucumber/core/backend/BackendProviderService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55

66
public interface BackendProviderService {
77

8-
Backend create(ObjectFactory objectFactory, ResourceLoader resourceLoader, TypeRegistry typeRegistry);
8+
Backend create(Container container, ResourceLoader resourceLoader, TypeRegistry typeRegistry);
99

1010
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.cucumber.core.backend;
2+
3+
public interface Container extends Lookup {
4+
/**
5+
* Collects glue classes in the classpath. Called once on init.
6+
*
7+
* @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...)
8+
* @return true if stepdefs and hooks in this class should be used, false if they should be ignored.
9+
*/
10+
boolean addClass(Class<?> glueClass);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.cucumber.core.backend;
2+
3+
public interface Lookup {
4+
/**
5+
* Provides the glue instances used to execute the current scenario.
6+
*
7+
* @param glueClass type of instance to be created.
8+
* @param <T> type of Glue class
9+
* @return new Glue instance of type T
10+
*/
11+
<T> T getInstance(Class<T> glueClass);
12+
}

core/src/main/java/io/cucumber/core/backend/ObjectFactory.java

+1-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* Minimal facade for Dependency Injection containers
55
*/
6-
public interface ObjectFactory {
6+
public interface ObjectFactory extends Container {
77

88
/**
99
* Instantiate glue code <b>before</b> scenario execution. Called once per scenario.
@@ -15,21 +15,4 @@ public interface ObjectFactory {
1515
*/
1616
void stop();
1717

18-
/**
19-
* Collects glue classes in the classpath. Called once on init.
20-
*
21-
* @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...)
22-
* @return true if stepdefs and hooks in this class should be used, false if they should be ignored.
23-
*/
24-
boolean addClass(Class<?> glueClass);
25-
26-
/**
27-
* Provides the glue instances used to execute the current scenario. The instance can be prepared in
28-
* {@link #start()}.
29-
*
30-
* @param glueClass type of instance to be created.
31-
* @param <T> type of Glue class
32-
* @return new Glue instance of type T
33-
*/
34-
<T> T getInstance(Class<T> glueClass);
3518
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.cucumber.core.backend;
2+
3+
public interface ObjectFactorySupplier {
4+
5+
ObjectFactory get();
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.cucumber.core.backend;
2+
3+
import io.cucumber.core.options.Env;
4+
5+
import static io.cucumber.core.backend.ObjectFactoryLoader.loadObjectFactory;
6+
7+
public class SingletonObjectFactorySupplier implements ObjectFactorySupplier {
8+
9+
private ObjectFactory objectFactory;
10+
11+
@Override
12+
public ObjectFactory get() {
13+
if(objectFactory == null){
14+
objectFactory = loadObjectFactory(Env.INSTANCE.get(ObjectFactory.class.getName()));
15+
}
16+
return objectFactory;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.cucumber.core.backend;
2+
3+
4+
import io.cucumber.core.options.Env;
5+
6+
import static io.cucumber.core.backend.ObjectFactoryLoader.loadObjectFactory;
7+
8+
public class ThreadLocalObjectFactorySupplier implements ObjectFactorySupplier {
9+
10+
private final ThreadLocal<ObjectFactory> runners = ThreadLocal.withInitial(
11+
() -> loadObjectFactory(Env.INSTANCE.get(ObjectFactory.class.getName()))
12+
);
13+
14+
@Override
15+
public ObjectFactory get() {
16+
return runners.get();
17+
}
18+
}

core/src/main/java/io/cucumber/core/runner/Runner.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import gherkin.pickles.PickleTag;
99
import io.cucumber.core.backend.Backend;
1010
import io.cucumber.core.backend.HookDefinition;
11+
import io.cucumber.core.backend.ObjectFactory;
1112
import io.cucumber.core.event.EventBus;
1213
import io.cucumber.core.logging.Logger;
1314
import io.cucumber.core.logging.LoggerFactory;
@@ -27,11 +28,13 @@ public final class Runner {
2728
private final EventBus bus;
2829
private final Collection<? extends Backend> backends;
2930
private final RunnerOptions runnerOptions;
31+
private final ObjectFactory objectFactory;
3032

31-
public Runner(EventBus bus, Collection<? extends Backend> backends, RunnerOptions runnerOptions) {
33+
public Runner(EventBus bus, Collection<? extends Backend> backends, ObjectFactory objectFactory, RunnerOptions runnerOptions) {
3234
this.bus = bus;
3335
this.runnerOptions = runnerOptions;
3436
this.backends = backends;
37+
this.objectFactory = objectFactory;
3538
List<URI> gluePaths = runnerOptions.getGlue();
3639
log.debug("Loading glue from " + FixJava.join(gluePaths, ", "));
3740
for (Backend backend : backends) {
@@ -49,7 +52,6 @@ public void runPickle(PickleEvent pickle) {
4952
TestCase testCase = createTestCaseForPickle(pickle);
5053
testCase.run(bus);
5154
disposeBackendWorlds();
52-
glue.removeScenarioScopedGlue();
5355
}
5456

5557
public void reportStepDefinitions(StepDefinitionReporter stepDefinitionReporter) {
@@ -127,6 +129,7 @@ private List<HookTestStep> getBeforeStepHooks(List<PickleTag> tags) {
127129
}
128130

129131
private void buildBackendWorlds() {
132+
objectFactory.start();
130133
for (Backend backend : backends) {
131134
backend.buildWorld();
132135
}
@@ -136,5 +139,7 @@ private void disposeBackendWorlds() {
136139
for (Backend backend : backends) {
137140
backend.disposeWorld();
138141
}
142+
objectFactory.stop();
143+
glue.removeScenarioScopedGlue();
139144
}
140145
}

core/src/main/java/io/cucumber/core/runtime/BackendServiceLoader.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import io.cucumber.core.backend.Backend;
55
import io.cucumber.core.backend.BackendProviderService;
66
import io.cucumber.core.backend.BackendSupplier;
7-
import io.cucumber.core.backend.ObjectFactory;
7+
import io.cucumber.core.backend.Container;
8+
import io.cucumber.core.backend.ObjectFactorySupplier;
89
import io.cucumber.core.exception.CucumberException;
910
import io.cucumber.core.io.ClassFinder;
1011
import io.cucumber.core.io.ResourceLoader;
11-
import io.cucumber.core.options.Env;
1212
import io.cucumber.core.options.RuntimeOptions;
1313
import io.cucumber.core.reflection.Reflections;
1414
import io.cucumber.core.stepexpression.TypeRegistry;
@@ -19,8 +19,6 @@
1919
import java.util.Locale;
2020
import java.util.ServiceLoader;
2121

22-
import static io.cucumber.core.backend.ObjectFactoryLoader.loadObjectFactory;
23-
2422
/**
2523
* Supplies instances of {@link Backend} created by using a {@link ServiceLoader}
2624
* to locate instance of {@link BackendSupplier}.
@@ -30,11 +28,13 @@ public final class BackendServiceLoader implements BackendSupplier {
3028
private final ResourceLoader resourceLoader;
3129
private final ClassFinder classFinder;
3230
private final RuntimeOptions runtimeOptions;
31+
private final ObjectFactorySupplier container;
3332

34-
public BackendServiceLoader(ResourceLoader resourceLoader, ClassFinder classFinder, RuntimeOptions runtimeOptions) {
33+
public BackendServiceLoader(ResourceLoader resourceLoader, ClassFinder classFinder, RuntimeOptions runtimeOptions, ObjectFactorySupplier container) {
3534
this.resourceLoader = resourceLoader;
3635
this.classFinder = classFinder;
3736
this.runtimeOptions = runtimeOptions;
37+
this.container = container;
3838
}
3939

4040
@Override
@@ -54,8 +54,7 @@ private Collection<? extends Backend> loadBackends(Iterable<BackendProviderServi
5454
final TypeRegistry typeRegistry = createTypeRegistry();
5555
List<Backend> backends = new ArrayList<>();
5656
for (BackendProviderService backendProviderService : serviceLoader) {
57-
ObjectFactory objectFactory = loadObjectFactory(Env.INSTANCE.get(ObjectFactory.class.getName()));
58-
backends.add(backendProviderService.create(objectFactory, resourceLoader, typeRegistry));
57+
backends.add(backendProviderService.create(container.get(), resourceLoader, typeRegistry));
5958
}
6059
return backends;
6160
}

core/src/main/java/io/cucumber/core/runtime/Runtime.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import io.cucumber.core.api.event.TestRunFinished;
1212
import io.cucumber.core.api.event.TestRunStarted;
1313
import io.cucumber.core.backend.BackendSupplier;
14+
import io.cucumber.core.backend.ObjectFactory;
15+
import io.cucumber.core.backend.ObjectFactorySupplier;
16+
import io.cucumber.core.backend.SingletonObjectFactorySupplier;
17+
import io.cucumber.core.backend.ThreadLocalObjectFactorySupplier;
1418
import io.cucumber.core.exception.CucumberException;
1519
import io.cucumber.core.event.EventBus;
1620
import io.cucumber.core.options.Env;
@@ -38,6 +42,7 @@
3842
import java.util.concurrent.TimeUnit;
3943

4044
import static io.cucumber.core.api.event.Result.SEVERITY;
45+
import static io.cucumber.core.backend.ObjectFactoryLoader.loadObjectFactory;
4146
import static java.util.Collections.emptyList;
4247
import static java.util.Collections.max;
4348
import static java.util.Collections.min;
@@ -190,9 +195,13 @@ public Runtime build() {
190195
? this.classFinder
191196
: new ResourceLoaderClassFinder(resourceLoader, this.classLoader);
192197

198+
final ObjectFactorySupplier objectFactorySupplier = runtimeOptions.isMultiThreaded()
199+
? new ThreadLocalObjectFactorySupplier()
200+
: new SingletonObjectFactorySupplier();
201+
193202
final BackendSupplier backendSupplier = this.backendSupplier != null
194203
? this.backendSupplier
195-
: new BackendServiceLoader(resourceLoader, classFinder, runtimeOptions);
204+
: new BackendServiceLoader(resourceLoader, classFinder, runtimeOptions, objectFactorySupplier);
196205

197206
final Plugins plugins = new Plugins(new PluginFactory(), this.eventBus, runtimeOptions);
198207
for (final Plugin plugin : additionalPlugins) {
@@ -202,8 +211,8 @@ public Runtime build() {
202211
plugins.addPlugin(exitStatus);
203212

204213
final RunnerSupplier runnerSupplier = runtimeOptions.isMultiThreaded()
205-
? new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier)
206-
: new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier);
214+
? new ThreadLocalRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier)
215+
: new SingletonRunnerSupplier(runtimeOptions, eventBus, backendSupplier, objectFactorySupplier);
207216

208217
final ExecutorService executor = runtimeOptions.isMultiThreaded()
209218
? Executors.newFixedThreadPool(runtimeOptions.getThreads())

core/src/main/java/io/cucumber/core/runtime/SingletonRunnerSupplier.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.cucumber.core.runtime;
22

33
import io.cucumber.core.backend.BackendSupplier;
4+
import io.cucumber.core.backend.ObjectFactory;
5+
import io.cucumber.core.backend.ObjectFactorySupplier;
46
import io.cucumber.core.event.EventBus;
57
import io.cucumber.core.options.RunnerOptions;
68
import io.cucumber.core.runner.Runner;
@@ -15,17 +17,19 @@ public final class SingletonRunnerSupplier implements RunnerSupplier {
1517
private final BackendSupplier backendSupplier;
1618
private final RunnerOptions runnerOptions;
1719
private final EventBus eventBus;
20+
private final ObjectFactorySupplier objectFactory;
1821
private Runner runner;
1922

2023

2124
public SingletonRunnerSupplier(
2225
RunnerOptions runnerOptions,
2326
EventBus eventBus,
24-
BackendSupplier backendSupplier
25-
) {
27+
BackendSupplier backendSupplier,
28+
ObjectFactorySupplier objectFactory) {
2629
this.backendSupplier = backendSupplier;
2730
this.runnerOptions = runnerOptions;
2831
this.eventBus = eventBus;
32+
this.objectFactory = objectFactory;
2933
}
3034

3135
@Override
@@ -37,7 +41,7 @@ public Runner get() {
3741
}
3842

3943
private Runner createRunner() {
40-
return new Runner(eventBus, backendSupplier.get(), runnerOptions);
44+
return new Runner(eventBus, backendSupplier.get(), objectFactory.get(), runnerOptions);
4145
}
4246

4347
}

core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java

+8-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.cucumber.core.api.event.Event;
44
import io.cucumber.core.api.event.EventHandler;
55
import io.cucumber.core.backend.BackendSupplier;
6+
import io.cucumber.core.backend.ObjectFactory;
7+
import io.cucumber.core.backend.ObjectFactorySupplier;
68
import io.cucumber.core.event.AbstractEventBus;
79
import io.cucumber.core.event.EventBus;
810
import io.cucumber.core.options.RunnerOptions;
@@ -18,22 +20,19 @@ public final class ThreadLocalRunnerSupplier implements RunnerSupplier {
1820
private final BackendSupplier backendSupplier;
1921
private final RunnerOptions runnerOptions;
2022
private final SynchronizedEventBus sharedEventBus;
23+
private final ObjectFactorySupplier objectFactory;
2124

22-
private final ThreadLocal<Runner> runners = new ThreadLocal<Runner>() {
23-
@Override
24-
protected Runner initialValue() {
25-
return createRunner();
26-
}
27-
};
25+
private final ThreadLocal<Runner> runners = ThreadLocal.withInitial(this::createRunner);
2826

2927
public ThreadLocalRunnerSupplier(
3028
RunnerOptions runnerOptions,
3129
EventBus sharedEventBus,
32-
BackendSupplier backendSupplier
33-
) {
30+
BackendSupplier backendSupplier,
31+
ObjectFactorySupplier objectFactory) {
3432
this.runnerOptions = runnerOptions;
3533
this.sharedEventBus = SynchronizedEventBus.synchronize(sharedEventBus);
3634
this.backendSupplier = backendSupplier;
35+
this.objectFactory = objectFactory;
3736
}
3837

3938
@Override
@@ -42,7 +41,7 @@ public Runner get() {
4241
}
4342

4443
private Runner createRunner() {
45-
return new Runner(new LocalEventBus(sharedEventBus), backendSupplier.get(), runnerOptions);
44+
return new Runner(new LocalEventBus(sharedEventBus), backendSupplier.get(), objectFactory.get(), runnerOptions);
4645
}
4746

4847
private static final class LocalEventBus extends AbstractEventBus {

core/src/test/java/io/cucumber/core/backend/StubBackendProviderService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
public class StubBackendProviderService implements BackendProviderService {
1414
@Override
15-
public Backend create(ObjectFactory objectFactory, ResourceLoader resourceLoader, TypeRegistry typeRegistry) {
15+
public Backend create(Container container, ResourceLoader resourceLoader, TypeRegistry typeRegistry) {
1616
return new StubBackend();
1717
}
1818

core/src/test/java/io/cucumber/core/runner/HookTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.cucumber.core.backend.Glue;
55
import io.cucumber.core.backend.HookDefinition;
66
import io.cucumber.core.backend.Backend;
7+
import io.cucumber.core.backend.ObjectFactory;
78
import io.cucumber.core.event.EventBus;
89
import io.cucumber.core.io.MultiLoader;
910
import io.cucumber.core.options.Env;
@@ -47,6 +48,7 @@ public class HookTest {
4748
public void after_hooks_execute_before_objects_are_disposed() throws Throwable {
4849

4950
Backend backend = mock(Backend.class);
51+
ObjectFactory objectFactory = mock(ObjectFactory.class);
5052
final HookDefinition hook = mock(HookDefinition.class);
5153
when(hook.matches(ArgumentMatchers.<PickleTag>anyCollection())).thenReturn(true);
5254

@@ -59,7 +61,7 @@ public Object answer(InvocationOnMock invocation) {
5961
}
6062
}).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.<URI>anyList());
6163

62-
Runner runner = new Runner(bus, Collections.singleton(backend), runtimeOptions);
64+
Runner runner = new Runner(bus, Collections.singleton(backend), objectFactory, runtimeOptions);
6365

6466
runner.runPickle(pickleEvent);
6567

0 commit comments

Comments
 (0)