Skip to content

Commit

Permalink
[Core] Refactor Runtime (#1367)
Browse files Browse the repository at this point in the history
[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
  • Loading branch information
mpkorstanje authored Jun 5, 2018
1 parent 180537c commit 56c3477
Show file tree
Hide file tree
Showing 80 changed files with 2,348 additions and 1,487 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ jobs:
include:
- stage: test
jdk: oraclejdk8
script: mvn -q verify -Pcheck-semantic-version -DskipTests=true
env: CHECK_SEMANTIC_VERSION=true
- jdk: oraclejdk8
script: mvn -q install
after_success:
- mvn clean cobertura:cobertura coveralls:report -P coveralls.io
Expand Down
100 changes: 58 additions & 42 deletions android/src/main/java/cucumber/runtime/android/CucumberExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
import cucumber.api.TypeRegistryConfigurer;
import cucumber.api.CucumberOptions;
import cucumber.api.StepDefinitionReporter;
import cucumber.api.event.TestRunStarted;
import cucumber.runner.EventBus;
import cucumber.runner.Runner;
import cucumber.runner.TimeService;
import cucumber.runtime.BackendSupplier;
import cucumber.runtime.FeaturePathFeatureSupplier;
import cucumber.runtime.filter.Filters;
import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.filter.RerunFilters;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.model.FeatureLoader;
import cucumber.runtime.ThreadLocalRunnerSupplier;
import cucumber.runtime.RuntimeGlueSupplier;
import io.cucumber.stepexpression.TypeRegistry;
import cucumber.api.event.TestRunFinished;
import cucumber.api.java.ObjectFactory;
Expand All @@ -15,10 +28,9 @@
import cucumber.runtime.DefaultTypeRegistryConfiguration;
import cucumber.runtime.Env;
import cucumber.runtime.Reflections;
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.RuntimeOptionsFactory;
import cucumber.runtime.Stats;
import cucumber.runtime.formatter.Stats;
import cucumber.runtime.UndefinedStepsTracker;
import cucumber.runtime.formatter.AndroidInstrumentationReporter;
import cucumber.runtime.formatter.AndroidLogcatReporter;
Expand Down Expand Up @@ -56,11 +68,6 @@ public final class CucumberExecutor {
*/
private final Instrumentation instrumentation;

/**
* The {@link java.lang.ClassLoader} for all test relevant classes.
*/
private final ClassLoader classLoader;

/**
* The {@link cucumber.runtime.ClassFinder} to find all to be loaded classes.
*/
Expand All @@ -71,15 +78,10 @@ public final class CucumberExecutor {
*/
private final RuntimeOptions runtimeOptions;

/**
* The {@link cucumber.runtime.Runtime} to run with.
*/
private final Runtime runtime;

/**
* The actual {@link PickleEvent}s to run stored in {@link PickleStruct}s.
*/
private final List<PickleEvent> pickleEvents;
private final EventBus bus;
private final Plugins plugins;
private final Runner runner;

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

trySetCucumberOptionsToSystemProperties(arguments);

final Context context = instrumentation.getContext();
this.instrumentation = instrumentation;
this.classLoader = context.getClassLoader();
ClassLoader classLoader = context.getClassLoader();
this.classFinder = createDexClassFinder(context);
this.runtimeOptions = createRuntimeOptions(context).noSummaryPrinter();

ResourceLoader resourceLoader = new AndroidResourceLoader(context);
this.runtime = new Runtime(resourceLoader, classLoader, createBackends(), runtimeOptions);

this.bus = new EventBus(TimeService.SYSTEM);
this.plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
RuntimeGlueSupplier glueSupplier = new RuntimeGlueSupplier();
this.runner = new ThreadLocalRunnerSupplier(runtimeOptions, bus, createBackends(), glueSupplier).get();
FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
FeaturePathFeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
RerunFilters rerunFilters = new RerunFilters(runtimeOptions, featureLoader);
Filters filters = new Filters(runtimeOptions, rerunFilters);
UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
undefinedStepsTracker.setEventPublisher(runtime.getEventBus());
undefinedStepsTracker.setEventPublisher(bus);
Stats stats = new Stats();
stats.setEventPublisher(runtime.getEventBus());
stats.setEventPublisher(bus);

AndroidInstrumentationReporter instrumentationReporter = new AndroidInstrumentationReporter(undefinedStepsTracker, instrumentation);
runtimeOptions.addPlugin(instrumentationReporter);
runtimeOptions.addPlugin(new AndroidLogcatReporter(stats, undefinedStepsTracker, TAG));

List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader, runtime.getEventBus());
this.pickleEvents = FeatureCompiler.compile(cucumberFeatures, this.runtime);
plugins.addPlugin(instrumentationReporter);
plugins.addPlugin(new AndroidLogcatReporter(stats, undefinedStepsTracker, TAG));

// Start the run before reading the features.
// Allows the test source read events to be broadcast properly
List<CucumberFeature> features = featureSupplier.get();
bus.send(new TestRunStarted(bus.getTime()));
for (CucumberFeature feature : features) {
feature.sendTestSourceRead(bus);
}
this.pickleEvents = FeatureCompiler.compile(features, filters);
instrumentationReporter.setNumberOfTests(getNumberOfConcreteScenarios());
}

/**
* Runs the cucumber scenarios with the specified arguments.
*/
public void execute() {

// TODO: This is duplicated in info.cucumber.Runtime.

final StepDefinitionReporter stepDefinitionReporter = runtimeOptions.stepDefinitionReporter(classLoader);
runtime.reportStepDefinitions(stepDefinitionReporter);

final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();
runner.reportStepDefinitions(stepDefinitionReporter);
for (final PickleEvent pickleEvent : pickleEvents) {
runtime.getRunner().runPickle(pickleEvent);
runner.runPickle(pickleEvent);
}

runtime.getEventBus().send(new TestRunFinished(runtime.getEventBus().getTime()));
bus.send(new TestRunFinished(bus.getTime()));
}

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

private Collection<? extends Backend> createBackends() {
final Reflections reflections = new Reflections(classFinder);
final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName()));
final AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, instrumentation);
final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
return singletonList(new JavaBackend(objectFactory, classFinder, typeRegistry));
private BackendSupplier createBackends() {
return new BackendSupplier() {
@Override
public Collection<? extends Backend> get() {
final Reflections reflections = new Reflections(classFinder);
final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder, Env.INSTANCE.get(ObjectFactory.class.getName()));
final AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, instrumentation);
final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class, MultiLoader.packageName(runtimeOptions.getGlue()), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
return singletonList(new JavaBackend(objectFactory, classFinder, typeRegistry));
}
};

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cucumber.runtime.android;

