Skip to content

Commit 56c3477

Browse files
authored
[Core] Refactor Runtime (#1367)
[Core] Refactor Runtime Extracting: Backend creation, Glue creation, Runner creation Feature compilation from the runtime allows Tests on runners to skip the creation of Runtime Makes extracting Filter provider(#1352) and Feature provider(#1366) easier. Other runtimes such as JUnit and TestNg to skip the creation of the runtime --parallel (#1357), junit 5 (#1149) and pickle runner support. Clarifies the execution loop of Cucumber
1 parent 180537c commit 56c3477

File tree

80 files changed

+2348
-1487
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2348
-1487
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ jobs:
1111
include:
1212
- stage: test
1313
jdk: oraclejdk8
14+
script: mvn -q verify -Pcheck-semantic-version -DskipTests=true
15+
env: CHECK_SEMANTIC_VERSION=true
16+
- jdk: oraclejdk8
1417
script: mvn -q install
1518
after_success:
1619
- mvn clean cobertura:cobertura coveralls:report -P coveralls.io

android/src/main/java/cucumber/runtime/android/CucumberExecutor.java

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66
import cucumber.api.TypeRegistryConfigurer;
77
import cucumber.api.CucumberOptions;
88
import cucumber.api.StepDefinitionReporter;
9+
import cucumber.api.event.TestRunStarted;
10+
import cucumber.runner.EventBus;
11+
import cucumber.runner.Runner;
12+
import cucumber.runner.TimeService;
13+
import cucumber.runtime.BackendSupplier;
14+
import cucumber.runtime.FeaturePathFeatureSupplier;
15+
import cucumber.runtime.filter.Filters;
16+
import cucumber.runtime.formatter.Plugins;
17+
import cucumber.runtime.filter.RerunFilters;
18+
import cucumber.runtime.formatter.PluginFactory;
19+
import cucumber.runtime.model.FeatureLoader;
20+
import cucumber.runtime.ThreadLocalRunnerSupplier;
21+
import cucumber.runtime.RuntimeGlueSupplier;
922
import io.cucumber.stepexpression.TypeRegistry;
1023
import cucumber.api.event.TestRunFinished;
1124
import cucumber.api.java.ObjectFactory;
@@ -15,10 +28,9 @@
1528
import cucumber.runtime.DefaultTypeRegistryConfiguration;
1629
import cucumber.runtime.Env;
1730
import cucumber.runtime.Reflections;
18-
import cucumber.runtime.Runtime;
1931
import cucumber.runtime.RuntimeOptions;
2032
import cucumber.runtime.RuntimeOptionsFactory;
21-
import cucumber.runtime.Stats;
33+
import cucumber.runtime.formatter.Stats;
2234
import cucumber.runtime.UndefinedStepsTracker;
2335
import cucumber.runtime.formatter.AndroidInstrumentationReporter;
2436
import cucumber.runtime.formatter.AndroidLogcatReporter;
@@ -56,11 +68,6 @@ public final class CucumberExecutor {
5668
*/
5769
private final Instrumentation instrumentation;
5870

59-
/**
60-
* The {@link java.lang.ClassLoader} for all test relevant classes.
61-
*/
62-
private final ClassLoader classLoader;
63-
6471
/**
6572
* The {@link cucumber.runtime.ClassFinder} to find all to be loaded classes.
6673
*/
@@ -71,15 +78,10 @@ public final class CucumberExecutor {
7178
*/
7279
private final RuntimeOptions runtimeOptions;
7380

74-
/**
75-
* The {@link cucumber.runtime.Runtime} to run with.
76-
*/
77-
private final Runtime runtime;
78-
79-
/**
80-
* The actual {@link PickleEvent}s to run stored in {@link PickleStruct}s.
81-
*/
8281
private final List<PickleEvent> pickleEvents;
82+
private final EventBus bus;
83+
private final Plugins plugins;
84+
private final Runner runner;
8385

8486
/**
8587
* Creates a new instance for the given parameters.
@@ -91,44 +93,52 @@ public final class CucumberExecutor {
9193
public CucumberExecutor(final Arguments arguments, final Instrumentation instrumentation) {
9294

9395
trySetCucumberOptionsToSystemProperties(arguments);
94-
9596
final Context context = instrumentation.getContext();
9697
this.instrumentation = instrumentation;
97-
this.classLoader = context.getClassLoader();
98+
ClassLoader classLoader = context.getClassLoader();
9899
this.classFinder = createDexClassFinder(context);
99100
this.runtimeOptions = createRuntimeOptions(context).noSummaryPrinter();
100101

101102
ResourceLoader resourceLoader = new AndroidResourceLoader(context);
102-
this.runtime = new Runtime(resourceLoader, classLoader, createBackends(), runtimeOptions);
103+
104+
this.bus = new EventBus(TimeService.SYSTEM);
105+
this.plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
106+
RuntimeGlueSupplier glueSupplier = new RuntimeGlueSupplier();
107+
this.runner = new ThreadLocalRunnerSupplier(runtimeOptions, bus, createBackends(), glueSupplier).get();
108+
FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
109+
FeaturePathFeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
110+
RerunFilters rerunFilters = new RerunFilters(runtimeOptions, featureLoader);
111+
Filters filters = new Filters(runtimeOptions, rerunFilters);
103112
UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
104-
undefinedStepsTracker.setEventPublisher(runtime.getEventBus());
113+
undefinedStepsTracker.setEventPublisher(bus);
105114
Stats stats = new Stats();
106-
stats.setEventPublisher(runtime.getEventBus());
115+
stats.setEventPublisher(bus);
107116

108117
AndroidInstrumentationReporter instrumentationReporter = new AndroidInstrumentationReporter(undefinedStepsTracker, instrumentation);
109-
runtimeOptions.addPlugin(instrumentationReporter);
110-
runtimeOptions.addPlugin(new AndroidLogcatReporter(stats, undefinedStepsTracker, TAG));
111-
112-
List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader, runtime.getEventBus());
113-
this.pickleEvents = FeatureCompiler.compile(cucumberFeatures, this.runtime);
118+
plugins.addPlugin(instrumentationReporter);
119+
plugins.addPlugin(new AndroidLogcatReporter(stats, undefinedStepsTracker, TAG));
120+
121+
// Start the run before reading the features.
122+
// Allows the test source read events to be broadcast properly
123+
List<CucumberFeature> features = featureSupplier.get();
124+
bus.send(new TestRunStarted(bus.getTime()));
125+
for (CucumberFeature feature : features) {
126+
feature.sendTestSourceRead(bus);
127+
}
128+
this.pickleEvents = FeatureCompiler.compile(features, filters);
114129
instrumentationReporter.setNumberOfTests(getNumberOfConcreteScenarios());
115130
}
116131

117132
/**
118133
* Runs the cucumber scenarios with the specified arguments.
119134
*/
120135
public void execute() {
121-
122-
// TODO: This is duplicated in info.cucumber.Runtime.
123-
124-
final StepDefinitionReporter stepDefinitionReporter = runtimeOptions.stepDefinitionReporter(classLoader);
125-
runtime.reportStepDefinitions(stepDefinitionReporter);
126-
136+
final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();
137+
runner.reportStepDefinitions(stepDefinitionReporter);
127138
for (final PickleEvent pickleEvent : pickleEvents) {
128-
runtime.getRunner().runPickle(pickleEvent);
139+
runner.runPickle(pickleEvent);
129140
}
130-
131-
runtime.getEventBus().send(new TestRunFinished(runtime.getEventBus().getTime()));
141+
bus.send(new TestRunFinished(bus.getTime()));
132142
}
133143

134144
/**
@@ -171,13 +181,19 @@ private RuntimeOptions createRuntimeOptions(final Context context) {
171181
throw new CucumberException("No CucumberOptions annotation");
172182
}
173183

174-
private Collection<? extends Backend> createBackends() {
175-
final Reflections reflections = new Reflections(classFinder);
176-
final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName()));
177-
final AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, instrumentation);
178-
final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
179-
final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
180-
typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
181-
return singletonList(new JavaBackend(objectFactory, classFinder, typeRegistry));
184+
private BackendSupplier createBackends() {
185+
return new BackendSupplier() {
186+
@Override
187+
public Collection<? extends Backend> get() {
188+
final Reflections reflections = new Reflections(classFinder);
189+
final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName()));
190+
final AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, instrumentation);
191+
final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
192+
final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
193+
typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
194+
return singletonList(new JavaBackend(objectFactory, classFinder, typeRegistry));
195+
}
196+
};
197+
182198
}
183199
}

android/src/main/java/cucumber/runtime/android/FeatureCompiler.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package cucumber.runtime.android;
22

3-
import cucumber.runtime.Runtime;
3+
import cucumber.runtime.filter.Filters;
44
import cucumber.runtime.model.CucumberFeature;
55
import gherkin.events.PickleEvent;
66

@@ -18,11 +18,12 @@ final class FeatureCompiler {
1818
* @param cucumberFeatures the list of {@link CucumberFeature} to compile
1919
* @return the compiled pickles in {@link PickleEvent}s
2020
*/
21-
static List<PickleEvent> compile(final List<CucumberFeature> cucumberFeatures, final Runtime runtime) {
21+
static List<PickleEvent> compile(final List<CucumberFeature> cucumberFeatures, final Filters filters) {
2222
List<PickleEvent> pickles = new ArrayList<PickleEvent>();
23+
cucumber.runtime.FeatureCompiler compiler = new cucumber.runtime.FeatureCompiler();
2324
for (final CucumberFeature feature : cucumberFeatures) {
24-
for (final PickleEvent pickleEvent : runtime.compileFeature(feature)) {
25-
if (runtime.matchesFilters(pickleEvent)) {
25+
for (final PickleEvent pickleEvent : compiler.compileFeature(feature)) {
26+
if (filters.matchesFilters(pickleEvent)) {
2627
pickles.add(pickleEvent);
2728
}
2829
}

android/src/main/java/cucumber/runtime/formatter/AndroidLogcatReporter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import cucumber.api.event.TestRunFinished;
99
import cucumber.api.event.TestStepStarted;
1010
import cucumber.api.formatter.Formatter;
11-
import cucumber.runtime.Stats;
1211
import cucumber.runtime.UndefinedStepsTracker;
1312

1413
/**

core/src/main/java/cucumber/api/cli/Main.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
package cucumber.api.cli;
22

3+
import cucumber.runner.EventBus;
4+
import cucumber.runner.TimeService;
5+
import cucumber.runtime.BackendModuleBackendSupplier;
36
import cucumber.runtime.ClassFinder;
7+
import cucumber.runtime.FeaturePathFeatureSupplier;
8+
import cucumber.runtime.FeatureSupplier;
9+
import cucumber.runtime.GlueSupplier;
10+
import cucumber.runtime.RunnerSupplier;
11+
import cucumber.runtime.filter.Filters;
12+
import cucumber.runtime.formatter.Plugins;
13+
import cucumber.runtime.filter.RerunFilters;
14+
import cucumber.runtime.ThreadLocalRunnerSupplier;
15+
import cucumber.runtime.RuntimeGlueSupplier;
416
import cucumber.runtime.Runtime;
517
import cucumber.runtime.RuntimeOptions;
18+
import cucumber.runtime.formatter.PluginFactory;
619
import cucumber.runtime.io.MultiLoader;
720
import cucumber.runtime.io.ResourceLoader;
821
import cucumber.runtime.io.ResourceLoaderClassFinder;
9-
10-
import java.io.IOException;
11-
import java.util.ArrayList;
22+
import cucumber.runtime.model.FeatureLoader;
1223

1324
import static java.util.Arrays.asList;
1425

@@ -27,11 +38,20 @@ public static void main(String[] argv) {
2738
* @return 0 if execution was successful, 1 if it was not (test failures)
2839
*/
2940
public static byte run(String[] argv, ClassLoader classLoader) {
30-
RuntimeOptions runtimeOptions = new RuntimeOptions(new ArrayList<String>(asList(argv)));
41+
RuntimeOptions runtimeOptions = new RuntimeOptions(asList(argv));
3142

3243
ResourceLoader resourceLoader = new MultiLoader(classLoader);
3344
ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
34-
Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
45+
BackendModuleBackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions);
46+
EventBus bus = new EventBus(TimeService.SYSTEM);
47+
Plugins plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
48+
GlueSupplier glueSupplier = new RuntimeGlueSupplier();
49+
RunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, glueSupplier);
50+
FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
51+
FeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
52+
RerunFilters rerunFilters = new RerunFilters(runtimeOptions, featureLoader);
53+
Filters filters = new Filters(runtimeOptions, rerunFilters);
54+
Runtime runtime = new Runtime(plugins, runtimeOptions, bus, filters, runnerSupplier, featureSupplier);
3555
runtime.run();
3656
return runtime.exitStatus();
3757
}

core/src/main/java/cucumber/runner/EventBus.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import cucumber.api.event.Event;
44
import cucumber.api.event.EventHandler;
55
import cucumber.api.event.EventPublisher;
6+
import cucumber.api.event.TestCaseFinished;
7+
import cucumber.api.event.TestCaseStarted;
68

79
import java.util.ArrayList;
810
import java.util.HashMap;
@@ -17,11 +19,16 @@ public EventBus(TimeService stopWatch) {
1719
this.stopWatch = stopWatch;
1820
}
1921

22+
public EventBus createBatchedEventBus() {
23+
return new BatchEventBus(this);
24+
}
25+
2026
public Long getTime() {
2127
return stopWatch.time();
2228
}
2329

24-
public void send(Event event) {
30+
31+
public synchronized void send(Event event) {
2532
if (handlers.containsKey(event.getClass())) {
2633
for (EventHandler handler : handlers.get(event.getClass())) {
2734
//noinspection unchecked: protected by registerHandlerFor
@@ -30,6 +37,12 @@ public void send(Event event) {
3037
}
3138
}
3239

40+
synchronized void sendAll(List<Event> events) {
41+
for (Event event : events) {
42+
send(event);
43+
}
44+
}
45+
3346
@Override
3447
public <T extends Event> void registerHandlerFor(Class<T> eventType, EventHandler<T> handler) {
3548
if (handlers.containsKey(eventType)) {
@@ -40,4 +53,33 @@ public <T extends Event> void registerHandlerFor(Class<T> eventType, EventHandle
4053
handlers.put(eventType, list);
4154
}
4255
}
56+
57+
public <T extends Event> void removeHandlerFor(Class<T> eventType, EventHandler<T> handler) {
58+
if (handlers.containsKey(eventType)) {
59+
handlers.get(eventType).remove(handler);
60+
}
61+
}
62+
63+
64+
private class BatchEventBus extends EventBus {
65+
66+
private final EventBus parent;
67+
private final List<Event> queue = new ArrayList<Event>();
68+
69+
BatchEventBus(EventBus parent) {
70+
super(parent.stopWatch);
71+
this.parent = parent;
72+
}
73+
74+
@Override
75+
public void send(Event event) {
76+
super.send(event);
77+
queue.add(event);
78+
if(event instanceof TestCaseFinished){
79+
parent.sendAll(queue);
80+
queue.clear();
81+
}
82+
}
83+
}
84+
4385
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public Runner(Glue glue, EventBus bus, Collection<? extends Backend> backends, R
3838

3939
}
4040

41+
public EventBus getBus() {
42+
return bus;
43+
}
44+
4145
public void runPickle(PickleEvent pickle) {
4246
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
4347
TestCase testCase = createTestCaseForPickle(pickle);
@@ -127,8 +131,6 @@ private List<HookTestStep> getBeforeStepHooks(List<PickleTag> tags) {
127131
}
128132

129133
private void buildBackendWorlds() {
130-
runtimeOptions.getPlugins(); // To make sure that the plugins are instantiated after
131-
// the features have been parsed but before the pickles start to execute.
132134
for (Backend backend : backends) {
133135
backend.buildWorld();
134136
}

0 commit comments

Comments
 (0)