From 1b5db2bcf2f17fa75643ef62031f62c13315f65e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 28 Aug 2019 20:33:15 +0200 Subject: [PATCH] [JUnit/TestNG] Print snippets for undefined steps as needed (cherry picked from commit 93659b9565c4a636c6337bbbd9f5bfb5d29c862e) --- .../main/java/io/cucumber/core/cli/Main.java | 4 +- .../cucumber/core/options/PluginOption.java | 24 ++++---- .../cucumber/core/options/RuntimeOptions.java | 17 ++++++ .../core/options/RuntimeOptionsBuilder.java | 41 ++++++++------ .../core/plugin/UndefinedStepsPrinter.java | 55 +++++++++++++++++++ .../CucumberOptionsAnnotationParserTest.java | 12 ++-- .../core/options/RuntimeOptionsTest.java | 4 +- .../main/java/io/cucumber/junit/Cucumber.java | 1 + .../cucumber/testng/TestNGCucumberRunner.java | 1 + 9 files changed, 122 insertions(+), 37 deletions(-) create mode 100644 core/src/main/java/io/cucumber/core/plugin/UndefinedStepsPrinter.java diff --git a/core/src/main/java/io/cucumber/core/cli/Main.java b/core/src/main/java/io/cucumber/core/cli/Main.java index f4cba915ac..8afae97b0f 100644 --- a/core/src/main/java/io/cucumber/core/cli/Main.java +++ b/core/src/main/java/io/cucumber/core/cli/Main.java @@ -50,8 +50,8 @@ public static byte run(String[] argv, ClassLoader classLoader) { RuntimeOptions runtimeOptions = new CommandlineOptionsParser() .parse(argv) - .addDefaultFormatterIfNotPresent() - .addDefaultSummaryPrinterIfNotPresent() + .addDefaultFormatterIfAbsent() + .addDefaultSummaryPrinterIfAbsent() .build(systemOptions); diff --git a/core/src/main/java/io/cucumber/core/options/PluginOption.java b/core/src/main/java/io/cucumber/core/options/PluginOption.java index 40742d19cc..7ac614654e 100644 --- a/core/src/main/java/io/cucumber/core/options/PluginOption.java +++ b/core/src/main/java/io/cucumber/core/options/PluginOption.java @@ -1,9 +1,5 @@ package io.cucumber.core.options; -import io.cucumber.plugin.Plugin; -import io.cucumber.plugin.SummaryPrinter; -import io.cucumber.plugin.ConcurrentEventListener; -import io.cucumber.plugin.EventListener; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; @@ -18,8 +14,13 @@ import io.cucumber.core.plugin.RerunFormatter; import io.cucumber.core.plugin.TestNGFormatter; import io.cucumber.core.plugin.TimelineFormatter; +import io.cucumber.core.plugin.UndefinedStepsPrinter; import io.cucumber.core.plugin.UnusedStepsSummaryPrinter; import io.cucumber.core.plugin.UsageFormatter; +import io.cucumber.plugin.ConcurrentEventListener; +import io.cucumber.plugin.EventListener; +import io.cucumber.plugin.Plugin; +import io.cucumber.plugin.SummaryPrinter; import java.util.HashMap; import java.util.regex.Matcher; @@ -31,19 +32,20 @@ public class PluginOption implements Options.Plugin { private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)"); private static final HashMap> PLUGIN_CLASSES = new HashMap>() {{ - put("junit", JUnitFormatter.class); - put("testng", TestNGFormatter.class); + put("default_summary", DefaultSummaryPrinter.class); put("html", HTMLFormatter.class); + put("json", JSONFormatter.class); + put("junit", JUnitFormatter.class); + put("null_summary", NullSummaryPrinter.class); put("pretty", PrettyFormatter.class); put("progress", ProgressFormatter.class); - put("json", JSONFormatter.class); - put("usage", UsageFormatter.class); put("rerun", RerunFormatter.class); put("summary", DefaultSummaryPrinter.class); - put("default_summary", DefaultSummaryPrinter.class); - put("null_summary", NullSummaryPrinter.class); - put("unused", UnusedStepsSummaryPrinter.class); + put("testng", TestNGFormatter.class); put("timeline", TimelineFormatter.class); + put("undefined", UndefinedStepsPrinter.class); + put("unused", UnusedStepsSummaryPrinter.class); + put("usage", UsageFormatter.class); }}; // Refuse plugins known to implement the old API diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java index 06b1161f7f..0e93ed3336 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java @@ -53,6 +53,23 @@ public static RuntimeOptions defaultOptions() { return new RuntimeOptions(); } + void addUndefinedStepsSummaryPrinterIfAbsent() { + if (summaryPrinters.isEmpty()) { + summaryPrinters.add(PluginOption.parse("undefined")); + } + } + + void addDefaultFormatterIfAbsent(){ + if (formatters.isEmpty()) { + formatters.add(PluginOption.parse("progress")); + } + } + void addDefaultSummaryPrinterIfAbsent(){ + if (summaryPrinters.isEmpty()) { + summaryPrinters.add(PluginOption.parse("default_summary")); + } + } + public int getCount() { return count; } diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java index de74ff3859..fcbcb081fa 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java @@ -30,6 +30,9 @@ public final class RuntimeOptionsBuilder { private PickleOrder parsedPickleOrder = null; private Integer parsedCount = null; private Class parsedObjectFactoryClass = null; + private boolean addDefaultSummaryPrinterIfAbsent; + private boolean addDefaultFormatterIfAbsent; + private boolean addUndefinedStepsSummaryPrinterIfAbsent; public RuntimeOptionsBuilder addRerun(Collection featureWithLines) { if (parsedRerunPaths == null) { @@ -124,6 +127,18 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) { runtimeOptions.setObjectFactoryClass(parsedObjectFactoryClass); } + if(addDefaultFormatterIfAbsent) { + runtimeOptions.addDefaultFormatterIfAbsent(); + } + + if(addDefaultSummaryPrinterIfAbsent) { + runtimeOptions.addDefaultSummaryPrinterIfAbsent(); + } + + if(addUndefinedStepsSummaryPrinterIfAbsent) { + runtimeOptions.addUndefinedStepsSummaryPrinterIfAbsent(); + } + return runtimeOptions; } @@ -185,13 +200,19 @@ public RuntimeOptionsBuilder setWip(boolean wip) { return this; } - public RuntimeOptionsBuilder addDefaultSummaryPrinterIfNotPresent() { - parsedPluginData.addDefaultSummaryPrinterIfNotPresent(); + public RuntimeOptionsBuilder addDefaultSummaryPrinterIfAbsent() { + this.addDefaultSummaryPrinterIfAbsent = true; + return this; + } + + public RuntimeOptionsBuilder addDefaultFormatterIfAbsent() { + this.addDefaultFormatterIfAbsent = true; return this; } - public RuntimeOptionsBuilder addDefaultFormatterIfNotPresent() { - parsedPluginData.addDefaultFormatterIfNotPresent(); + + public RuntimeOptionsBuilder addUndefinedStepsSummaryPrinterIfAbsent() { + this.addUndefinedStepsSummaryPrinterIfAbsent = true; return this; } @@ -214,18 +235,6 @@ void addPluginName(String name, boolean isAddPlugin) { } } - void addDefaultSummaryPrinterIfNotPresent() { - if (summaryPrinters.names.isEmpty()) { - addPluginName("summary", false); - } - } - - void addDefaultFormatterIfNotPresent() { - if (formatters.names.isEmpty()) { - addPluginName("progress", false); - } - } - void updateFormatters(List formatter) { this.formatters.updateNameList(formatter); } diff --git a/core/src/main/java/io/cucumber/core/plugin/UndefinedStepsPrinter.java b/core/src/main/java/io/cucumber/core/plugin/UndefinedStepsPrinter.java new file mode 100644 index 0000000000..683fad0b9b --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/UndefinedStepsPrinter.java @@ -0,0 +1,55 @@ +package io.cucumber.core.plugin; + +import io.cucumber.plugin.EventListener; +import io.cucumber.plugin.StrictAware; +import io.cucumber.plugin.event.EventPublisher; +import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.TestRunFinished; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Work around for + */ +public class UndefinedStepsPrinter implements EventListener, StrictAware { + + private final PrintStream out; + private final List snippets = new ArrayList<>(); + private boolean strict; + + public UndefinedStepsPrinter() { + this.out = System.out; + } + + @Override + public void setStrict(boolean strict) { + this.strict = strict; + } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(SnippetsSuggestedEvent.class, this::handleSnippetSuggestedEvent); + publisher.registerHandlerFor(TestRunFinished.class, this::handleTestRunFinishedEvent); + } + + private void handleSnippetSuggestedEvent(SnippetsSuggestedEvent event) { + snippets.addAll(event.getSnippets()); + } + + private void handleTestRunFinishedEvent(TestRunFinished event) { + if (strict) { + return; + } + + if (!snippets.isEmpty()) { + out.append("\n"); + out.println("There were undefined steps. You can implement missing steps with the snippets below:"); + out.println(); + for (String snippet : snippets) { + out.println(snippet); + } + } + } +} diff --git a/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java index b8bbb977b2..e1c05d385c 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java @@ -2,11 +2,11 @@ import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.exception.CucumberException; -import io.cucumber.plugin.Plugin; import io.cucumber.core.plugin.PluginFactory; import io.cucumber.core.plugin.Plugins; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.snippets.SnippetType; +import io.cucumber.plugin.Plugin; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -50,8 +50,8 @@ void create_non_strict() { void create_without_options() { RuntimeOptions runtimeOptions = parser() .parse(WithoutOptions.class) - .addDefaultSummaryPrinterIfNotPresent() - .addDefaultFormatterIfNotPresent() + .addDefaultSummaryPrinterIfAbsent() + .addDefaultFormatterIfAbsent() .build(); assertAll("Checking RuntimeOptions", @@ -80,8 +80,8 @@ void create_without_options_with_base_class_without_options() { Class subClassWithMonoChromeTrueClass = WithoutOptionsWithBaseClassWithoutOptions.class; RuntimeOptions runtimeOptions = parser() .parse(subClassWithMonoChromeTrueClass) - .addDefaultFormatterIfNotPresent() - .addDefaultSummaryPrinterIfNotPresent() + .addDefaultFormatterIfAbsent() + .addDefaultSummaryPrinterIfAbsent() .build(); Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); @@ -140,7 +140,7 @@ private String getRegexpPattern(Object pattern) { void create_default_summary_printer_when_no_summary_printer_plugin_is_defined() { RuntimeOptions runtimeOptions = parser() .parse(ClassWithNoSummaryPrinterPlugin.class) - .addDefaultSummaryPrinterIfNotPresent() + .addDefaultSummaryPrinterIfAbsent() .build(); Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); diff --git a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java index 99766f9f2b..1eb663652d 100644 --- a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java +++ b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java @@ -165,7 +165,7 @@ void creates_html_formatter() { void creates_progress_formatter_as_default() { RuntimeOptions options = new CommandlineOptionsParser() .parse() - .addDefaultFormatterIfNotPresent() + .addDefaultFormatterIfAbsent() .build(); Plugins plugins = new Plugins(new PluginFactory(), options); plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); @@ -177,7 +177,7 @@ void creates_progress_formatter_as_default() { void creates_default_summary_printer_when_no_summary_printer_plugin_is_specified() { RuntimeOptions options = new CommandlineOptionsParser() .parse("--plugin", "pretty") - .addDefaultSummaryPrinterIfNotPresent() + .addDefaultSummaryPrinterIfAbsent() .build(); Plugins plugins = new Plugins(new PluginFactory(), options); plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC())); diff --git a/junit/src/main/java/io/cucumber/junit/Cucumber.java b/junit/src/main/java/io/cucumber/junit/Cucumber.java index 587d34f544..f3d300a0f5 100644 --- a/junit/src/main/java/io/cucumber/junit/Cucumber.java +++ b/junit/src/main/java/io/cucumber/junit/Cucumber.java @@ -114,6 +114,7 @@ public Cucumber(Class clazz) throws InitializationError { RuntimeOptions runtimeOptions = new CucumberPropertiesParser() .parse(CucumberProperties.fromSystemProperties()) + .addUndefinedStepsSummaryPrinterIfAbsent() .build(environmentOptions); // Next parse the junit options diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java index 732bb51bfc..6974407b3b 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java @@ -85,6 +85,7 @@ public TestNGCucumberRunner(Class clazz) { runtimeOptions = new CucumberPropertiesParser() .parse(CucumberProperties.fromSystemProperties()) + .addUndefinedStepsSummaryPrinterIfAbsent() .build(environmentOptions); ClassLoader classLoader = clazz.getClassLoader();