import cucumber.runtime.Runtime;
import cucumber.runtime.filter.Filters;
import cucumber.runtime.model.CucumberFeature;
import gherkin.events.PickleEvent;

Expand All @@ -18,11 +18,12 @@ final class FeatureCompiler {
* @param cucumberFeatures the list of {@link CucumberFeature} to compile
* @return the compiled pickles in {@link PickleEvent}s
*/
static List<PickleEvent> compile(final List<CucumberFeature> cucumberFeatures, final Runtime runtime) {
static List<PickleEvent> compile(final List<CucumberFeature> cucumberFeatures, final Filters filters) {
List<PickleEvent> pickles = new ArrayList<PickleEvent>();
cucumber.runtime.FeatureCompiler compiler = new cucumber.runtime.FeatureCompiler();
for (final CucumberFeature feature : cucumberFeatures) {
for (final PickleEvent pickleEvent : runtime.compileFeature(feature)) {
if (runtime.matchesFilters(pickleEvent)) {
for (final PickleEvent pickleEvent : compiler.compileFeature(feature)) {
if (filters.matchesFilters(pickleEvent)) {
pickles.add(pickleEvent);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import cucumber.api.event.TestRunFinished;
import cucumber.api.event.TestStepStarted;
import cucumber.api.formatter.Formatter;
import cucumber.runtime.Stats;
import cucumber.runtime.UndefinedStepsTracker;

/**
Expand Down
30 changes: 25 additions & 5 deletions core/src/main/java/cucumber/api/cli/Main.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package cucumber.api.cli;

import cucumber.runner.EventBus;
import cucumber.runner.TimeService;
import cucumber.runtime.BackendModuleBackendSupplier;
import cucumber.runtime.ClassFinder;
import cucumber.runtime.FeaturePathFeatureSupplier;
import cucumber.runtime.FeatureSupplier;
import cucumber.runtime.GlueSupplier;
import cucumber.runtime.RunnerSupplier;
import cucumber.runtime.filter.Filters;
import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.filter.RerunFilters;
import cucumber.runtime.ThreadLocalRunnerSupplier;
import cucumber.runtime.RuntimeGlueSupplier;
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;

import java.io.IOException;
import java.util.ArrayList;
import cucumber.runtime.model.FeatureLoader;

import static java.util.Arrays.asList;

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

ResourceLoader resourceLoader = new MultiLoader(classLoader);
ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
BackendModuleBackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions);
EventBus bus = new EventBus(TimeService.SYSTEM);
Plugins plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
GlueSupplier glueSupplier = new RuntimeGlueSupplier();
RunnerSupplier runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier, glueSupplier);
FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
FeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
RerunFilters rerunFilters = new RerunFilters(runtimeOptions, featureLoader);
Filters filters = new Filters(runtimeOptions, rerunFilters);
Runtime runtime = new Runtime(plugins, runtimeOptions, bus, filters, runnerSupplier, featureSupplier);
runtime.run();
return runtime.exitStatus();
}
Expand Down
44 changes: 43 additions & 1 deletion core/src/main/java/cucumber/runner/EventBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import cucumber.api.event.Event;
import cucumber.api.event.EventHandler;
import cucumber.api.event.EventPublisher;
import cucumber.api.event.TestCaseFinished;
import cucumber.api.event.TestCaseStarted;

import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -17,11 +19,16 @@ public EventBus(TimeService stopWatch) {
this.stopWatch = stopWatch;
}

public EventBus createBatchedEventBus() {
return new BatchEventBus(this);
}

public Long getTime() {
return stopWatch.time();
}

public void send(Event event) {

public synchronized void send(Event event) {
if (handlers.containsKey(event.getClass())) {
for (EventHandler handler : handlers.get(event.getClass())) {
//noinspection unchecked: protected by registerHandlerFor
Expand All @@ -30,6 +37,12 @@ public void send(Event event) {
}
}

synchronized void sendAll(List<Event> events) {
for (Event event : events) {
send(event);
}
}

@Override
public <T extends Event> void registerHandlerFor(Class<T> eventType, EventHandler<T> handler) {
if (handlers.containsKey(eventType)) {
Expand All @@ -40,4 +53,33 @@ public <T extends Event> void registerHandlerFor(Class<T> eventType, EventHandle
handlers.put(eventType, list);
}
}

public <T extends Event> void removeHandlerFor(Class<T> eventType, EventHandler<T> handler) {
if (handlers.containsKey(eventType)) {
handlers.get(eventType).remove(handler);
}
}


private class BatchEventBus extends EventBus {

private final EventBus parent;
private final List<Event> queue = new ArrayList<Event>();

BatchEventBus(EventBus parent) {
super(parent.stopWatch);
this.parent = parent;
}

@Override
public void send(Event event) {
super.send(event);
queue.add(event);
if(event instanceof TestCaseFinished){
parent.sendAll(queue);
queue.clear();
}
}
}

}
6 changes: 4 additions & 2 deletions core/src/main/java/cucumber/runner/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public Runner(Glue glue, EventBus bus, Collection<? extends Backend> backends, R

}

public EventBus getBus() {
return bus;
}

public void runPickle(PickleEvent pickle) {
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
TestCase testCase = createTestCaseForPickle(pickle);
Expand Down Expand Up @@ -127,8 +131,6 @@ private List<HookTestStep> getBeforeStepHooks(List<PickleTag> tags) {
}

private void buildBackendWorlds() {
runtimeOptions.getPlugins(); // To make sure that the plugins are instantiated after
// the features have been parsed but before the pickles start to execute.
for (Backend backend : backends) {
backend.buildWorld();
}
Expand Down
Loading

0 comments on commit 56c3477

Please sign in to comment.