
io.quarkiverse.cucumber.CucumberQuarkusTest Maven / Gradle / Ivy
package io.quarkiverse.cucumber;
import java.net.URI;
import java.time.Clock;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.spi.CDI;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.platform.console.ConsoleLauncher;
import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.feature.FeatureParser;
import io.cucumber.core.filter.Filters;
import io.cucumber.core.gherkin.Pickle;
import io.cucumber.core.options.CommandlineOptionsParser;
import io.cucumber.core.options.Constants;
import io.cucumber.core.options.CucumberOptionsAnnotationParser;
import io.cucumber.core.options.CucumberProperties;
import io.cucumber.core.options.CucumberPropertiesParser;
import io.cucumber.core.options.RuntimeOptions;
import io.cucumber.core.options.RuntimeOptionsBuilder;
import io.cucumber.core.plugin.Options;
import io.cucumber.core.plugin.PluginFactory;
import io.cucumber.core.plugin.Plugins;
import io.cucumber.core.plugin.PrettyFormatter;
import io.cucumber.core.runner.Runner;
import io.cucumber.core.runtime.CucumberExecutionContext;
import io.cucumber.core.runtime.ExitStatus;
import io.cucumber.core.runtime.FeaturePathFeatureSupplier;
import io.cucumber.core.runtime.FeatureSupplier;
import io.cucumber.core.runtime.ObjectFactorySupplier;
import io.cucumber.core.runtime.TimeServiceEventBus;
import io.cucumber.java.JavaBackendProviderService;
import io.cucumber.plugin.event.EventHandler;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestStep;
import io.cucumber.plugin.event.TestStepFinished;
import io.quarkus.arc.Arc;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public abstract class CucumberQuarkusTest {
@TestFactory
List getTests() {
EventBus eventBus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID);
final FeatureParser parser = new FeatureParser(eventBus::generateId);
RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromPropertiesFile())
.build();
RuntimeOptions environmentOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromEnvironment())
.build(propertiesFileOptions);
RuntimeOptions systemOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromSystemProperties())
.build(environmentOptions);
RuntimeOptions runtimeOptions;
RuntimeOptionsBuilder runtimeOptionsBuilder = new RuntimeOptionsBuilder()
.addDefaultFeaturePathIfAbsent()
.addDefaultGlueIfAbsent()
.addDefaultSummaryPrinterIfNotDisabled();
QuarkusCucumberOptionsProvider optionsProvider = new QuarkusCucumberOptionsProvider();
if (optionsProvider.hasOptions(this.getClass())) {
CucumberOptionsAnnotationParser annotationParser = new CucumberOptionsAnnotationParser()
.withOptionsProvider(optionsProvider);
RuntimeOptions annotationOptions = annotationParser
.parse(this.getClass())
.build(systemOptions);
runtimeOptions = runtimeOptionsBuilder.build(annotationOptions);
} else {
runtimeOptions = runtimeOptionsBuilder.build(systemOptions);
}
FeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(() -> Thread.currentThread().getContextClassLoader(),
runtimeOptions, parser);
final Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions);
plugins.addPlugin(new PrettyFormatter(System.out));
final ExitStatus exitStatus = new ExitStatus(runtimeOptions);
plugins.addPlugin(exitStatus);
if (runtimeOptions.isMultiThreaded()) {
plugins.setSerialEventBusOnEventListenerPlugins(eventBus);
} else {
plugins.setEventBusOnEventListenerPlugins(eventBus);
}
CucumberExecutionContext context = cucumberExecutionContext(eventBus, runtimeOptions, exitStatus);
List features = new LinkedList<>();
features.add(DynamicTest.dynamicTest("Start Cucumber", context::startTestRun));
features.add(DynamicTest.dynamicTest("Before All Features", context::runBeforeAllHooks));
Predicate filters = new Filters(runtimeOptions);
EventHandler scenarioFinishedHandler = __ -> {
var scenarioContext = Arc.container().getActiveContext(ScenarioScope.class);
if (scenarioContext != null) {
scenarioContext.destroy();
}
};
featureSupplier.get().forEach(f -> {
List tests = new LinkedList<>();
tests.add(DynamicTest.dynamicTest("Start Feature", () -> context.beforeFeature(f)));
f.getPickles()
.stream()
.filter(filters)
.forEach(p -> tests.add(DynamicTest.dynamicTest(p.getName(), () -> {
AtomicReference resultAtomicReference = new AtomicReference<>();
EventHandler handler = event -> {
if (event.getResult().getStatus() != Status.PASSED) {
// save the first failed test step, so that we can get the line number of the cucumber file
resultAtomicReference.compareAndSet(null, event);
}
};
eventBus.registerHandlerFor(TestCaseFinished.class, scenarioFinishedHandler);
eventBus.registerHandlerFor(TestStepFinished.class, handler);
context.runTestCase(r -> r.runPickle(p));
eventBus.removeHandlerFor(TestStepFinished.class, handler);
eventBus.removeHandlerFor(TestCaseFinished.class, scenarioFinishedHandler);
// if we have no main arguments, we are running as part of a junit test suite, we need to fail the junit test explicitly
if (resultAtomicReference.get() != null) {
TestStep testStep = resultAtomicReference.get().getTestStep();
if (testStep instanceof PickleStepTestStep) {
// failed in step, we have a line in the feature file
Assertions.fail(
"failed in " + f.getUri() + " at line "
+ ((PickleStepTestStep) testStep).getStep()
.getLocation()
.getLine(),
resultAtomicReference.get().getResult().getError());
} else {
// failed somewhere in hooks
Assertions.fail(
"failed in " + f.getUri() + " at "
+ testStep.getCodeLocation(),
resultAtomicReference.get().getResult().getError());
}
}
})));
if (tests.size() > 1) {
features.add(DynamicContainer.dynamicContainer(f.getName().orElse(f.getSource()), tests.stream()));
}
});
features.add(DynamicTest.dynamicTest("After All Features", context::runAfterAllHooks));
features.add(DynamicTest.dynamicTest("Finish Cucumber", context::finishTestRun));
return features;
}
private static CucumberExecutionContext cucumberExecutionContext(EventBus eventBus, RuntimeOptions runtimeOptions,
ExitStatus exitStatus) {
ObjectFactory objectFactory = new CdiObjectFactory();
ObjectFactorySupplier objectFactorySupplier = () -> objectFactory;
Runner runner = new Runner(eventBus,
Collections.singleton(new JavaBackendProviderService().create(objectFactorySupplier.get(),
objectFactorySupplier.get(),
() -> Thread.currentThread()
.getContextClassLoader())),
objectFactorySupplier.get(),
runtimeOptions);
return new CucumberExecutionContext(eventBus, exitStatus, () -> runner);
}
public static class CdiObjectFactory implements ObjectFactory {
public CdiObjectFactory() {
}
public void start() {
}
public void stop() {
}
public boolean addClass(Class> clazz) {
return true;
}
public T getInstance(Class type) {
var old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(type.getClassLoader());
Instance selected = CDI.current().select(type);
if (selected.isUnsatisfied()) {
throw new IllegalArgumentException(type.getName() + " is no CDI bean.");
} else {
return selected.get();
}
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
}
protected static void runMain(Class testClass, String[] args) {
RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromPropertiesFile())
.build();
RuntimeOptions environmentOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromEnvironment())
.build(propertiesFileOptions);
RuntimeOptions systemOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromSystemProperties())
.build(environmentOptions);
CommandlineOptionsParser commandlineOptionsParser = new CommandlineOptionsParser(System.out);
RuntimeOptions runtimeOptions = commandlineOptionsParser.parse(args).build(systemOptions);
commandlineOptionsParser.exitStatus().ifPresent(System::exit);
System.setProperty(Constants.ANSI_COLORS_DISABLED_PROPERTY_NAME, String.valueOf(runtimeOptions.isMonochrome()));
//TODO: CUCUMBER_PROPERTIES_FILE_NAME
System.setProperty(Constants.EXECUTION_DRY_RUN_PROPERTY_NAME, String.valueOf(runtimeOptions.isDryRun()));
System.setProperty(Constants.EXECUTION_LIMIT_PROPERTY_NAME, String.valueOf(runtimeOptions.getLimitCount()));
//TODO: EXECUTION_ORDER_PROPERTY_NAME runtimeOptions.getPickleOrder(); (how can we convert this?)
//--strict/--no-strict is already handled by the CommandlineOptionsParser EXECUTION_STRICT_PROPERTY_NAME
System.setProperty(Constants.WIP_PROPERTY_NAME, String.valueOf(runtimeOptions.isWip()));
System.setProperty(Constants.FEATURES_PROPERTY_NAME,
runtimeOptions.getFeaturePaths().stream().map(URI::toString).collect(Collectors.joining(",")));
System.setProperty(Constants.FILTER_NAME_PROPERTY_NAME,
runtimeOptions.getNameFilters().stream().map(Pattern::toString).collect(Collectors.joining(",")));
System.setProperty(Constants.FILTER_TAGS_PROPERTY_NAME,
runtimeOptions.getTagExpressions().stream().map(Object::toString).collect(Collectors.joining(",")));
System.setProperty(Constants.GLUE_PROPERTY_NAME,
runtimeOptions.getGlue().stream().map(URI::toString).collect(Collectors.joining(",")));
Optional.ofNullable(runtimeOptions.getObjectFactoryClass())
.ifPresent(s -> System.setProperty(Constants.OBJECT_FACTORY_PROPERTY_NAME, s.getName()));
System.setProperty(Constants.PLUGIN_PROPERTY_NAME,
runtimeOptions.plugins().stream().map(Options.Plugin::pluginString).collect(Collectors.joining(",")));
//Not supported via CLI argument: PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME
//Not supported via CLI argument: PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME
//Not supported via CLI argument: PLUGIN_PUBLISH_URL_PROPERTY_NAME
//Not supported via CLI argument: PLUGIN_PUBLISH_QUIET_PROPERTY_NAME
System.setProperty(Constants.SNIPPET_TYPE_PROPERTY_NAME, runtimeOptions.getSnippetType().toString().toLowerCase());
ConsoleLauncher.main("-c", testClass.getName());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy