diff --git a/core/pom.xml b/core/pom.xml index 952a2662b4..32ab3772c6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,18 +28,11 @@ io.cucumber - cucumber-gherkin-vintage - - - - io.cucumber - gherkin - ${gherkin-vintage.version} + cucumber-gherkin-messages io.cucumber - gherkin-jvm-deps - ${gherkin-vintage-jvm-deps.version} + messages io.cucumber @@ -61,6 +54,10 @@ io.cucumber docstring + + io.cucumber + html-formatter + org.apiguardian apiguardian-api diff --git a/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java index 50d5c4dd46..4cd9ca7ea5 100644 --- a/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java +++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java @@ -1,16 +1,14 @@ package io.cucumber.core.eventbus; -import io.cucumber.plugin.event.Event; - public abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus { @Override - public void send(Event event) { + public void send(T event) { super.send(event); } @Override - public void sendAll(Iterable queue) { + public void sendAll(Iterable queue) { super.sendAll(queue); } } diff --git a/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java index b280773dcc..ddd12f88e7 100644 --- a/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java +++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java @@ -10,10 +10,10 @@ import java.util.Map; public abstract class AbstractEventPublisher implements EventPublisher { - protected final Map, List> handlers = new HashMap<>(); + protected final Map, List> handlers = new HashMap<>(); @Override - public final void registerHandlerFor(Class eventType, EventHandler handler) { + public final void registerHandlerFor(Class eventType, EventHandler handler) { if (handlers.containsKey(eventType)) { handlers.get(eventType).add(handler); } else { @@ -24,15 +24,15 @@ public final void registerHandlerFor(Class eventType, Event } @Override - public final void removeHandlerFor(Class eventType, EventHandler handler) { + public final void removeHandlerFor(Class eventType, EventHandler handler) { if (handlers.containsKey(eventType)) { handlers.get(eventType).remove(handler); } } - protected void send(Event event) { - if (handlers.containsKey(Event.class)) { + protected void send(T event) { + if (handlers.containsKey(Event.class) && event instanceof Event) { for (EventHandler handler : handlers.get(Event.class)) { //noinspection unchecked: protected by registerHandlerFor handler.receive(event); @@ -47,8 +47,8 @@ protected void send(Event event) { } } - protected void sendAll(Iterable events) { - for (Event event : events) { + protected void sendAll(Iterable events) { + for (T event : events) { send(event); } } diff --git a/core/src/main/java/io/cucumber/core/eventbus/EventBus.java b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java index 4df0859a1a..8ef9384b33 100644 --- a/core/src/main/java/io/cucumber/core/eventbus/EventBus.java +++ b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java @@ -3,7 +3,6 @@ import java.time.Instant; import java.util.UUID; -import io.cucumber.plugin.event.Event; import io.cucumber.plugin.event.EventPublisher; public interface EventBus extends EventPublisher { @@ -12,8 +11,8 @@ public interface EventBus extends EventPublisher { UUID generateId(); - void send(Event event); + void send(T event); - void sendAll(Iterable queue); + void sendAll(Iterable queue); } 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 0119d89bd5..c1ec53b9bd 100644 --- a/core/src/main/java/io/cucumber/core/options/PluginOption.java +++ b/core/src/main/java/io/cucumber/core/options/PluginOption.java @@ -4,9 +4,9 @@ import io.cucumber.core.logging.Logger; import io.cucumber.core.logging.LoggerFactory; import io.cucumber.core.plugin.DefaultSummaryPrinter; -import io.cucumber.core.plugin.HTMLFormatter; -import io.cucumber.core.plugin.JSONFormatter; +import io.cucumber.core.plugin.HtmlFormatter; import io.cucumber.core.plugin.JUnitFormatter; +import io.cucumber.core.plugin.MessageFormatter; import io.cucumber.core.plugin.NullSummaryPrinter; import io.cucumber.core.plugin.Options; import io.cucumber.core.plugin.PrettyFormatter; @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -33,23 +34,35 @@ public class PluginOption implements Options.Plugin { private static final Logger log = LoggerFactory.getLogger(PluginOption.class); private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)"); - private static final HashMap> PLUGIN_CLASSES = new HashMap>() {{ - 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("rerun", RerunFormatter.class); - put("summary", DefaultSummaryPrinter.class); - put("testng", TestNGFormatter.class); - put("timeline", TimelineFormatter.class); - put("unused", UnusedStepsSummaryPrinter.class); - put("usage", UsageFormatter.class); - put("teamcity", TeamCityPlugin.class); + private static final HashMap>> PLUGIN_CLASSES = new HashMap>>() {{ + put("default_summary", () -> DefaultSummaryPrinter.class); + put("html", () -> HtmlFormatter.class); + put("json", () -> loadClassFromGherkinVintage("io.cucumber.core.gherkin.vintage.JsonFormatter")); + put("junit", () -> JUnitFormatter.class); + put("null_summary", () -> NullSummaryPrinter.class); + put("pretty", () -> PrettyFormatter.class); + put("progress", () -> ProgressFormatter.class); + put("message", () -> MessageFormatter.class); + put("rerun", () -> RerunFormatter.class); + put("summary", () -> DefaultSummaryPrinter.class); + put("testng", () -> TestNGFormatter.class); + put("timeline", () -> TimelineFormatter.class); + put("unused", () -> UnusedStepsSummaryPrinter.class); + put("usage", () -> UsageFormatter.class); + put("teamcity", () -> TeamCityPlugin.class); }}; + private static Class loadClassFromGherkinVintage(String className) { + try { + Class aClass = Thread.currentThread().getContextClassLoader().loadClass(className); + return (Class) aClass; + } catch (ClassNotFoundException | NoClassDefFoundError e) { + throw new CucumberException("" + + "Couldn't load plugin class: " + className + "\n" + + "Make sure `cucumber-gherkin-vintage` is available on the classpath", e); + } + } + // Replace IDEA plugin with TeamCity private static final Set INCOMPATIBLE_INTELLIJ_IDEA_PLUGIN_CLASSES = new HashSet() {{ add("org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter"); @@ -77,6 +90,29 @@ private PluginOption(String pluginString, Class pluginClass, S this.argument = argument; } + @Override + public Class pluginClass() { + return pluginClass; + } + + @Override + public String argument() { + return argument; + } + + @Override + public String pluginString() { + return pluginString; + } + + boolean isFormatter() { + return EventListener.class.isAssignableFrom(pluginClass) || ConcurrentEventListener.class.isAssignableFrom(pluginClass); + } + + boolean isSummaryPrinter() { + return SummaryPrinter.class.isAssignableFrom(pluginClass); + } + public static PluginOption parse(String pluginArgumentPattern) { Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(pluginArgumentPattern); if (!pluginWithFile.matches()) { @@ -97,7 +133,7 @@ private static Class parsePluginName(String pluginName) { return TeamCityPlugin.class; } - Class pluginClass = PLUGIN_CLASSES.get(pluginName); + Class pluginClass = PLUGIN_CLASSES.get(pluginName).get(); if (pluginClass == null) { pluginClass = loadClass(pluginName); } @@ -118,28 +154,5 @@ private static Class loadClass(String className) { } } - @Override - public Class pluginClass() { - return pluginClass; - } - - @Override - public String argument() { - return argument; - } - - @Override - public String pluginString() { - return pluginString; - } - - boolean isFormatter() { - return EventListener.class.isAssignableFrom(pluginClass) || ConcurrentEventListener.class.isAssignableFrom(pluginClass); - } - - boolean isSummaryPrinter() { - return SummaryPrinter.class.isAssignableFrom(pluginClass); - } - } diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java index 31f0145967..a2ad4699d1 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java @@ -1,12 +1,12 @@ package io.cucumber.core.options; -import gherkin.GherkinDialect; -import gherkin.GherkinDialectProvider; -import gherkin.IGherkinDialectProvider; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.feature.FeatureWithLines; import io.cucumber.core.feature.GluePath; import io.cucumber.datatable.DataTable; +import io.cucumber.gherkin.GherkinDialect; +import io.cucumber.gherkin.GherkinDialectProvider; +import io.cucumber.gherkin.IGherkinDialectProvider; import java.io.BufferedReader; import java.io.InputStream; diff --git a/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java deleted file mode 100644 index f9d3cc9ba3..0000000000 --- a/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java +++ /dev/null @@ -1,516 +0,0 @@ -package io.cucumber.core.plugin; - -import gherkin.ast.Background; -import gherkin.ast.DataTable; -import gherkin.ast.DocString; -import gherkin.ast.Examples; -import gherkin.ast.Feature; -import gherkin.ast.Node; -import gherkin.ast.ScenarioDefinition; -import gherkin.ast.ScenarioOutline; -import gherkin.ast.Step; -import gherkin.ast.TableCell; -import gherkin.ast.TableRow; -import gherkin.ast.Tag; -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; -import io.cucumber.core.exception.CucumberException; -import io.cucumber.plugin.EventListener; -import io.cucumber.plugin.event.DataTableArgument; -import io.cucumber.plugin.event.DocStringArgument; -import io.cucumber.plugin.event.EmbedEvent; -import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.HookTestStep; -import io.cucumber.plugin.event.HookType; -import io.cucumber.plugin.event.PickleStepTestStep; -import io.cucumber.plugin.event.Result; -import io.cucumber.plugin.event.StepArgument; -import io.cucumber.plugin.event.TestCase; -import io.cucumber.plugin.event.TestCaseStarted; -import io.cucumber.plugin.event.TestRunFinished; -import io.cucumber.plugin.event.TestSourceRead; -import io.cucumber.plugin.event.TestStepFinished; -import io.cucumber.plugin.event.TestStepStarted; -import io.cucumber.plugin.event.WriteEvent; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.URI; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Locale.ROOT; - -public final class HTMLFormatter implements EventListener { - private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private static final String JS_FORMATTER_VAR = "formatter"; - private static final String JS_REPORT_FILENAME = "report.js"; - private static final String[] TEXT_ASSETS = new String[]{ - "/io/cucumber/core/plugin/html/formatter.js", - "/io/cucumber/core/plugin/html/index.html", - "/io/cucumber/core/plugin/html/jquery-3.4.1.min.js", - "/io/cucumber/core/plugin/html/style.css" - }; - private static final Map MIME_TYPES_EXTENSIONS = new HashMap() { - { - put("image/bmp", "bmp"); - put("image/gif", "gif"); - put("image/jpeg", "jpg"); - put("image/png", "png"); - put("image/svg+xml", "svg"); - put("video/ogg", "ogg"); - } - }; - - private final TestSourcesModel testSources = new TestSourcesModel(); - private final URL htmlReportDir; - private final NiceAppendable jsOut; - - private boolean firstFeature = true; - private URI currentFeatureFile; - private Map currentTestCaseMap; - private ScenarioOutline currentScenarioOutline; - private Examples currentExamples; - private int embeddedIndex; - - @SuppressWarnings("WeakerAccess") // Used by PluginFactory - public HTMLFormatter(URL htmlReportDir) { - this(htmlReportDir, createJsOut(htmlReportDir)); - } - - HTMLFormatter(URL htmlReportDir, NiceAppendable jsOut) { - this.htmlReportDir = htmlReportDir; - this.jsOut = jsOut; - } - - @Override - public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestSourceRead.class, this::handleTestSourceRead); - publisher.registerHandlerFor(TestCaseStarted.class, this::handleTestCaseStarted); - publisher.registerHandlerFor(TestStepStarted.class, this::handleTestStepStarted); - publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished); - publisher.registerHandlerFor(EmbedEvent.class, this::handleEmbed); - publisher.registerHandlerFor(WriteEvent.class, this::handleWrite); - publisher.registerHandlerFor(TestRunFinished.class, event -> finishReport()); - } - - private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.getUri(), event); - } - - private void handleTestCaseStarted(TestCaseStarted event) { - if (firstFeature) { - jsOut.append("$(document).ready(function() {").append("var ") - .append(JS_FORMATTER_VAR).append(" = new CucumberHTML.DOMFormatter($('.cucumber-report'));"); - firstFeature = false; - } - handleStartOfFeature(event.getTestCase()); - handleScenarioOutline(event.getTestCase()); - currentTestCaseMap = createTestCase(event.getTestCase()); - if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLine())) { - jsFunctionCall("background", createBackground(event.getTestCase())); - } else { - jsFunctionCall("scenario", currentTestCaseMap); - currentTestCaseMap = null; - } - } - - private void handleTestStepStarted(TestStepStarted event) { - if (event.getTestStep() instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); - if (isFirstStepAfterBackground(testStep)) { - jsFunctionCall("scenario", currentTestCaseMap); - currentTestCaseMap = null; - } - jsFunctionCall("step", createTestStep(testStep)); - jsFunctionCall("match", createMatchMap((PickleStepTestStep) event.getTestStep())); - } - } - - private void handleTestStepFinished(TestStepFinished event) { - if (event.getTestStep() instanceof PickleStepTestStep) { - jsFunctionCall("result", createResultMap(event.getResult())); - } else if (event.getTestStep() instanceof HookTestStep) { - HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); - jsFunctionCall(getFunctionName(hookTestStep), createResultMap(event.getResult())); - } else { - throw new IllegalStateException(); - } - } - - private String getFunctionName(HookTestStep hookTestStep) { - HookType hookType = hookTestStep.getHookType(); - switch (hookType) { - case BEFORE: - return "before"; - case AFTER: - return "after"; - case BEFORE_STEP: - return "beforestep"; - case AFTER_STEP: - return "afterstep"; - default: - throw new IllegalArgumentException(hookType.name()); - } - } - - private void handleEmbed(EmbedEvent event) { - String mediaType = event.getMediaType(); - if (mediaType.startsWith("text/")) { - // just pass straight to the plugin to output in the html - jsFunctionCall("embedding", mediaType, new String(event.getData()), event.getName()); - } else { - // Creating a file instead of using data urls to not clutter the js file - String extension = MIME_TYPES_EXTENSIONS.get(mediaType); - if (extension != null) { - StringBuilder fileName = new StringBuilder("embedded").append(embeddedIndex++).append(".").append(extension); - writeBytesToURL(event.getData(), toUrl(fileName.toString())); - jsFunctionCall("embedding", mediaType, fileName, event.getName()); - } - } - } - - private void handleWrite(WriteEvent event) { - jsFunctionCall("write", event.getText()); - } - - private void finishReport() { - if (!firstFeature) { - jsOut.append("});"); - copyReportFiles(); - } - jsOut.close(); - } - - private void handleStartOfFeature(TestCase testCase) { - if (currentFeatureFile == null || !currentFeatureFile.equals(testCase.getUri())) { - currentFeatureFile = testCase.getUri(); - jsFunctionCall("uri", TestSourcesModel.relativize(currentFeatureFile)); - jsFunctionCall("feature", createFeature(testCase)); - } - } - - private Map createFeature(TestCase testCase) { - Map featureMap = new HashMap<>(); - Feature feature = testSources.getFeature(testCase.getUri()); - if (feature != null) { - featureMap.put("keyword", feature.getKeyword()); - featureMap.put("name", feature.getName()); - featureMap.put("description", feature.getDescription() != null ? feature.getDescription() : ""); - if (!feature.getTags().isEmpty()) { - featureMap.put("tags", createTagList(feature.getTags())); - } - } - return featureMap; - } - - private List> createTagList(List tags) { - List> tagList = new ArrayList<>(); - for (Tag tag : tags) { - Map tagMap = new HashMap<>(); - tagMap.put("name", tag.getName()); - tagList.add(tagMap); - } - return tagList; - } - - private void handleScenarioOutline(TestCase testCase) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); - if (TestSourcesModel.isScenarioOutlineScenario(astNode)) { - ScenarioOutline scenarioOutline = (ScenarioOutline) TestSourcesModel.getScenarioDefinition(astNode); - if (currentScenarioOutline == null || !currentScenarioOutline.equals(scenarioOutline)) { - currentScenarioOutline = scenarioOutline; - jsFunctionCall("scenarioOutline", createScenarioOutline(currentScenarioOutline)); - addOutlineStepsToReport(scenarioOutline); - } - Examples examples = (Examples) astNode.parent.node; - if (currentExamples == null || !currentExamples.equals(examples)) { - currentExamples = examples; - jsFunctionCall("examples", createExamples(currentExamples)); - } - } else { - currentScenarioOutline = null; - currentExamples = null; - } - } - - private Map createScenarioOutline(ScenarioOutline scenarioOutline) { - Map scenarioOutlineMap = new HashMap<>(); - scenarioOutlineMap.put("name", scenarioOutline.getName()); - scenarioOutlineMap.put("keyword", scenarioOutline.getKeyword()); - scenarioOutlineMap.put("description", scenarioOutline.getDescription() != null ? scenarioOutline.getDescription() : ""); - if (!scenarioOutline.getTags().isEmpty()) { - scenarioOutlineMap.put("tags", createTagList(scenarioOutline.getTags())); - } - return scenarioOutlineMap; - } - - private void addOutlineStepsToReport(ScenarioOutline scenarioOutline) { - for (Step step : scenarioOutline.getSteps()) { - Map stepMap = new HashMap<>(); - stepMap.put("name", step.getText()); - stepMap.put("keyword", step.getKeyword()); - if (step.getArgument() != null) { - Node argument = step.getArgument(); - if (argument instanceof DocString) { - stepMap.put("doc_string", createDocStringMap((DocString) argument)); - } else if (argument instanceof DataTable) { - stepMap.put("rows", createDataTableList((DataTable) argument)); - } - } - jsFunctionCall("step", stepMap); - } - } - - private Map createDocStringMap(DocString docString) { - Map docStringMap = new HashMap<>(); - docStringMap.put("value", docString.getContent()); - return docStringMap; - } - - private List> createDataTableList(DataTable dataTable) { - List> rowList = new ArrayList<>(); - for (TableRow row : dataTable.getRows()) { - rowList.add(createRowMap(row)); - } - return rowList; - } - - private Map createRowMap(TableRow row) { - Map rowMap = new HashMap<>(); - rowMap.put("cells", createCellList(row)); - return rowMap; - } - - private List createCellList(TableRow row) { - List cells = new ArrayList<>(); - for (TableCell cell : row.getCells()) { - cells.add(cell.getValue()); - } - return cells; - } - - private Map createExamples(Examples examples) { - Map examplesMap = new HashMap<>(); - examplesMap.put("name", examples.getName()); - examplesMap.put("keyword", examples.getKeyword()); - examplesMap.put("description", examples.getDescription() != null ? examples.getDescription() : ""); - List> rowList = new ArrayList<>(); - rowList.add(createRowMap(examples.getTableHeader())); - for (TableRow row : examples.getTableBody()) { - rowList.add(createRowMap(row)); - } - examplesMap.put("rows", rowList); - if (!examples.getTags().isEmpty()) { - examplesMap.put("tags", createTagList(examples.getTags())); - } - return examplesMap; - } - - private Map createTestCase(TestCase testCase) { - Map testCaseMap = new HashMap<>(); - testCaseMap.put("name", testCase.getName()); - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); - if (astNode != null) { - ScenarioDefinition scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode); - testCaseMap.put("keyword", scenarioDefinition.getKeyword()); - testCaseMap.put("description", scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : ""); - } - if (!testCase.getTags().isEmpty()) { - List> tagList = new ArrayList<>(); - for (String tag : testCase.getTags()) { - Map tagMap = new HashMap<>(); - tagMap.put("name", tag); - tagList.add(tagMap); - } - testCaseMap.put("tags", tagList); - } - return testCaseMap; - } - - private Map createBackground(TestCase testCase) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); - if (astNode != null) { - Background background = TestSourcesModel.getBackgroundForTestCase(astNode); - Map testCaseMap = new HashMap<>(); - testCaseMap.put("name", background.getName()); - testCaseMap.put("keyword", background.getKeyword()); - testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); - return testCaseMap; - } - return null; - } - - private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - if (astNode != null) { - return currentTestCaseMap != null && !TestSourcesModel.isBackgroundStep(astNode); - } - return false; - } - - private Map createTestStep(PickleStepTestStep testStep) { - Map stepMap = new HashMap<>(); - stepMap.put("name", testStep.getStepText()); - StepArgument argument = testStep.getStepArgument(); - if (argument != null) { - if (argument instanceof DocStringArgument) { - DocStringArgument docStringArgument = (DocStringArgument) argument; - stepMap.put("doc_string", createDocStringMap(docStringArgument)); - } else if (argument instanceof DataTableArgument) { - DataTableArgument dataTableArgument = (DataTableArgument) argument; - stepMap.put("rows", createDataTableList(dataTableArgument)); - } - } - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - if (astNode != null) { - Step step = (Step) astNode.node; - stepMap.put("keyword", step.getKeyword()); - } - - return stepMap; - } - - private Map createDocStringMap(DocStringArgument docString) { - Map docStringMap = new HashMap<>(); - docStringMap.put("value", docString.getContent()); - return docStringMap; - } - - private List> createDataTableList(DataTableArgument dataTable) { - List> rowList = new ArrayList<>(); - for (List row : dataTable.cells()) { - rowList.add(createRowMap(row)); - } - return rowList; - } - - private Map createRowMap(List row) { - Map rowMap = new HashMap<>(); - rowMap.put("cells", row); - return rowMap; - } - - private Map createMatchMap(PickleStepTestStep testStep) { - Map matchMap = new HashMap<>(); - String location = testStep.getCodeLocation(); - if (location != null) { - matchMap.put("location", location); - } - return matchMap; - } - - private Map createResultMap(Result result) { - Map resultMap = new HashMap<>(); - resultMap.put("status", result.getStatus().name().toLowerCase(ROOT)); - if (result.getError() != null) { - resultMap.put("error_message", printStackTrace(result.getError())); - } - return resultMap; - } - - private void jsFunctionCall(String functionName, Object... args) { - NiceAppendable out = jsOut.append(JS_FORMATTER_VAR + ".").append(functionName).append("("); - boolean comma = false; - for (Object arg : args) { - if (comma) { - out.append(", "); - } - gson.toJson(arg, out); - comma = true; - } - out.append(");").println(); - } - - private void copyReportFiles() { - if (htmlReportDir == null) { - return; - } - for (String textAsset : TEXT_ASSETS) { - InputStream textAssetStream = getClass().getResourceAsStream(textAsset); - if (textAssetStream == null) { - throw new CucumberException("Couldn't find " + textAsset + ". Is cucumber-html on your classpath? Make sure you have the right version."); - } - String fileName = new File(textAsset).getName(); - writeStreamToURL(textAssetStream, toUrl(fileName)); - } - } - - private static String printStackTrace(Throwable error) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - error.printStackTrace(printWriter); - return stringWriter.toString(); - } - - private URL toUrl(String fileName) { - try { - return new URL(htmlReportDir, fileName); - } catch (IOException e) { - throw new CucumberException(e); - } - } - - private static void writeStreamToURL(InputStream in, URL url) { - OutputStream out = createReportFileOutputStream(url); - - byte[] buffer = new byte[16 * 1024]; - try { - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - } catch (IOException e) { - throw new CucumberException("Unable to write to report file item: ", e); - } finally { - closeQuietly(out); - } - } - - private static void writeBytesToURL(byte[] buf, URL url) throws CucumberException { - OutputStream out = createReportFileOutputStream(url); - try { - out.write(buf); - } catch (IOException e) { - throw new CucumberException("Unable to write to report file item: ", e); - } finally { - closeQuietly(out); - } - } - - private static NiceAppendable createJsOut(URL htmlReportDir) { - try { - return new NiceAppendable(new OutputStreamWriter(createReportFileOutputStream(new URL(htmlReportDir, JS_REPORT_FILENAME)), UTF_8)); - } catch (IOException e) { - throw new CucumberException(e); - } - } - - private static OutputStream createReportFileOutputStream(URL url) { - try { - return new URLOutputStream(url); - } catch (IOException e) { - throw new CucumberException(e); - } - } - - private static void closeQuietly(Closeable out) { - try { - out.close(); - } catch (IOException ignored) { - // go gentle into that good night - } - } - -} diff --git a/core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java b/core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java new file mode 100644 index 0000000000..c0b78a7e5a --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java @@ -0,0 +1,43 @@ +package io.cucumber.core.plugin; + +import io.cucumber.htmlformatter.MessagesToHtmlWriter; +import io.cucumber.messages.Messages.Envelope; +import io.cucumber.plugin.ConcurrentEventListener; +import io.cucumber.plugin.event.EventPublisher; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +public final class HtmlFormatter implements ConcurrentEventListener { + + private final MessagesToHtmlWriter writer; + + @SuppressWarnings("WeakerAccess") // Used by PluginFactory + public HtmlFormatter(OutputStream out) throws IOException { + this.writer = new MessagesToHtmlWriter(new OutputStreamWriter(out)); + } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(Envelope.class, this::write); + } + + private void write(Envelope event) { + try { + writer.write(event); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + // TODO: Plugins should implement the closable interface + // and be closed by Cucumber + if (event.hasTestRunFinished()) { + try { + writer.close(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/core/src/main/java/io/cucumber/core/plugin/MessageFormatter.java b/core/src/main/java/io/cucumber/core/plugin/MessageFormatter.java new file mode 100644 index 0000000000..841a8015d1 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/plugin/MessageFormatter.java @@ -0,0 +1,42 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.Messages.Envelope; +import io.cucumber.messages.internal.com.google.protobuf.util.JsonFormat; +import io.cucumber.plugin.EventListener; +import io.cucumber.plugin.event.EventPublisher; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +public final class MessageFormatter implements EventListener { + private final Writer writer; + private final JsonFormat.Printer jsonPrinter = JsonFormat.printer() + .omittingInsignificantWhitespace(); + + public MessageFormatter(OutputStream outputStream) { + this.writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); + } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(Envelope.class, this::writeMessage); + } + + private void writeMessage(Envelope envelope) { + try { + jsonPrinter.appendTo(envelope, writer); + writer.write("\n"); + writer.flush(); + if (envelope.hasTestRunFinished()) { + writer.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} + diff --git a/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java b/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java index 13c7541d89..8df09bdd1b 100644 --- a/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java +++ b/core/src/main/java/io/cucumber/core/plugin/PluginFactory.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -24,7 +25,7 @@ * @see Plugin for specific requirements */ public final class PluginFactory { - private final Class[] CTOR_PARAMETERS = new Class[]{String.class, Appendable.class, URI.class, URL.class, File.class}; + private final Class[] CTOR_PARAMETERS = new Class[]{String.class, Appendable.class, URI.class, URL.class, File.class, OutputStream.class}; private String defaultOutFormatter = null; @@ -109,6 +110,9 @@ private Object convertOrNull(String arg, Class ctorArgClass, String formatter if (ctorArgClass.equals(Appendable.class)) { return new UTF8OutputStreamWriter(new URLOutputStream(toURL(arg))); } + if (ctorArgClass.equals(OutputStream.class)) { + return new URLOutputStream(toURL(arg)); + } return null; } diff --git a/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java b/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java index 4dc43623e4..fa176787a2 100644 --- a/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java @@ -15,17 +15,19 @@ import io.cucumber.plugin.event.WriteEvent; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import static io.cucumber.core.plugin.TestSourcesModel.relativize; import static java.lang.Math.max; import static java.util.Locale.ROOT; @@ -225,4 +227,21 @@ private static String formatStackTrace(Throwable error) { return stringWriter.toString(); } + static URI relativize(URI uri) { + if (!"file".equals(uri.getScheme())) { + return uri; + } + if (!uri.isAbsolute()) { + return uri; + } + + try { + URI root = new File("").toURI(); + URI relative = root.relativize(uri); + // Scheme is lost by relativize + return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } } diff --git a/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java b/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java index 5b90751d0b..21faed183c 100644 --- a/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java @@ -15,7 +15,7 @@ import java.util.Map; import static io.cucumber.core.feature.FeatureWithLines.create; -import static io.cucumber.core.plugin.TestSourcesModel.relativize; +import static io.cucumber.core.plugin.PrettyFormatter.relativize; /** * Formatter for reporting all failed test cases and print their locations diff --git a/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java b/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java index 9296e3e12d..a3bf05cd0f 100644 --- a/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java @@ -1,10 +1,10 @@ package io.cucumber.core.plugin; -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; -import gherkin.deps.com.google.gson.annotations.SerializedName; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.feature.FeatureParser; +import io.cucumber.messages.internal.com.google.gson.Gson; +import io.cucumber.messages.internal.com.google.gson.GsonBuilder; +import io.cucumber.messages.internal.com.google.gson.annotations.SerializedName; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; import io.cucumber.plugin.event.TestCase; diff --git a/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java b/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java index dbc95b2e8e..04cf5348b2 100644 --- a/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java +++ b/core/src/main/java/io/cucumber/core/plugin/URLOutputStream.java @@ -1,6 +1,6 @@ package io.cucumber.core.plugin; -import gherkin.deps.com.google.gson.Gson; +import io.cucumber.messages.internal.com.google.gson.Gson; import java.io.BufferedReader; import java.io.File; diff --git a/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java b/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java index 81a53ce86d..55f0c5a05a 100644 --- a/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/UsageFormatter.java @@ -1,9 +1,9 @@ package io.cucumber.core.plugin; -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; -import gherkin.deps.com.google.gson.JsonPrimitive; -import gherkin.deps.com.google.gson.JsonSerializer; +import io.cucumber.messages.internal.com.google.gson.Gson; +import io.cucumber.messages.internal.com.google.gson.GsonBuilder; +import io.cucumber.messages.internal.com.google.gson.JsonPrimitive; +import io.cucumber.messages.internal.com.google.gson.JsonSerializer; import io.cucumber.plugin.event.EventPublisher; import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; diff --git a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java index 9244b3a93a..31d99b8010 100644 --- a/core/src/main/java/io/cucumber/core/runner/CachingGlue.java +++ b/core/src/main/java/io/cucumber/core/runner/CachingGlue.java @@ -11,12 +11,15 @@ import io.cucumber.core.backend.ScenarioScoped; import io.cucumber.core.backend.StepDefinition; import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.exception.CucumberException; import io.cucumber.core.gherkin.Step; import io.cucumber.core.stepexpression.Argument; import io.cucumber.core.stepexpression.StepTypeRegistry; import io.cucumber.cucumberexpressions.ParameterByTypeTransformer; +import io.cucumber.cucumberexpressions.UndefinedParameterTypeException; import io.cucumber.datatable.TableCellByTypeTransformer; import io.cucumber.datatable.TableEntryByTypeTransformer; +import io.cucumber.messages.Messages; import io.cucumber.plugin.event.StepDefinedEvent; import java.net.URI; @@ -211,23 +214,67 @@ void prepareGlue(StepTypeRegistry stepTypeRegistry) throws DuplicateStepDefiniti throw new DuplicateDefaultDataTableCellTransformers(defaultDataTableCellTransformers); } + // TODO: Redefine hooks for each scenario, similar to how we're doing for CoreStepDefinition + beforeHooks.forEach(this::emitHook); + stepDefinitions.forEach(stepDefinition -> { - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(bus.generateId(), stepDefinition, stepTypeRegistry); CoreStepDefinition previous = stepDefinitionsByPattern.get(stepDefinition.getPattern()); if (previous != null) { throw new DuplicateStepDefinitionException(previous.getStepDefinition(), stepDefinition); } stepDefinitionsByPattern.put(coreStepDefinition.getPattern(), coreStepDefinition); - bus.send( - new StepDefinedEvent( - bus.getInstant(), - new io.cucumber.plugin.event.StepDefinition( - stepDefinition.getLocation(), - stepDefinition.getPattern() - ) - ) - ); + emitStepDefined(coreStepDefinition); }); + + afterHooks.forEach(this::emitHook); + } + + private CucumberException registerTypeInConfiguration(String expressionString, UndefinedParameterTypeException e) { + return new CucumberException(String.format("" + + "Could not create a cucumber expression for '%s'.\n" + + "It appears you did not register parameter type. The details are in the stacktrace below.\n" + + "You can find the documentation here: https://docs.cucumber.io/cucumber/cucumber-expressions/", + expressionString + ), e); + } + + private void emitHook(CoreHookDefinition hook) { + bus.send(Messages.Envelope.newBuilder() + .setHook(Messages.Hook.newBuilder() + .setId(hook.getId().toString()) + .setTagExpression(hook.getTagExpression()) + .setSourceReference(Messages.SourceReference.newBuilder() + // TODO: Maybe we should add a proper URI prefix here, like "javamethod:....". Maybe there is + // a standard for this + .setUri(hook.getLocation())) + ) + .build() + ); + } + + private void emitStepDefined(CoreStepDefinition stepDefinition) { + bus.send(new StepDefinedEvent( + bus.getInstant(), + new io.cucumber.plugin.event.StepDefinition( + stepDefinition.getStepDefinition().getLocation(), + stepDefinition.getPattern() + ) + ) + ); + bus.send(Messages.Envelope.newBuilder() + .setStepDefinition( + Messages.StepDefinition.newBuilder() + .setId(stepDefinition.getId().toString()) + .setPattern(Messages.StepDefinitionPattern.newBuilder() + .setSource(stepDefinition.getPattern()) + .build()) + .setSourceReference(Messages.SourceReference.newBuilder() + .setUri(stepDefinition.getStepDefinition().getLocation()) + .build()) + .build()) + .build() + ); } PickleStepDefinitionMatch stepDefinitionMatch(URI uri, Step step) throws AmbiguousStepDefinitionsException { diff --git a/core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java b/core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java index b7b74e913e..843bed1f9c 100644 --- a/core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java +++ b/core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java @@ -7,6 +7,9 @@ import io.cucumber.tagexpressions.TagExpressionParser; import java.util.List; +import java.util.UUID; + +import static java.util.Objects.requireNonNull; class CoreHookDefinition { @@ -16,13 +19,15 @@ static CoreHookDefinition create(HookDefinition hookDefinition) { if (hookDefinition instanceof ScenarioScoped) { return new ScenarioScopedCoreHookDefinition(hookDefinition); } - return new CoreHookDefinition(hookDefinition); + return new CoreHookDefinition(UUID.randomUUID(), hookDefinition); } + private final UUID id; private final HookDefinition delegate; private final Expression tagExpression; - private CoreHookDefinition(HookDefinition delegate) { + private CoreHookDefinition(UUID id, HookDefinition delegate) { + this.id = requireNonNull(id); this.delegate = delegate; this.tagExpression = new TagExpressionParser().parse(delegate.getTagExpression()); } @@ -39,6 +44,10 @@ String getLocation() { return delegate.getLocation(); } + UUID getId() { + return id; + } + int getOrder() { return delegate.getOrder(); } @@ -47,9 +56,13 @@ boolean matches(List tags) { return tagExpression.evaluate(tags); } + String getTagExpression() { + return delegate.getTagExpression(); + } + static class ScenarioScopedCoreHookDefinition extends CoreHookDefinition implements ScenarioScoped { private ScenarioScopedCoreHookDefinition(HookDefinition delegate) { - super(delegate); + super(UUID.randomUUID(), delegate); } } diff --git a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java index 5c8653ebf2..e4ae2a8778 100644 --- a/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java +++ b/core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java @@ -11,18 +11,21 @@ import java.lang.reflect.Type; import java.util.List; +import java.util.UUID; import java.util.function.Supplier; import static java.util.Objects.requireNonNull; final class CoreStepDefinition { + private final UUID id; private final StepExpression expression; private final ArgumentMatcher argumentMatcher; private final StepDefinition stepDefinition; private final Type[] types; - CoreStepDefinition(StepDefinition stepDefinition, StepTypeRegistry stepTypeRegistry) { + CoreStepDefinition(UUID id, StepDefinition stepDefinition, StepTypeRegistry stepTypeRegistry) { + this.id = requireNonNull(id); this.stepDefinition = requireNonNull(stepDefinition); List parameterInfos = stepDefinition.parameterInfos(); this.expression = createExpression(parameterInfos, stepDefinition.getPattern(), stepTypeRegistry); @@ -41,11 +44,11 @@ private StepExpression createExpression(List parameterInfos, Stri } } - public String getPattern() { + String getPattern() { return expression.getSource(); } - public StepDefinition getStepDefinition() { + StepDefinition getStepDefinition() { return stepDefinition; } @@ -53,6 +56,10 @@ List matchedArguments(Step step) { return argumentMatcher.argumentsFrom(step, types); } + UUID getId() { + return id; + } + private static Type[] getTypes(List parameterInfos) { if (parameterInfos == null) { return new Type[0]; diff --git a/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java b/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java index 6631782c8e..e12db6033c 100644 --- a/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java +++ b/core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java @@ -4,13 +4,17 @@ import io.cucumber.plugin.event.Argument; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; final class DefinitionArgument implements Argument { + private final ExpressionArgument argument; private final io.cucumber.cucumberexpressions.Group group; private DefinitionArgument(ExpressionArgument argument) { + this.argument = argument; this.group = argument.getGroup(); } @@ -25,6 +29,11 @@ static List createArguments(List children; + + private Group(io.cucumber.cucumberexpressions.Group group) { + this.group = group; + children = group.getChildren().stream() + .map(Group::new) + .collect(Collectors.toList()); + } + + @Override + public Collection getChildren() { + return children; + } + + @Override + public String getValue() { + return group.getValue(); + } + + @Override + public int getStart() { + return group.getStart(); + } + + @Override + public int getEnd() { + return group.getEnd(); + } + } } diff --git a/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java b/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java index 262b4ea41a..79ad3f784d 100644 --- a/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java +++ b/core/src/main/java/io/cucumber/core/runner/HookDefinitionMatch.java @@ -4,8 +4,10 @@ import io.cucumber.core.backend.CucumberInvocationTargetException; import io.cucumber.core.backend.TestCaseState; import io.cucumber.core.exception.CucumberException; +import io.cucumber.messages.Messages; import static io.cucumber.core.runner.StackManipulation.removeFrameworkFrames; +import static java.util.Collections.emptyList; final class HookDefinitionMatch implements StepDefinitionMatch { private final CoreHookDefinition hookDefinition; diff --git a/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java index 7c98dc3837..a2f0310f3e 100644 --- a/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/PickleStepTestStep.java @@ -36,17 +36,17 @@ final class PickleStepTestStep extends TestStep implements io.cucumber.plugin.ev } @Override - boolean run(TestCase testCase, EventBus bus, TestCaseState state, boolean skipSteps, UUID testExecutionId) { + boolean run(TestCase testCase, EventBus bus, TestCaseState state, boolean skipSteps) { boolean skipNextStep = skipSteps; for (HookTestStep before : beforeStepHookSteps) { - skipNextStep |= before.run(testCase, bus, state, skipSteps, testExecutionId); + skipNextStep |= before.run(testCase, bus, state, skipSteps); } - skipNextStep |= super.run(testCase, bus, state, skipNextStep, testExecutionId); + skipNextStep |= super.run(testCase, bus, state, skipNextStep); for (HookTestStep after : afterStepHookSteps) { - skipNextStep |= after.run(testCase, bus, state, skipSteps, testExecutionId); + skipNextStep |= after.run(testCase, bus, state, skipSteps); } return skipNextStep; diff --git a/core/src/main/java/io/cucumber/core/runner/TestCase.java b/core/src/main/java/io/cucumber/core/runner/TestCase.java index 5345dd448b..7ac3b7a34d 100644 --- a/core/src/main/java/io/cucumber/core/runner/TestCase.java +++ b/core/src/main/java/io/cucumber/core/runner/TestCase.java @@ -2,12 +2,19 @@ import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.gherkin.Pickle; +import io.cucumber.messages.Messages; +import io.cucumber.messages.Messages.Envelope; +import io.cucumber.messages.Messages.StepMatchArgument; +import io.cucumber.messages.Messages.TestCase.TestStep.StepMatchArgumentsList; +import io.cucumber.plugin.event.Group; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCaseFinished; import io.cucumber.plugin.event.TestCaseStarted; import io.cucumber.plugin.event.TestStep; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URI; import java.time.Duration; import java.time.Instant; @@ -15,6 +22,12 @@ import java.util.List; import java.util.UUID; +import static io.cucumber.core.runner.TestStepResultStatus.from; +import static io.cucumber.messages.TimeConversion.javaDurationToDuration; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + final class TestCase implements io.cucumber.plugin.event.TestCase { private final Pickle pickle; private final List testSteps; @@ -38,28 +51,31 @@ final class TestCase implements io.cucumber.plugin.event.TestCase { void run(EventBus bus) { boolean skipNextStep = this.dryRun; + emitTestCaseMessage(bus); + Instant start = bus.getInstant(); UUID executionId = bus.generateId(); - bus.send(new TestCaseStarted(start, this)); - TestCaseState state = new TestCaseState(bus, this); + emitTestCaseStarted(bus, start, executionId); + + TestCaseState state = new TestCaseState(bus, executionId, this); for (HookTestStep before : beforeHooks) { - skipNextStep |= before.run(this, bus, state, dryRun, executionId); + skipNextStep |= before.run(this, bus, state, dryRun); } for (PickleStepTestStep step : testSteps) { - skipNextStep |= step.run(this, bus, state, skipNextStep, executionId); + skipNextStep |= step.run(this, bus, state, skipNextStep); } for (HookTestStep after : afterHooks) { - after.run(this, bus, state, dryRun, executionId); + after.run(this, bus, state, dryRun); } Instant stop = bus.getInstant(); Duration duration = Duration.between(start, stop); Status status = Status.valueOf(state.getStatus().name()); Result result = new Result(status, duration, state.getError()); - bus.send(new TestCaseFinished(stop, this, result)); + emitTestCaseFinished(bus, executionId, stop, duration, status, result); } @Override @@ -113,4 +129,97 @@ public List getTags() { return pickle.getTags(); } + private void emitTestCaseMessage(EventBus bus) { + bus.send(Envelope.newBuilder() + .setTestCase(Messages.TestCase.newBuilder() + .setId(id.toString()) + .setPickleId(pickle.getId()) + .addAllTestSteps(getTestSteps() + .stream() + .map(this::createTestStep) + .collect(toList()) + ) + ).build() + ); + } + + private Messages.TestCase.TestStep createTestStep(TestStep testStep) { + Messages.TestCase.TestStep.Builder testStepBuilder = Messages.TestCase.TestStep + .newBuilder() + .setId(testStep.getId().toString()); + + if (testStep instanceof HookTestStep) { + HookTestStep hookTestStep = (HookTestStep) testStep; + testStepBuilder.setHookId(hookTestStep.getId().toString()); + } else if (testStep instanceof PickleStepTestStep) { + PickleStepTestStep pickleStep = (PickleStepTestStep) testStep; + testStepBuilder + .addAllStepDefinitionIds(singletonList(pickleStep.getId().toString())) + .setPickleStepId(pickleStep.getStep().getId()) + .addAllStepMatchArgumentsLists(getStepMatchArguments(pickleStep)); + } + + return testStepBuilder.build(); + } + + public Iterable getStepMatchArguments(PickleStepTestStep pickleStep) { + return pickleStep.getDefinitionArgument().stream() + .map(arg -> StepMatchArgumentsList.newBuilder() + .addStepMatchArguments(StepMatchArgument.newBuilder() + .setParameterTypeName(arg.getParameterTypeName()) + .setGroup(makeMessageGroup(arg.getGroup())) + .build()) + .build() + ).collect(toList()); + } + + private static StepMatchArgument.Group makeMessageGroup(Group group) { + StepMatchArgument.Group.Builder builder = StepMatchArgument.Group.newBuilder(); + if (group == null) { + return builder.build(); + } + + if (group.getValue() != null) { + builder.setValue(group.getValue()); + } + return builder + //TODO: We can't represent undefined / missing matches. + .setStart(group.getStart()) + .addAllChildren(group.getChildren().stream() + .map(TestCase::makeMessageGroup) + .collect(toList())) + .build(); + } + + private void emitTestCaseStarted(EventBus bus, Instant start, UUID executionId) { + bus.send(new TestCaseStarted(start, this)); + bus.send(Envelope.newBuilder() + .setTestCaseStarted(Messages.TestCaseStarted.newBuilder() + .setId(executionId.toString()) + .setTestCaseId(id.toString()) + .setTimestamp(javaInstantToTimestamp(start))).build()); + } + + private void emitTestCaseFinished(EventBus bus, UUID executionId, Instant stop, Duration duration, Status status, Result result) { + bus.send(new TestCaseFinished(stop, this, result)); + Messages.TestStepResult.Builder testResultBuilder = Messages.TestStepResult.newBuilder() + .setStatus(from(status)) + .setDuration(javaDurationToDuration(duration)); + + if (result.getError() != null) { + testResultBuilder.setMessage(toString(result.getError())); + } + + bus.send(Envelope.newBuilder() + .setTestCaseFinished(Messages.TestCaseFinished.newBuilder() + .setTestCaseStartedId(executionId.toString()) + .setTimestamp(javaInstantToTimestamp(stop))) + .build()); + } + + private static String toString(Throwable error) { + StringWriter stringWriter = new StringWriter(); + error.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } } diff --git a/core/src/main/java/io/cucumber/core/runner/TestCaseState.java b/core/src/main/java/io/cucumber/core/runner/TestCaseState.java index 1c4a9086ea..ef70cab440 100644 --- a/core/src/main/java/io/cucumber/core/runner/TestCaseState.java +++ b/core/src/main/java/io/cucumber/core/runner/TestCaseState.java @@ -2,6 +2,8 @@ import io.cucumber.core.backend.Status; import io.cucumber.core.eventbus.EventBus; +import io.cucumber.messages.Messages; +import io.cucumber.messages.internal.com.google.protobuf.ByteString; import io.cucumber.plugin.event.EmbedEvent; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.TestCase; @@ -11,6 +13,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.UUID; import static java.util.Collections.max; import static java.util.Comparator.comparing; @@ -21,9 +24,13 @@ class TestCaseState implements io.cucumber.core.backend.TestCaseState { private final List stepResults = new ArrayList<>(); private final EventBus bus; private final TestCase testCase; + private final UUID testExecutionId; - TestCaseState(EventBus bus, TestCase testCase) { + private UUID currentTestStepId; + + TestCaseState(EventBus bus, UUID testExecutionId, TestCase testCase) { this.bus = requireNonNull(bus); + this.testExecutionId = requireNonNull(testExecutionId); this.testCase = requireNonNull(testCase); } @@ -31,6 +38,10 @@ void add(Result result) { stepResults.add(result); } + UUID getTestExecutionId() { + return testExecutionId; + } + @Override public Collection getSourceTagNames() { return testCase.getTags(); @@ -54,17 +65,40 @@ public boolean isFailed() { @Deprecated @Override public void embed(byte[] data, String mediaType) { - bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mediaType)); + embed(data, mediaType, null); } @Override public void embed(byte[] data, String mediaType, String name) { bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mediaType, name)); + bus.send(Messages.Envelope.newBuilder() + .setAttachment( + Messages.Attachment.newBuilder() + .setTestCaseStartedId(testExecutionId.toString()) + .setTestStepId(currentTestStepId.toString()) + .setBinary(ByteString.copyFrom(data)) + //TODO: Add file name to message protocol + .setMediaType(mediaType) + .build() + ) + .build() + ); } @Override public void write(String text) { bus.send(new WriteEvent(bus.getInstant(), testCase, text)); + bus.send(Messages.Envelope.newBuilder() + .setAttachment( + Messages.Attachment.newBuilder() + .setTestCaseStartedId(testExecutionId.toString()) + .setTestStepId(currentTestStepId.toString()) + .setText(text) + .setMediaType("text/plain") + .build() + ) + .build() + ); } @Override @@ -94,4 +128,13 @@ Throwable getError() { return max(stepResults, comparing(Result::getStatus)).getError(); } + + void setCurrentTestStepId(UUID currentTestStepId) { + this.currentTestStepId = currentTestStepId; + } + + void clearCurrentTestStepId() { + this.currentTestStepId = null; + } + } diff --git a/core/src/main/java/io/cucumber/core/runner/TestStep.java b/core/src/main/java/io/cucumber/core/runner/TestStep.java index 63cbe0ae13..e4fd50d8dc 100644 --- a/core/src/main/java/io/cucumber/core/runner/TestStep.java +++ b/core/src/main/java/io/cucumber/core/runner/TestStep.java @@ -2,17 +2,25 @@ import io.cucumber.core.backend.Pending; import io.cucumber.core.eventbus.EventBus; +import io.cucumber.messages.Messages; +import io.cucumber.messages.Messages.TestStepResult; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestStepFinished; import io.cucumber.plugin.event.TestStepStarted; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.UUID; +import static io.cucumber.core.runner.TestStepResultStatus.from; +import static io.cucumber.messages.TimeConversion.javaDurationToDuration; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; import static java.time.Duration.ZERO; abstract class TestStep implements io.cucumber.plugin.event.TestStep { @@ -35,14 +43,20 @@ abstract class TestStep implements io.cucumber.plugin.event.TestStep { this.stepDefinitionMatch = stepDefinitionMatch; } + @Override + public UUID getId() { + return id; + } + @Override public String getCodeLocation() { return stepDefinitionMatch.getCodeLocation(); } - boolean run(TestCase testCase, EventBus bus, TestCaseState state, boolean skipSteps, UUID textExecutionId) { + boolean run(TestCase testCase, EventBus bus, TestCaseState state, boolean skipSteps) { Instant startTime = bus.getInstant(); - bus.send(new TestStepStarted(startTime, testCase, this)); + emitTestStepStarted(testCase, bus, state.getTestExecutionId(), startTime); + Status status; Throwable error = null; try { @@ -55,17 +69,62 @@ boolean run(TestCase testCase, EventBus bus, TestCaseState state, boolean skipSt Duration duration = Duration.between(startTime, stopTime); Result result = mapStatusToResult(status, error, duration); state.add(result); - bus.send(new TestStepFinished(stopTime, testCase, this, result)); + + emitTestStepFinished(testCase, bus, state.getTestExecutionId(), stopTime, duration, result); + return !result.getStatus().is(Status.PASSED); } + + private void emitTestStepStarted(TestCase testCase, EventBus bus, UUID textExecutionId, Instant startTime) { + bus.send(new TestStepStarted(startTime, testCase, this)); + bus.send(Messages.Envelope.newBuilder() + .setTestStepStarted(Messages.TestStepStarted.newBuilder() + .setTestCaseStartedId(textExecutionId.toString()) + .setTestStepId(id.toString()) + .setTimestamp(javaInstantToTimestamp(startTime)) + ).build() + ); + } + + private void emitTestStepFinished(TestCase testCase, EventBus bus, UUID textExecutionId, Instant stopTime, Duration duration, Result result) { + bus.send(new TestStepFinished(stopTime, testCase, this, result)); + TestStepResult.Builder builder = TestStepResult.newBuilder(); + + if (result.getError() != null) { + builder.setMessage(extractStackTrace(result.getError())); + } + TestStepResult testResult = builder.setStatus(from(result.getStatus())) + .setDuration(javaDurationToDuration(duration)) + .build(); + bus.send(Messages.Envelope.newBuilder() + .setTestStepFinished(Messages.TestStepFinished.newBuilder() + .setTestCaseStartedId(textExecutionId.toString()) + .setTestStepId(id.toString()) + .setTimestamp(javaInstantToTimestamp(stopTime)) + .setTestStepResult(testResult) + ).build() + ); + } + private String extractStackTrace(Throwable error) { + ByteArrayOutputStream s = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(s); + error.printStackTrace(printStream); + return new String(s.toByteArray(), StandardCharsets.UTF_8); + } + private Status executeStep(TestCaseState state, boolean skipSteps) throws Throwable { - if (!skipSteps) { - stepDefinitionMatch.runStep(state); - return Status.PASSED; - } else { - stepDefinitionMatch.dryRunStep(state); - return Status.SKIPPED; + state.setCurrentTestStepId(id); + try { + if (!skipSteps) { + stepDefinitionMatch.runStep(state); + return Status.PASSED; + } else { + stepDefinitionMatch.dryRunStep(state); + return Status.SKIPPED; + } + } finally { + state.clearCurrentTestStepId(); } } diff --git a/core/src/main/java/io/cucumber/core/runner/TestStepResultStatus.java b/core/src/main/java/io/cucumber/core/runner/TestStepResultStatus.java new file mode 100644 index 0000000000..71fca67098 --- /dev/null +++ b/core/src/main/java/io/cucumber/core/runner/TestStepResultStatus.java @@ -0,0 +1,26 @@ +package io.cucumber.core.runner; + +import io.cucumber.messages.Messages; +import io.cucumber.plugin.event.Status; + +import java.util.HashMap; +import java.util.Map; + +class TestStepResultStatus { + private static final Map STATUS = new HashMap() {{ + put(Status.FAILED, Messages.TestStepResult.Status.FAILED); + put(Status.PASSED, Messages.TestStepResult.Status.PASSED); + put(Status.UNDEFINED, Messages.TestStepResult.Status.UNDEFINED); + put(Status.PENDING, Messages.TestStepResult.Status.PENDING); + put(Status.SKIPPED, Messages.TestStepResult.Status.SKIPPED); + put(Status.AMBIGUOUS, Messages.TestStepResult.Status.AMBIGUOUS); + }}; + + private TestStepResultStatus() { + } + + static Messages.TestStepResult.Status from(Status status) { + return STATUS.getOrDefault(status, Messages.TestStepResult.Status.UNKNOWN); + } + +} diff --git a/core/src/main/java/io/cucumber/core/runtime/Runtime.java b/core/src/main/java/io/cucumber/core/runtime/Runtime.java index 4c3266d6eb..68fbafe39b 100644 --- a/core/src/main/java/io/cucumber/core/runtime/Runtime.java +++ b/core/src/main/java/io/cucumber/core/runtime/Runtime.java @@ -14,6 +14,7 @@ import io.cucumber.core.plugin.PluginFactory; import io.cucumber.core.plugin.Plugins; import io.cucumber.core.resource.ClassLoaders; +import io.cucumber.messages.Messages; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.Plugin; import io.cucumber.plugin.event.EventHandler; @@ -26,6 +27,7 @@ import io.cucumber.plugin.event.TestSourceRead; import java.time.Clock; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -42,6 +44,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; import static java.util.Collections.emptyList; import static java.util.Collections.max; import static java.util.Collections.min; @@ -86,9 +89,9 @@ private Runtime(final ExitStatus exitStatus, public void run() { final List features = featureSupplier.get(); - bus.send(new TestRunStarted(bus.getInstant())); + emitTestRunStarted(); for (Feature feature : features) { - bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + emitTestSource(feature); } final List> executingPickles = features.stream() @@ -120,7 +123,30 @@ public void run() { throw new CompositeCucumberException(thrown); } - bus.send(new TestRunFinished(bus.getInstant())); + emitTestRunFinished(); + } + + private void emitTestRunStarted() { + Instant instant = bus.getInstant(); + bus.send(new TestRunStarted(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); + } + + private void emitTestSource(Feature feature) { + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + bus.sendAll(feature.getParseEvents()); + } + + private void emitTestRunFinished() { + Instant instant = bus.getInstant(); + bus.send(new TestRunFinished(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); } public byte exitStatus() { diff --git a/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java index ceef89d1a4..a6500fed3d 100644 --- a/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java +++ b/core/src/main/java/io/cucumber/core/runtime/ThreadLocalRunnerSupplier.java @@ -1,6 +1,5 @@ package io.cucumber.core.runtime; -import io.cucumber.plugin.event.Event; import io.cucumber.plugin.event.EventHandler; import io.cucumber.core.eventbus.AbstractEventBus; import io.cucumber.core.eventbus.EventBus; @@ -63,7 +62,7 @@ private static final class LocalEventBus extends AbstractEventBus { } @Override - public void send(final Event event) { + public void send(final T event) { super.send(event); parent.send(event); } @@ -96,22 +95,22 @@ private SynchronizedEventBus(final EventBus delegate) { } @Override - public synchronized void send(final Event event) { + public synchronized void send(final T event) { delegate.send(event); } @Override - public synchronized void sendAll(final Iterable events) { + public synchronized void sendAll(final Iterable events) { delegate.sendAll(events); } @Override - public synchronized void registerHandlerFor(Class eventType, EventHandler handler) { + public synchronized void registerHandlerFor(Class eventType, EventHandler handler) { delegate.registerHandlerFor(eventType, handler); } @Override - public synchronized void removeHandlerFor(Class eventType, EventHandler handler) { + public synchronized void removeHandlerFor(Class eventType, EventHandler handler) { delegate.removeHandlerFor(eventType, handler); } diff --git a/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java index bf60d1c2ea..a9fc7bc640 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/ArgumentMatcher.java @@ -33,7 +33,7 @@ public List argumentsFrom(Step step, Type... types) { if (arg instanceof io.cucumber.core.gherkin.DocStringArgument) { DocStringArgument docString = (DocStringArgument) arg; String content = docString.getContent(); - String contentType = docString.getContentType(); + String contentType = docString.getMediaType(); return expression.match(step.getText(), content, contentType, types); } diff --git a/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java b/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java index 5ec5e8c355..d73292339b 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/ExpressionArgument.java @@ -2,6 +2,8 @@ import io.cucumber.cucumberexpressions.Group; +import java.lang.reflect.Type; + public final class ExpressionArgument implements Argument { private final io.cucumber.cucumberexpressions.Argument argument; @@ -19,6 +21,14 @@ public Group getGroup() { return argument.getGroup(); } + public Type getType() { + return argument.getType(); + } + + public String getParameterTypeName() { + return argument.getParameterType().getName(); + } + @Override public String toString() { return argument.getGroup() == null ? null : argument.getGroup().getValue(); diff --git a/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java index 1be7421b02..6a9618d97f 100644 --- a/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java +++ b/core/src/main/java/io/cucumber/core/stepexpression/StepExpression.java @@ -54,7 +54,6 @@ public List match(String text, String content, String contentType, Typ return list; } - private static List wrapPlusOne(List> match) { List copy = new ArrayList<>(match.size() + 1); for (io.cucumber.cucumberexpressions.Argument argument : match) { @@ -62,5 +61,4 @@ private static List wrapPlusOne(List').appendTo(tBody); - $.each(row.cells, function(index, cell) { - var td = $('' + cell + '').appendTo(tBody); - }); - }); - } - }; - - this.examples = function(examples) { - var examplesElement = blockElement(currentElement.children('details'), examples, 'examples'); - var examplesTable = $('.examples_table', $templates).clone(); - examplesTable.appendTo(examplesElement.children('details')); - - $.each(examples.rows, function(index, row) { - var parent = index == 0 ? examplesTable.find('thead') : examplesTable.find('tbody'); - var tr = $('').appendTo(parent); - $.each(row.cells, function(index, cell) { - var td = $('' + cell + '').appendTo(tr); - }); - }); - }; - - this.match = function(match) { - currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')'); - currentStepIndex++; - }; - - this.result = function(result) { - currentStep.addClass(result.status); - if (result.error_message != '') { - populateStepError(currentStep, result.error_message); - } - currentElement.addClass(result.status); - var isLastStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')').length == 0; - if (isLastStep) { - if (currentSteps.find('.failed').length == 0) { - // No failed steps. Collapse it. - currentElement.find('details').prop('open', false); - } else { - currentElement.find('details').attr('open', 'open'); - } - } - }; - - this.embedding = function(mediaType, data, name) { - var nameHtml; - if (!name) { - nameHtml = ""; - } else { - nameHtml = "

" + name + "

"; - } - if (currentStepIndex == 1) { - this.dummyStep(); - } - if (mediaType.match(/^image\//)) - { - currentStep.append(nameHtml + ''); - } - else if (mediaType.match(/^video\//)) - { - currentStep.append(nameHtml + ''); - } - else if (mediaType.match(/^text\//)) - { - this.write(nameHtml + data); - } - }; - - this.write = function(text) { - if (currentStepIndex == 1) { - this.dummyStep(); - } - currentStep.append('
' + text + '
'); - }; - - this.before = function(before) { - this.handleHookResult(before); - }; - - this.after = function(after) { - this.handleHookResult(after); - }; - - this.beforestep = function(beforestep) { - this.handleHookResult(beforestep); - }; - - this.afterstep = function(afterstep) { - this.handleHookResult(afterstep); - }; - - this.handleHookResult = function(hook) { - if (hook.status != 'passed' && hook.error_message != '') { - this.dummyStep(); - currentStep.addClass(hook.status); - currentElement.addClass(hook.status); - populateStepError(currentStep, hook.error_message); - } - }; - - this.dummyStep = function() { - var stepElement = $('.step', $templates).clone(); - stepElement.appendTo(currentSteps); - populate(stepElement, {keyword: '', name: ''}, 'step'); - currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')'); - currentStepIndex++; - }; - - function featureElement(statement, itemtype) { - var e = blockElement(currentFeature.children('details'), statement, itemtype); - - currentSteps = $('.steps', $templates).clone(); - currentSteps.appendTo(e.children('details')); - - return e; - } - - function blockElement(parent, statement, itemtype) { - var e = $('.blockelement', $templates).clone(); - e.appendTo(parent); - return populate(e, statement, itemtype); - } - - function populate(e, statement, itemtype) { - populateTags(e, statement.tags); - populateComments(e, statement.comments); - e.find('.keyword').text(statement.keyword); - e.find('.name').text(statement.name); - e.find('.description').text(statement.description); - e.attr('itemtype', 'http://cukes.info/microformat/' + itemtype); - e.addClass(itemtype); - return e; - } - - function populateComments(e, comments) { - if (comments !== undefined) { - var commentsNode = $('.comments', $templates).clone().prependTo(e.find('.header')); - $.each(comments, function(index, comment) { - var commentNode = $('.comment', $templates).clone().appendTo(commentsNode); - commentNode.text(comment.value); - }); - } - } - - function populateTags(e, tags) { - if (tags !== undefined) { - var tagsNode = $('.tags', $templates).clone().prependTo(e.find('.header')); - $.each(tags, function(index, tag) { - var tagNode = $('.tag', $templates).clone().appendTo(tagsNode); - tagNode.text(tag.name); - }); - } - } - - function populateStepError(e, error) { - if (error !== undefined) { - errorNode = $('.error', $templates).clone().appendTo(e); - errorNode.text(error); - } - } -}; - -CucumberHTML.templates = '
\ -
\ -
\ - \ - Keyword: This is the block name\ - \ -
The description goes here
\ -
\ -
\ -\ -
    \ -\ -
      \ -
    1. KeywordName
    2. \ -
    \ -\ -
    \
    -\
    -  
    \
    -\
    -  \
    -    \
    -    \
    -  
    \ -\ - \ - \ - \ -
    \ -\ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ -
    '; - -if (typeof module !== 'undefined') { - module.exports = CucumberHTML; -} else if (typeof define !== 'undefined') { - define([], function() { return CucumberHTML; }); -} diff --git a/core/src/main/resources/io/cucumber/core/plugin/html/index.html b/core/src/main/resources/io/cucumber/core/plugin/html/index.html deleted file mode 100644 index 982462d555..0000000000 --- a/core/src/main/resources/io/cucumber/core/plugin/html/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Cucumber Features - - - - - - -
    - - diff --git a/core/src/main/resources/io/cucumber/core/plugin/html/jquery-3.4.1.min.js b/core/src/main/resources/io/cucumber/core/plugin/html/jquery-3.4.1.min.js deleted file mode 100644 index a1c07fd803..0000000000 --- a/core/src/main/resources/io/cucumber/core/plugin/html/jquery-3.4.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0 which costs ', line: 17}); - formatter.examples({description:'', name:'Some good examples', keyword:'Examples', line: 18, rows:[{cells:['name', 'price'], line:19}, {cells:['milk', '9'], line:20}, {cells:['bread', '7'], line:21}, {cells:['soap', '5'], line:22}]}); - formatter.before({status: 'passed', duration: 668816288}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'passed', duration: 0}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'passed', duration: 0}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'failed', error_message:'I didn\'t do it.', duration: 0}); - formatter.after({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.WidgetFurbicator.furbicateWidgets(WidgetFurbicator.java:678)'}); - - formatter.scenario({"tags":[{"name":"@stephooks","line":24}], keyword:'Scenario', name: 'Scenario with step hooks', line: 25}); - formatter.before({status: 'passed', duration: 668816288}); - formatter.beforestep({status: 'passed', duration: 668816288}); - formatter.step({keyword:'Given ', name:'step 1', line: 26}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'passed', duration: 0}); - formatter.afterstep({status: 'passed', duration: 668816288}); - formatter.beforestep({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.StepDefinitions.beforeStepHook()'}); - formatter.step({keyword:'When ', name:'step 2', line: 27}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'skipped', duration: 0}); - formatter.afterstep({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.StepDefinitions.afterStepHook()'}); - formatter.beforestep({status: 'skipped', duration: 0}); - formatter.step({keyword:'Then ', name:'step 3', line: 28}); - formatter.match({uri:'report.feature'}); - formatter.result({status:'skipped', duration: 0}); - formatter.afterstep({status: 'skipped', duration: 0}); - formatter.after({status: 'passed', duration: 668816288}); -} -console.log('Rendered %s features in %s ms', N, new Date().getTime() - start); - -}); diff --git a/core/src/main/resources/io/cucumber/core/plugin/html/style.css b/core/src/main/resources/io/cucumber/core/plugin/html/style.css deleted file mode 100644 index 674452e85a..0000000000 --- a/core/src/main/resources/io/cucumber/core/plugin/html/style.css +++ /dev/null @@ -1,97 +0,0 @@ -.cucumber-report .body { - font-family: Helvetica,Arial,sans-serif; -} - -.cucumber-report .keyword { - font-weight: bold; -} - -.cucumber-report .description { - font-style: italic; - margin-left: 20px; - white-space: pre; -} - -.cucumber-report details > section { - margin-left: 20px; -} - -.cucumber-report ol.steps { - list-style-type: none; - margin-top: 0; - margin-bottom: 0; -} - -.cucumber-report .step .embedded-text { - background: #dddddd; -} - -.cucumber-report .doc_string { - margin: 0 0 0 20px; -} - -.cucumber-report table { - border-collapse: collapse; - border: 1px; - border-style: solid; -} - -.cucumber-report td, .cucumber-report th { - border: 1px; - border-style: solid; - padding-left: 4px; - padding-right: 4px; -} - -.cucumber-report table { - margin-left: 20px; -} - -.cucumber-report thead { - background-color: #C0C0C0; -} - -.cucumber-report .passed { - background-color: #C5D88A; -} - -.cucumber-report .undefined, .cucumber-report .pending { - background-color: #EAEC2D; -} - -.cucumber-report .skipped { - background-color: #2DEAEC; -} - -.cucumber-report .failed { - background-color: #D88A8A; -} - -.cucumber-report .tags { - display: inline; -} - -.cucumber-report .tag { - margin-right: 0.25em; - color: #246ac1; -} - -.cucumber-report .comments { - display: inline; -} - -.cucumber-report .comment { - margin: 0; - padding: 0; -} - -.cucumber-report .error { - margin: .2em .75em; - padding: .2em; - border: 1px solid #900; - background-color: #EDBBBB; -} - -#cucumber-templates { - display: none; -} 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 c801e7c047..b9f2f379be 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java @@ -156,7 +156,7 @@ void inherit_plugin_from_baseclass() { List pluginList = plugins.getPlugins(); assertAll("Checking Plugin", - () -> assertPluginExists(pluginList, "io.cucumber.core.plugin.JSONFormatter"), + () -> assertPluginExists(pluginList, "io.cucumber.core.plugin.HtmlFormatter"), () -> assertPluginExists(pluginList, "io.cucumber.core.plugin.PrettyFormatter") ); } @@ -255,7 +255,7 @@ private static class SubClassWithFormatter extends BaseClassWithFormatter { // empty } - @CucumberOptions(plugin = "json:target/test-json-report.json") + @CucumberOptions(plugin = "html:target/test-report.html") private static class BaseClassWithFormatter { // empty } diff --git a/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java index 7522dff485..610be33d7a 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java @@ -132,9 +132,9 @@ void should_parse_object_factory() { @Test void should_parse_plugin() { - properties.put(Constants.PLUGIN_PROPERTY_NAME, "json:target/cucumber.json, html:target/cucumber.html"); + properties.put(Constants.PLUGIN_PROPERTY_NAME, "message:target/cucumber.ndjson, html:target/cucumber.html"); RuntimeOptions options = cucumberPropertiesParser.parse(properties).build(); - assertThat(options.plugins().get(0).pluginString(), equalTo("json:target/cucumber.json")); + assertThat(options.plugins().get(0).pluginString(), equalTo("message:target/cucumber.ndjson")); assertThat(options.plugins().get(1).pluginString(), equalTo("html:target/cucumber.html")); } 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 755087f8f0..c6bd9ea4ad 100644 --- a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java +++ b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsTest.java @@ -160,7 +160,7 @@ void creates_html_formatter() { Plugins plugins = new Plugins(new PluginFactory(), options); plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID)); - assertThat(plugins.getPlugins().get(0).getClass().getName(), is("io.cucumber.core.plugin.HTMLFormatter")); + assertThat(plugins.getPlugins().get(0).getClass().getName(), is("io.cucumber.core.plugin.HtmlFormatter")); } @Test @@ -498,7 +498,7 @@ void adds_to_formatter_plugins_with_add_plugin_option() { plugins.setEventBusOnEventListenerPlugins(new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID)); assertAll("Checking Plugins", - () -> assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.HTMLFormatter"))), + () -> assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.HtmlFormatter"))), () -> assertThat(plugins.getPlugins(), hasItem(plugin("io.cucumber.core.plugin.PrettyFormatter"))) ); } diff --git a/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java index 2455d37a5f..78acc4fe71 100644 --- a/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.UUID; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java b/core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java deleted file mode 100644 index e3db441bd1..0000000000 --- a/core/src/test/java/io/cucumber/core/plugin/FormatterBuilder.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.cucumber.core.plugin; - -public class FormatterBuilder { - - public static JSONFormatter jsonFormatter(Appendable out) { - return new JSONFormatter(out); - } -} diff --git a/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java deleted file mode 100644 index 269c0edf95..0000000000 --- a/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java +++ /dev/null @@ -1,785 +0,0 @@ -package io.cucumber.core.plugin; - -import gherkin.deps.com.google.gson.JsonParser; -import io.cucumber.plugin.event.Result; -import io.cucumber.core.gherkin.Feature; -import io.cucumber.core.feature.TestFeatureParser; -import io.cucumber.core.runner.TestHelper; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.junit.jupiter.api.Test; -import org.mockito.stubbing.Answer; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.time.Duration; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static io.cucumber.core.runner.TestHelper.createEmbedHookAction; -import static io.cucumber.core.runner.TestHelper.createWriteHookAction; -import static io.cucumber.core.runner.TestHelper.result; -import static java.nio.charset.StandardCharsets.US_ASCII; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.Duration.ofMillis; -import static java.util.Arrays.asList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.fail; - -class HTMLFormatterTest { - - private final static String jsFunctionCallRegexString = "formatter.(\\w*)\\(([^)]*)\\);"; - private final static Pattern jsFunctionCallRegex = Pattern.compile(jsFunctionCallRegexString); - - private final List features = new ArrayList<>(); - private final Map stepsToResult = new HashMap<>(); - private final Map stepsToLocation = new HashMap<>(); - private final List> hooks = new ArrayList<>(); - private final List hookLocations = new ArrayList<>(); - private final List> hookActions = new ArrayList<>(); - private Duration stepDuration = null; - - private URL outputDir; - - private void writeReport() throws Throwable { - outputDir = TempDir.createTempDirectory().toURI().toURL(); - runFeaturesWithFormatter(outputDir); - } - - @Test - void writes_index_html() throws Throwable { - writeReport(); - URL indexHtml = new URL(outputDir, "index.html"); - Document document = Jsoup.parse(new File(indexHtml.getFile()), UTF_8.name()); - Element reportElement = document.body().getElementsByClass("cucumber-report").first(); - assertThat(reportElement.text(), is(equalTo(""))); - } - - @Test - void writes_valid_report_js() throws Throwable { - writeReport(); - assertJsFunctionCallSequence(asList("" + - "formatter.uri(\"file:some/path/some.feature\");\n", - "formatter.feature({\n" + - " \"name\": \"\",\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Feature\"\n" + - "});\n", - "formatter.scenario({\n" + - " \"name\": \"some cukes\",\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\"\n" + - "});\n", - "formatter.step({\n" + - " \"name\": \"first step\",\n" + - " \"keyword\": \"Given \"\n" + - "});\n", - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", - "formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);\n", - "formatter.after({\n" + - " \"status\": \"passed\"\n" + - "});\n", - "formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");\n", - "formatter.after({\n" + - " \"status\": \"passed\"\n" + - "});\n" - - ), - readReportJs()); - } - - @Test - void includes_uri() throws Throwable { - writeReport(); - assertContains("formatter.uri(\"file:some/path/some.feature\");", readReportJs()); - } - - @Test - void included_embedding() throws Throwable { - writeReport(); - String reportJs = readReportJs(); - assertAll("Checking ReportJs", - () -> assertContains("formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");", reportJs), - () -> assertContains("formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);", reportJs) - ); - } - - @Test - void should_handle_a_single_scenario() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n" + - " Then second step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToResult.put("second step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - stepsToLocation.put("second step", "path/step_definitions.java:7"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.uri(\"file:path/test.feature\");\n", "" + - "formatter.feature({\n" + - " \"description\": \"\",\n" + - " \"name\": \"feature name\",\n" + - " \"keyword\": \"Feature\"\n" + - "});\n", "" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"second step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:7\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});"), - formatterOutput); - } - - @Test - void should_handle_backgound() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Background: background name\n" + - " Given first step\n" + - " Scenario: scenario 1\n" + - " Then second step\n" + - " Scenario: scenario 2\n" + - " Then third step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToResult.put("second step", result("passed")); - stepsToResult.put("third step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - stepsToLocation.put("second step", "path/step_definitions.java:7"); - stepsToLocation.put("third step", "path/step_definitions.java:11"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.background({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Background\",\n" + - " \"name\": \"background name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario 1\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"second step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:7\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.background({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Background\",\n" + - " \"name\": \"background name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario 2\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"third step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:11\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_scenario_outline() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario Outline: outline name\n" + - " Given first step\n" + - " Then step\n" + - " Examples: examples name\n" + - " | arg |\n" + - " | second |\n" + - " | third |\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToResult.put("second step", result("passed")); - stepsToResult.put("third step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - stepsToLocation.put("second step", "path/step_definitions.java:7"); - stepsToLocation.put("third step", "path/step_definitions.java:11"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.uri(\"file:path/test.feature\");\n", "" + - "formatter.feature({\n" + - " \"description\": \"\",\n" + - " \"name\": \"feature name\",\n" + - " \"keyword\": \"Feature\"\n" + - "});\n", "" + - "formatter.scenarioOutline({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario Outline\",\n" + - " \"name\": \"outline name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"\\u003carg\\u003e step\"\n" + - "});\n", "" + - "formatter.examples({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Examples\",\n" + - " \"name\": \"examples name\",\n" + - " \"rows\": [\n" + - " {\n" + - " \"cells\": [\n" + - " \"arg\"\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"cells\": [\n" + - " \"second\"\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"cells\": [\n" + - " \"third\"\n" + - " ]\n" + - " }\n" + - " ]\n" + - "});\n", "" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario Outline\",\n" + - " \"name\": \"outline name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"second step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:7\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario Outline\",\n" + - " \"name\": \"outline name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"third step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:11\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});"), - formatterOutput); - } - - @Test - void should_handle_before_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.before({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_after_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("after", result("passed"))); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.after({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_after_step_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n" + - " When second step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToResult.put("second step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - stepsToLocation.put("second step", "path/step_definitions.java:4"); - hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"When \",\n" + - " \"name\": \"second step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:4\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_output_from_before_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.write(\"printed from hook\");\n", "" + - "formatter.before({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_output_from_after_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("after", result("passed"))); - hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.write(\"printed from hook\");\n", "" + - "formatter.after({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_output_from_after_step_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n" + - " When second step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToResult.put("second step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - stepsToLocation.put("second step", "path/step_definitions.java:4"); - hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.write(\"printed from hook\");\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"When \",\n" + - " \"name\": \"second step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:4\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.write(\"printed from hook\");\n", "" + - "formatter.afterstep({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - @Test - void should_handle_text_embeddings_from_before_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: feature name\n" + - " Scenario: scenario name\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookActions.add(createEmbedHookAction("embedded from hook".getBytes(US_ASCII), "text/ascii")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - assertJsFunctionCallSequence(asList("" + - "formatter.scenario({\n" + - " \"description\": \"\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"name\": \"scenario name\"\n" + - "});\n", "" + - "formatter.embedding(\"text/ascii\", \"embedded from hook\", null);\n", "" + - "formatter.before({\n" + - " \"status\": \"passed\"\n" + - "});\n", "" + - "formatter.step({\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"first step\"\n" + - "});\n", "" + - "formatter.match({\n" + - " \"location\": \"path/step_definitions.java:3\"\n" + - "});\n", "" + - "formatter.result({\n" + - " \"status\": \"passed\"\n" + - "});\n"), - formatterOutput); - } - - private String readReportJs() throws IOException { - InputStream reportJsStream = new URL(outputDir, "report.js").openStream(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(reportJsStream, UTF_8))) { - return br.lines().collect(Collectors.joining(System.lineSeparator())); - } - } - - private void assertJsFunctionCallSequence(List expectedList, String actual) { - Iterator expectedIterator = expectedList.iterator(); - String expected = expectedIterator.next(); - Matcher expectedMatcher = jsFunctionCallRegex.matcher(expected); - Matcher actualMatcher = jsFunctionCallRegex.matcher(actual); - assertThat(jsFunctionCallMatchFailure(expected), expectedMatcher.find(), is(equalTo(true))); - boolean found = false; - while (actualMatcher.find()) { - if (matchFound(expectedMatcher, actualMatcher)) { - found = true; - break; - } - } - assertThat(jsFunctionCallNotFoundMessage(actual, expected), found, is(equalTo(true))); - while (expectedIterator.hasNext()) { - expected = expectedIterator.next(); - expectedMatcher = jsFunctionCallRegex.matcher(expected); - assertThat(jsFunctionCallMatchFailure(expected), expectedMatcher.find(), is(equalTo(true))); - assertThat(jsFunctionCallNotFoundMessage(actual, expected), actualMatcher.find(), is(equalTo(true))); - if (!matchFound(expectedMatcher, actualMatcher)) { - fail(jsFunctionCallNotFoundMessage(actual, expected)); - } - } - } - - private String jsFunctionCallMatchFailure(String expected) { - return "The expected string: " + expected + ", does not match " + jsFunctionCallRegexString; - } - - private String jsFunctionCallNotFoundMessage(String actual, String expected) { - return "The expected js function call: " + expected + ", is not found in " + actual; - } - - private boolean matchFound(Matcher expectedMatcher, Matcher actualMatcher) { - String expectedFunction = expectedMatcher.group(1); - String actualFunction = actualMatcher.group(1); - if (!expectedFunction.equals(actualFunction)) { - return false; - } - String expectedArgument = expectedMatcher.group(2); - String actualArgumant = actualMatcher.group(2); - if (matchUsingJson(expectedArgument, actualArgumant)) { - JsonParser parser = new JsonParser(); - return parser.parse(expectedArgument).equals(parser.parse(actualArgumant)); - } else { - return expectedArgument.equals(actualArgumant); - } - } - - private boolean matchUsingJson(String expected, String actual) { - return expected.startsWith("{") && actual.startsWith("{"); - } - - private void assertContains(String substring, String string) { - if (!string.contains(substring)) { - fail(String.format("[%s] not contained in [%s]", substring, string)); - } - } - - private void runFeaturesWithFormatter(URL outputDir) { - final HTMLFormatter f = new HTMLFormatter(outputDir); - Feature feature = TestFeatureParser.parse("some/path/some.feature", "" + - "Feature:\n" + - " Scenario: some cukes\n" + - " Given first step\n"); - features.add(feature); - stepsToResult.put("first step", result("passed")); - stepsToLocation.put("first step", "path/step_definitions.java:3"); - hooks.add(TestHelper.hookEntry("after", result("passed"))); - hooks.add(TestHelper.hookEntry("after", result("passed"))); - hookActions.add(createEmbedHookAction("fakedata".getBytes(US_ASCII), "image/png", "Fake image")); - hookActions.add(createEmbedHookAction("dodgy stack trace here".getBytes(US_ASCII), "text/plain")); - stepDuration = ofMillis(1L); - - runFeaturesWithFormatter(f); - } - - private String runFeaturesWithFormatter() { - final StringBuilder report = new StringBuilder(); - final HTMLFormatter formatter = new HTMLFormatter(null, new NiceAppendable(report)); - runFeaturesWithFormatter(formatter); - return report.toString(); - } - - private void runFeaturesWithFormatter(HTMLFormatter formatter) { - TestHelper.builder() - .withFormatterUnderTest(formatter) - .withFeatures(features) - .withStepsToResult(stepsToResult) - .withStepsToLocation(stepsToLocation) - .withHooks(hooks) - .withHookLocations(hookLocations) - .withHookActions(hookActions) - .withTimeServiceIncrement(stepDuration) - .build() - .run(); - } - -} diff --git a/core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java new file mode 100644 index 0000000000..f30f62db78 --- /dev/null +++ b/core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java @@ -0,0 +1,51 @@ +package io.cucumber.core.plugin; + +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.messages.Messages; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.time.Clock; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + +class HtmlFormatterTest { + + @Test + void writes_index_html() throws Throwable { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + HtmlFormatter formatter = new HtmlFormatter(bytes); + EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); + formatter.setEventPublisher(bus); + + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(Messages.Timestamp.newBuilder() + .setSeconds(10) + .build()) + .build()) + .build()); + + bus.send( + Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(Messages.Timestamp.newBuilder() + .setSeconds(15) + .build()) + .build()) + .build()); + + String html = new String(bytes.toByteArray(), UTF_8); + assertThat(html, containsString("" + + "window.CUCUMBER_MESSAGES = [" + + "{\"testRunStarted\":{\"timestamp\":{\"seconds\":\"10\"}}}," + + "{\"testRunFinished\":{\"timestamp\":{\"seconds\":\"15\"}}}" + + "];")); + } + + +} diff --git a/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java deleted file mode 100755 index 5c2dc84e88..0000000000 --- a/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java +++ /dev/null @@ -1,1316 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.backend.Glue; -import io.cucumber.core.backend.HookDefinition; -import io.cucumber.core.eventbus.EventBus; -import io.cucumber.core.gherkin.Feature; -import io.cucumber.core.feature.TestFeatureParser; -import io.cucumber.core.options.CommandlineOptionsParser; -import io.cucumber.core.options.RuntimeOptions; -import io.cucumber.core.runner.ClockStub; -import io.cucumber.core.runner.TestBackendSupplier; -import io.cucumber.core.runner.TestHelper; -import io.cucumber.core.runtime.Runtime; -import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.plugin.event.Result; -import org.junit.jupiter.api.Test; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.time.Duration; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.UUID; - -import static io.cucumber.core.runner.TestHelper.createEmbedHookAction; -import static io.cucumber.core.runner.TestHelper.createWriteHookAction; -import static io.cucumber.core.runner.TestHelper.result; -import static java.time.Duration.ofMillis; -import static java.util.Collections.singletonList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; - -class JSONFormatterTest { - - private final List features = new ArrayList<>(); - private final Map stepsToResult = new HashMap<>(); - private final Map stepsToLocation = new HashMap<>(); - private final List> hooks = new ArrayList<>(); - private final List hookLocations = new ArrayList<>(); - private final List> hookActions = new ArrayList<>(); - private Duration stepDuration = Duration.ZERO; - - @Test - void featureWithOutlineTest() { - List featurePaths = singletonList("classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature"); - String actual = runFeaturesWithFormatter(featurePaths); - InputStream resourceAsStream = getClass().getResourceAsStream("JSONPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - assertThat(actual, sameJSONAs(expected)); - } - - @Test - void featureWithOutlineTestParallel() throws Exception { - List featurePaths = singletonList("classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature"); - String actual = runFeaturesWithFormatterInParallel(featurePaths); - InputStream resourceAsStream = getClass().getResourceAsStream("JSONPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - - assertThat(actual, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_an_undefined_step() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("undefined")); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {},\n" + - " \"result\": {\n" + - " \"status\": \"undefined\"\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_a_passed_step() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_a_failed_step() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("failed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"failed\",\n" + - " \"error_message\": \"the stack trace\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_outline_with_one_example() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Fruit party\n" + - "\n" + - " Scenario Outline: Monkey eats fruits\n" + - " Given there are \n" + - " Examples: Fruit table\n" + - " | fruits |\n" + - " | bananas |\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"fruit-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Fruit party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"fruit-party;monkey-eats-fruits;fruit-table;2\",\n" + - " \"keyword\": \"Scenario Outline\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats fruits\",\n" + - " \"line\": 7,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_feature_with_background() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Background: There are bananas\n" + - " Given there are bananas\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Then the monkey eats bananas\n" + - "\n" + - " Scenario: Monkey eats more bananas\n" + - " Then the monkey eats more bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToResult.put("the monkey eats bananas", result("passed")); - stepsToResult.put("the monkey eats more bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepsToLocation.put("the monkey eats bananas", "StepDefs.monkey_eats_bananas()"); - stepsToLocation.put("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"keyword\": \"Background\",\n" + - " \"name\": \"There are bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"background\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 6,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"the monkey eats bananas\",\n" + - " \"line\": 7,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"keyword\": \"Background\",\n" + - " \"name\": \"There are bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"background\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-more-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.002Z\",\n" + - " \"name\": \"Monkey eats more bananas\",\n" + - " \"line\": 9,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Then \",\n" + - " \"name\": \"the monkey eats more bananas\",\n" + - " \"line\": 10,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_more_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_feature_and_scenario_with_tags() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "@Party @Banana\n" + - "Feature: Banana party\n" + - " @Monkey\n" + - " Scenario: Monkey eats more bananas\n" + - " Then the monkey eats more bananas\n"); - features.add(feature); - stepsToResult.put("the monkey eats more bananas", result("passed")); - stepsToLocation.put("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 2,\n" + - " \"elements\": [\n" + - " {\n" + - " \"line\": 4,\n" + - " \"name\": \"Monkey eats more bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;monkey-eats-more-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 5,\n" + - " \"name\": \"the monkey eats more bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_more_bananas()\"\n" + - " },\n" + - " \"keyword\": \"Then \"\n" + - " }\n" + - " ],\n" + - " \"tags\": [\n" + - " {\n" + - " \"name\": \"@Party\"\n" + - " },\n" + - " {\n" + - " \"name\": \"@Banana\"\n" + - " },\n" + - " {\n" + - " \"name\": \"@Monkey\"\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"name\": \"Banana party\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"tags\": [\n" + - " {\n" + - " \"name\": \"@Party\",\n" + - " \"type\": \"Tag\",\n" + - " \"location\": {\n" + - " \"line\": 1,\n" + - " \"column\": 1\n" + - " }\n" + - " },\n" + - " {\n" + - " \"name\": \"@Banana\",\n" + - " \"type\": \"Tag\",\n" + - " \"location\": {\n" + - " \"line\": 1,\n" + - " \"column\": 8\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_hooks() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hooks.add(TestHelper.hookEntry("after", result("passed"))); - hookLocations.add("Hooks.before_hook_1()"); - hookLocations.add("Hooks.after_hook_1()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"before\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"after\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.after_hook_1()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_add_step_hooks_to_step() { - Feature feature = TestFeatureParser.parse("file:path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " When monkey arrives\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToResult.put("monkey arrives", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepsToLocation.put("monkey arrives", "StepDefs.monkey_arrives()"); - hooks.add(TestHelper.hookEntry("beforestep", result("passed"))); - hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); - hookLocations.add("Hooks.beforestep_hooks_1()"); - hookLocations.add("Hooks.afterstep_hooks_1()"); - hookLocations.add("Hooks.afterstep_hooks_2()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"line\": 3,\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"before\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.beforestep_hooks_1()\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 4,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_2()\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_1()\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"keyword\": \"Given \"\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"before\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.beforestep_hooks_1()\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 5,\n" + - " \"name\": \"monkey arrives\",\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_arrives()\"\n" + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_2()\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_1()\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"keyword\": \"When \"\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"name\": \"Banana party\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_handle_write_from_a_hook() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookLocations.add("Hooks.before_hook_1()"); - hookActions.add(createWriteHookAction("printed from hook")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"before\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + - " },\n" + - " \"output\": [\n" + - " \"printed from hook\"\n" + - " ],\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_handle_embed_from_a_hook() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookLocations.add("Hooks.before_hook_1()"); - hookActions.add(createEmbedHookAction(new byte[]{1, 2, 3}, "mime-type;base64")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"before\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + - " },\n" + - " \"embeddings\": [\n" + - " {\n" + - " \"mime_type\": \"mime-type;base64\",\n" + - " \"data\": \"AQID\"\n" + - " }\n" + - " ],\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_handle_embed_with_name_from_a_hook() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - hooks.add(TestHelper.hookEntry("before", result("passed"))); - hookLocations.add("Hooks.before_hook_1()"); - hookActions.add(createEmbedHookAction(new byte[]{1, 2, 3}, "mime-type;base64", "someEmbedding")); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"before\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + - " },\n" + - " \"embeddings\": [\n" + - " {\n" + - " \"mime_type\": \"mime-type;base64\",\n" + - " \"data\": \"AQID\",\n" + - " \"name\": \"someEmbedding\"\n" + - " }\n" + - " ],\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " \"\"\"\n" + - " doc string content\n" + - " \"\"\"\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"doc_string\": {\n" + - " \"value\": \"doc string content\",\n" + - " \"line\": 5\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " \"\"\"doc\n" + - " doc string content\n" + - " \"\"\"\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"doc_string\": {\n" + - " \"content_type\": \"doc\",\n" + - " \"value\": \"doc string content\",\n" + - " \"line\": 5\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_format_scenario_with_a_step_with_a_data_table() { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " | aa | 11 |\n" + - " | bb | 22 |\n"); - features.add(feature); - stepsToResult.put("there are bananas", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"rows\": [\n" + - " {\n" + - " \"cells\": [\n" + - " \"aa\",\n" + - " \"11\"\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"cells\": [\n" + - " \"bb\",\n" + - " \"22\"\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - @Test - void should_handle_several_features() { - Feature feature1 = TestFeatureParser.parse("path/test1.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - Feature feature2 = TestFeatureParser.parse("path/test2.feature", "" + - "Feature: Orange party\n" + - "\n" + - " Scenario: Monkey eats oranges\n" + - " Given there are oranges\n"); - features.add(feature1); - features.add(feature2); - stepsToResult.put("there are bananas", result("passed")); - stepsToResult.put("there are oranges", result("passed")); - stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); - stepsToLocation.put("there are oranges", "StepDefs.there_are_oranges()"); - stepDuration = ofMillis(1L); - - String formatterOutput = runFeaturesWithFormatter(); - - String expected = "" + - "[\n" + - " {\n" + - " \"id\": \"banana-party\",\n" + - " \"uri\": \"file:path/test1.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Banana party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"banana-party;monkey-eats-bananas\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are bananas\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " },\n" + - " {\n" + - " \"id\": \"orange-party\",\n" + - " \"uri\": \"file:path/test2.feature\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"name\": \"Orange party\",\n" + - " \"line\": 1,\n" + - " \"description\": \"\",\n" + - " \"elements\": [\n" + - " {\n" + - " \"id\": \"orange-party;monkey-eats-oranges\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.001Z\",\n" + - " \"name\": \"Monkey eats oranges\",\n" + - " \"line\": 3,\n" + - " \"description\": \"\",\n" + - " \"type\": \"scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"keyword\": \"Given \",\n" + - " \"name\": \"there are oranges\",\n" + - " \"line\": 4,\n" + - " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_oranges()\"\n" + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(formatterOutput, sameJSONAs(expected)); - } - - private String runFeaturesWithFormatterInParallel(final List featurePaths) throws IOException { - final HookDefinition hook = mock(HookDefinition.class); - when(hook.getTagExpression()).thenReturn(""); - File report = File.createTempFile("cucumber-jvm-junit", ".json"); - - List args = new ArrayList<>(); - args.add("--threads"); - args.add("4"); - args.add("--plugin"); - args.add("json:" + report.getAbsolutePath()); - args.addAll(featurePaths); - - final TestBackendSupplier backendSupplier = new TestBackendSupplier() { - @Override - public void loadGlue(Glue glue, List gluePaths) { - glue.addBeforeHook(hook); - - } - }; - final EventBus bus = new TimeServiceEventBus(new ClockStub(ofMillis(1234L)), UUID::randomUUID); - - Appendable stringBuilder = new StringBuilder(); - - RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); - Runtime.builder() - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse(featurePaths) - .build(runtimeOptions) - ) - .withEventBus(bus) - .withBackendSupplier(backendSupplier) - .withAdditionalPlugins(new JSONFormatter(stringBuilder)) - .build() - .run(); - - return stringBuilder.toString(); - } - - - private String runFeaturesWithFormatter(final List featurePaths) { - final HookDefinition hook = mock(HookDefinition.class); - when(hook.getTagExpression()).thenReturn(""); - - final TestBackendSupplier backendSupplier = new TestBackendSupplier() { - @Override - public void loadGlue(Glue glue, List gluePaths) { - glue.addBeforeHook(hook); - - } - }; - final EventBus bus = new TimeServiceEventBus(new ClockStub(ofMillis(1234L)), UUID::randomUUID); - - Appendable stringBuilder = new StringBuilder(); - - Runtime.builder() - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse(featurePaths) - .build() - ) - .withEventBus(bus) - .withBackendSupplier(backendSupplier) - .withAdditionalPlugins(new JSONFormatter(stringBuilder)) - .build() - .run(); - - return stringBuilder.toString(); - } - - private String runFeaturesWithFormatter() { - final StringBuilder report = new StringBuilder(); - - TestHelper.builder() - .withFormatterUnderTest(new JSONFormatter(report)) - .withFeatures(features) - .withStepsToResult(stepsToResult) - .withStepsToLocation(stepsToLocation) - .withHooks(hooks) - .withHookLocations(hookLocations) - .withHookActions(hookActions) - .withTimeServiceIncrement(stepDuration) - .build() - .run(); - - return report.toString(); - } - -} diff --git a/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java index f656eb83b0..560d6d0218 100644 --- a/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/JUnitFormatterTest.java @@ -244,6 +244,7 @@ void should_handle_failure_in_before_hook() throws Throwable { stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("failed"))); + hookLocations.add("hook-location"); stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -277,6 +278,7 @@ void should_handle_pending_in_before_hook() throws Throwable { stepsToResult.put("second step", result("skipped")); stepsToResult.put("third step", result("skipped")); hooks.add(TestHelper.hookEntry("before", result("pending"))); + hookLocations.add("hook-location"); stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -308,6 +310,7 @@ void should_handle_failure_in_before_hook_with_background() throws Throwable { stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("failed"))); + hookLocations.add("hook-location"); stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -341,6 +344,7 @@ void should_handle_failure_in_after_hook() throws Throwable { stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("failed"))); + hookLocations.add("hook-location"); stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); @@ -373,6 +377,8 @@ void should_accumulate_time_from_steps_and_hooks() throws Throwable { stepsToResult.put("second step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("passed"))); hooks.add(TestHelper.hookEntry("after", result("passed"))); + hookLocations.add("hook-location-1"); + hookLocations.add("hook-location-2"); stepDuration = Duration.ofMillis(1L); String formatterOutput = runFeaturesWithFormatter(); diff --git a/core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java b/core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java deleted file mode 100644 index 0e5f846cf6..0000000000 --- a/core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.options.CommandlineOptionsParser; -import io.cucumber.core.runner.ClockStub; -import io.cucumber.core.runtime.Runtime; -import io.cucumber.core.runtime.TimeServiceEventBus; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static java.time.Duration.ZERO; -import static org.hamcrest.MatcherAssert.assertThat; -import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; - -//TODO: Merge with the existing test -class JsonParallelRuntimeTest { - - @Test - void testSingleFeature() { - StringBuilder parallel = new StringBuilder(); - - Runtime.builder() - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse( - "--threads", "3", - "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature") - .build() - ) - .withAdditionalPlugins(new JSONFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - StringBuilder serial = new StringBuilder(); - - Runtime.builder() - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse( - "--threads", "1", - "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature") - .build() - ) - .withAdditionalPlugins(new JSONFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - assertThat(parallel.toString(), sameJSONAs(serial.toString()).allowingAnyArrayOrdering()); - } - - @Test - void testMultipleFeatures() { - StringBuilder parallel = new StringBuilder(); - - Runtime.builder() - .withRuntimeOptions( - new CommandlineOptionsParser() - .parse("--threads", "3", - "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", - "src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature") - .build() - ) - .withAdditionalPlugins(new JSONFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - - StringBuilder serial = new StringBuilder(); - - Runtime.builder() - .withRuntimeOptions(new CommandlineOptionsParser() - .parse("--threads", "1", - "src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", - "src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature") - .build()) - .withAdditionalPlugins(new JSONFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - assertThat(parallel.toString(), sameJSONAs(serial.toString()).allowingAnyArrayOrdering()); - } - -} diff --git a/core/src/test/java/io/cucumber/core/plugin/MessageFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/MessageFormatterTest.java new file mode 100644 index 0000000000..3ef7a47aaf --- /dev/null +++ b/core/src/test/java/io/cucumber/core/plugin/MessageFormatterTest.java @@ -0,0 +1,49 @@ +package io.cucumber.core.plugin; + +import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.messages.Messages; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Clock; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + +public class MessageFormatterTest { + + @Test + void test() { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + MessageFormatter formatter = new MessageFormatter(bytes); + EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); + formatter.setEventPublisher(bus); + + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(Messages.Timestamp.newBuilder() + .setSeconds(10) + .build()) + .build()) + .build()); + + bus.send( + Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(Messages.Timestamp.newBuilder() + .setSeconds(15) + .build()) + .build()) + .build()); + + String html = new String(bytes.toByteArray(), UTF_8); + assertThat(html, containsString("" + + "{\"testRunStarted\":{\"timestamp\":{\"seconds\":\"10\"}}}\n" + + "{\"testRunFinished\":{\"timestamp\":{\"seconds\":\"15\"}}}" + )); + } +} diff --git a/core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java b/core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java deleted file mode 100755 index 24860319a5..0000000000 --- a/core/src/test/java/io/cucumber/core/plugin/PickleStepMatcher.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.cucumber.core.plugin; - -import gherkin.pickles.PickleStep; - -import org.mockito.ArgumentMatcher; - -public class PickleStepMatcher implements ArgumentMatcher { - private final String textToMatch; - - public PickleStepMatcher(String text) { - this.textToMatch = text; - } - - @Override - public boolean matches(PickleStep argument) { - return argument != null && (argument.getText().contains(textToMatch)); - } -} diff --git a/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java b/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java index 85abba0cf9..fa99dba5ff 100644 --- a/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/PluginFactoryTest.java @@ -44,9 +44,9 @@ void instantiates_junit_plugin_with_file_arg() throws IOException { } @Test - void instantiates_html_plugin_with_dir_arg() throws IOException { - Object plugin = fc.create(parse("html:" + TempDir.createTempDirectory().getAbsolutePath())); - assertThat(plugin.getClass(), is(equalTo(HTMLFormatter.class))); + void instantiates_html_plugin_with_file_arg() throws IOException { + Object plugin = fc.create(parse("html:" + File.createTempFile("cucumber", "html"))); + assertThat(plugin.getClass(), is(equalTo(HtmlFormatter.class))); } @Test diff --git a/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java index 9d8f3567cb..409cb8a152 100755 --- a/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java @@ -172,6 +172,7 @@ void should_print_error_message_for_before_hooks() { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToResult.put("first step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("failed"))); + hookLocations.add("hook-location"); String formatterOutput = runFeaturesWithFormatter(true); @@ -191,6 +192,7 @@ void should_print_error_message_for_after_hooks() { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToResult.put("first step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("failed"))); + hookLocations.add("hook-location"); String formatterOutput = runFeaturesWithFormatter(true); @@ -209,6 +211,7 @@ void should_print_output_from_before_hooks() { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToResult.put("first step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("passed"))); + hookLocations.add("hook-location"); hookActions.add(createWriteHookAction("printed from hook")); String formatterOutput = runFeaturesWithFormatter(true); @@ -231,6 +234,7 @@ void should_print_output_from_after_hooks() { stepsToLocation.put("first step", "path/step_definitions.java:3"); stepsToResult.put("first step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("passed"))); + hookLocations.add("hook-location"); hookActions.add(createWriteHookAction("printed from hook")); String formatterOutput = runFeaturesWithFormatter(true); @@ -254,6 +258,7 @@ void should_print_output_from_afterStep_hooks() { stepsToResult.put("first step", result("passed")); stepsToResult.put("second step", result("passed")); hooks.add(TestHelper.hookEntry("afterstep", result("passed"))); + hookLocations.add("hook-location"); hookActions.add(createWriteHookAction("printed from afterstep hook")); String formatterOutput = runFeaturesWithFormatter(true); diff --git a/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java index 3aab99850f..caf79f1b15 100755 --- a/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/RerunFormatterTest.java @@ -22,6 +22,7 @@ class RerunFormatterTest { private final List features = new ArrayList<>(); private final Map stepsToResult = new HashMap<>(); private final List> hooks = new ArrayList<>(); + private final List hookLocations = new ArrayList<>(); @Test void should_leave_report_empty_when_exit_code_is_zero() { @@ -134,6 +135,7 @@ void should_use_scenario_location_when_before_hook_fails() { stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("failed"))); + hookLocations.add("hook-location"); String formatterOutput = runFeaturesWithFormatter(false); @@ -153,6 +155,7 @@ void should_use_scenario_location_when_after_hook_fails() { stepsToResult.put("second step", result("passed")); stepsToResult.put("third step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("failed"))); + hookLocations.add("hook-location"); String formatterOutput = runFeaturesWithFormatter(false); @@ -214,6 +217,7 @@ private String runFeaturesWithFormatter(boolean isStrict) { .withFeatures(features) .withStepsToResult(stepsToResult) .withHooks(hooks) + .withHookLocations(hookLocations) .withTimeServiceIncrement(ZERO) .build() .run(); diff --git a/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java index 27dfc1905a..55474a8eea 100644 --- a/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TestNGFormatterTest.java @@ -248,6 +248,8 @@ void testDurationCalculationOfStepsAndHooks() throws Throwable { stepsToResult.put("step", result("passed")); hooks.add(TestHelper.hookEntry("before", result("passed"))); hooks.add(TestHelper.hookEntry("after", result("passed"))); + hookLocations.add("hook-location-1"); + hookLocations.add("hook-location-2"); stepDuration = ofMillis(1); String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + @@ -277,6 +279,7 @@ void testScenarioWithFailedBeforeHook() throws Throwable { features.add(feature); stepsToResult.put("step", result("skipped")); hooks.add(TestHelper.hookEntry("before", result("failed", new TestNGException("message", "stacktrace")))); + hookLocations.add("hook-location"); stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + @@ -309,6 +312,7 @@ void testScenarioWithFailedAfterHook() throws Throwable { features.add(feature); stepsToResult.put("step", result("passed")); hooks.add(TestHelper.hookEntry("after", result("failed", new TestNGException("message", "stacktrace")))); + hookLocations.add("hook-location"); stepDuration = ZERO; String actual = runFeaturesWithFormatter(false); assertXmlEqual("" + diff --git a/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java index 1c14302cb4..d4ddc81fb3 100644 --- a/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TimelineFormatterTest.java @@ -1,8 +1,8 @@ package io.cucumber.core.plugin; -import gherkin.deps.com.google.gson.Gson; -import gherkin.deps.com.google.gson.GsonBuilder; -import gherkin.deps.com.google.gson.JsonDeserializer; +import io.cucumber.messages.internal.com.google.gson.Gson; +import io.cucumber.messages.internal.com.google.gson.GsonBuilder; +import io.cucumber.messages.internal.com.google.gson.JsonDeserializer; import io.cucumber.plugin.event.Result; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.feature.TestFeatureParser; diff --git a/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java index 5902c57979..70b2732c85 100644 --- a/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java +++ b/core/src/test/java/io/cucumber/core/runner/CoreStepDefinitionTest.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.UUID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -27,6 +28,7 @@ class CoreStepDefinitionTest { private final StepTypeRegistry stepTypeRegistry = new StepTypeRegistry(Locale.ENGLISH); + private final UUID id = UUID.randomUUID(); @Test void should_apply_identity_transform_to_doc_string_when_target_type_is_object() { @@ -39,7 +41,7 @@ void should_apply_identity_transform_to_doc_string_when_target_type_is_object() " \"\"\"\n" ); StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); - CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, stepTypeRegistry); + CoreStepDefinition stepDefinition = new CoreStepDefinition(id, stub, stepTypeRegistry); Step step = feature.getPickles().get(0).getSteps().get(0); List arguments = stepDefinition.matchedArguments(step); assertThat(arguments.get(0).getValue(), is(equalTo(DocString.create("content")))); @@ -55,7 +57,7 @@ void should_apply_identity_transform_to_data_table_when_target_type_is_object() " | content |\n" ); StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); - CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, stepTypeRegistry); + CoreStepDefinition stepDefinition = new CoreStepDefinition(id, stub, stepTypeRegistry); List arguments = stepDefinition.matchedArguments(feature.getPickles().get(0).getSteps().get(0)); assertThat(arguments.get(0).getValue(), is(equalTo(DataTable.create(singletonList(singletonList("content")))))); } @@ -69,7 +71,7 @@ void should_convert_empty_pickle_table_cells_to_null_values() { " | |\n" ); StubStepDefinition stub = new StubStepDefinition("I have some step", Object.class); - CoreStepDefinition stepDefinition = new CoreStepDefinition(stub, stepTypeRegistry); + CoreStepDefinition stepDefinition = new CoreStepDefinition(id, stub, stepTypeRegistry); List arguments = stepDefinition.matchedArguments(feature.getPickles().get(0).getSteps().get(0)); assertEquals(DataTable.create(singletonList(singletonList(null))), arguments.get(0).getValue()); } @@ -193,7 +195,7 @@ void passes_transposed_data_table() throws Throwable { @SuppressWarnings("unchecked") private T runStepDef(Method method, boolean transposed, Feature feature) { StubStepDefinition stub = new StubStepDefinition("some text", transposed, method.getGenericParameterTypes()); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stub, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stub, stepTypeRegistry); Step stepWithTable = feature.getPickles().get(0).getSteps().get(0); List arguments = coreStepDefinition.matchedArguments(stepWithTable); diff --git a/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java index aae5edddfe..d4e54d1dbc 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookOrderTest.java @@ -186,6 +186,7 @@ private List mockHooks(int... ordering) { HookDefinition hook = mock(HookDefinition.class, "Mock number " + order); when(hook.getOrder()).thenReturn(order); when(hook.getTagExpression()).thenReturn(""); + when(hook.getLocation()).thenReturn("Mock location"); hooks.add(hook); } return hooks; diff --git a/core/src/test/java/io/cucumber/core/runner/HookTest.java b/core/src/test/java/io/cucumber/core/runner/HookTest.java index 78bb2fcaa4..b42744db84 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookTest.java @@ -47,6 +47,7 @@ void after_hooks_execute_before_objects_are_disposed() { when(backend.getSnippet()).thenReturn(new TestSnippet()); ObjectFactory objectFactory = mock(ObjectFactory.class); final HookDefinition hook = mock(HookDefinition.class); + when(hook.getLocation()).thenReturn("hook-location"); TypeRegistryConfigurer typeRegistryConfigurer = mock(TypeRegistryConfigurer.class); when(hook.getTagExpression()).thenReturn(""); diff --git a/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java b/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java index 97a43d829e..c8bf68c319 100644 --- a/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java +++ b/core/src/test/java/io/cucumber/core/runner/HookTestStepTest.java @@ -45,9 +45,9 @@ class HookTestStepTest { false ); private final EventBus bus = mock(EventBus.class); - private final TestCaseState state = new TestCaseState(bus, testCase); - private final HookTestStep step = new HookTestStep(UUID.randomUUID(), HookType.AFTER_STEP, definitionMatch); private final UUID testExecutionId = UUID.randomUUID(); + private final TestCaseState state = new TestCaseState(bus, testExecutionId, testCase); + private HookTestStep step = new HookTestStep(UUID.randomUUID(), HookType.AFTER_STEP, definitionMatch); @BeforeEach void init() { @@ -56,7 +56,7 @@ void init() { @Test void run_does_run() { - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); InOrder order = inOrder(bus, hookDefintion); order.verify(bus).send(isA(TestStepStarted.class)); @@ -66,7 +66,7 @@ void run_does_run() { @Test void run_does_dry_run() { - step.run(testCase, bus, state, true, testExecutionId); + step.run(testCase, bus, state, true); InOrder order = inOrder(bus, hookDefintion); order.verify(bus).send(isA(TestStepStarted.class)); @@ -76,14 +76,14 @@ void run_does_dry_run() { @Test void result_is_passed_when_step_definition_does_not_throw_exception() { - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertFalse(skipNextStep); assertThat(state.getStatus(), is(equalTo(PASSED))); } @Test void result_is_skipped_when_skip_step_is_skip_all_skipable() { - boolean skipNextStep = step.run(testCase, bus, state, true, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, true); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(SKIPPED))); } diff --git a/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java b/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java index 7df8435ccb..a7943f7ecf 100644 --- a/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java +++ b/core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java @@ -60,7 +60,8 @@ class PickleStepTestStepTest { private final Pickle pickle = feature.getPickles().get(0); private final TestCase testCase = new TestCase(UUID.randomUUID(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), pickle, false); private final EventBus bus = mock(EventBus.class); - private final TestCaseState state = new TestCaseState(bus, testCase); + private final UUID testExecutionId = UUID.randomUUID(); + private final TestCaseState state = new TestCaseState(bus, testExecutionId, testCase); private final PickleStepDefinitionMatch definitionMatch = mock(PickleStepDefinitionMatch.class); private final CoreHookDefinition afterHookDefinition = mock(CoreHookDefinition.class); private final HookTestStep afterHook = new HookTestStep(UUID.randomUUID(), AFTER_STEP, new HookDefinitionMatch(afterHookDefinition)); @@ -74,7 +75,6 @@ class PickleStepTestStepTest { singletonList(afterHook), definitionMatch ); - private final UUID testExecutionId = UUID.randomUUID(); private static ArgumentMatcher scenarioDoesNotHave(final Throwable type) { return argument -> !type.equals(argument.getError()); } @@ -86,7 +86,7 @@ void init() { @Test void run_wraps_run_step_in_test_step_started_and_finished_events() throws Throwable { - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); InOrder order = inOrder(bus, definitionMatch); order.verify(bus).send(isA(TestStepStarted.class)); @@ -96,7 +96,7 @@ void run_wraps_run_step_in_test_step_started_and_finished_events() throws Throwa @Test void run_does_dry_run_step_when_skip_steps_is_true() throws Throwable { - step.run(testCase, bus, state, true, testExecutionId); + step.run(testCase, bus, state, true); InOrder order = inOrder(bus, definitionMatch); order.verify(bus).send(isA(TestStepStarted.class)); @@ -106,14 +106,14 @@ void run_does_dry_run_step_when_skip_steps_is_true() throws Throwable { @Test void result_is_passed_when_step_definition_does_not_throw_exception() { - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertFalse(skipNextStep); assertThat(state.getStatus(), is(equalTo(PASSED))); } @Test void result_is_skipped_when_skip_step_is_not_run_all() { - boolean skipNextStep = step.run(testCase, bus, state, true, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, true); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(SKIPPED))); @@ -122,7 +122,7 @@ void result_is_skipped_when_skip_step_is_not_run_all() { @Test void result_is_skipped_when_before_step_hook_does_not_pass() { doThrow(TestAbortedException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(SKIPPED))); } @@ -130,7 +130,7 @@ void result_is_skipped_when_before_step_hook_does_not_pass() { @Test void step_execution_is_dry_run_when_before_step_hook_does_not_pass() throws Throwable { doThrow(TestAbortedException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); verify(definitionMatch).dryRunStep(any(TestCaseState.class)); } @@ -139,14 +139,14 @@ void result_is_result_from_hook_when_before_step_hook_does_not_pass() { Exception exception = new RuntimeException(); doThrow(exception).when(beforeHookDefinition).execute(any(TestCaseState.class)); Result failure = new Result(Status.FAILED, ZERO, exception); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(FAILED))); ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); + verify(bus, times(12)).send(captor.capture()); List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(1)).getResult(), is(equalTo(failure))); + assertThat(((TestStepFinished) allValues.get(2)).getResult(), is(equalTo(failure))); } @Test @@ -154,14 +154,14 @@ void result_is_result_from_step_when_step_hook_does_not_pass() throws Throwable RuntimeException runtimeException = new RuntimeException(); Result failure = new Result(Status.FAILED, ZERO, runtimeException); doThrow(runtimeException).when(definitionMatch).runStep(any(TestCaseState.class)); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(FAILED))); ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); + verify(bus, times(12)).send(captor.capture()); List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(3)).getResult(), is(equalTo(failure))); + assertThat(((TestStepFinished) allValues.get(6)).getResult(), is(equalTo(failure))); } @Test @@ -169,27 +169,27 @@ void result_is_result_from_hook_when_after_step_hook_does_not_pass() { Exception exception = new RuntimeException(); Result failure = new Result(Status.FAILED, ZERO, exception); doThrow(exception).when(afterHookDefinition).execute(any(TestCaseState.class)); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(FAILED))); - ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); - List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(5)).getResult(), is(equalTo(failure))); + ArgumentCaptor captor = forClass(TestCaseEvent.class); + verify(bus, times(12)).send(captor.capture()); + List allValues = captor.getAllValues(); + assertThat(((TestStepFinished) allValues.get(10)).getResult(), is(equalTo(failure))); } @Test void after_step_hook_is_run_when_before_step_hook_does_not_pass() { doThrow(RuntimeException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); verify(afterHookDefinition).execute(any(TestCaseState.class)); } @Test void after_step_hook_is_run_when_step_does_not_pass() throws Throwable { doThrow(Exception.class).when(definitionMatch).runStep(any(TestCaseState.class)); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); verify(afterHookDefinition).execute(any(TestCaseState.class)); } @@ -198,7 +198,7 @@ void after_step_hook_scenario_contains_step_failure_when_step_does_not_pass() th Throwable expectedError = new TestAbortedException("oops"); doThrow(expectedError).when(definitionMatch).runStep(any(TestCaseState.class)); doThrow(new RuntimeException()).when(afterHookDefinition).execute(argThat(scenarioDoesNotHave(expectedError))); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); assertThat(state.getError(), is(expectedError)); } @@ -207,7 +207,7 @@ void after_step_hook_scenario_contains_before_step_hook_failure_when_before_step Throwable expectedError = new TestAbortedException("oops"); doThrow(expectedError).when(beforeHookDefinition).execute(any(TestCaseState.class)); doThrow(new RuntimeException()).when(afterHookDefinition).execute(argThat(scenarioDoesNotHave(expectedError))); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); assertThat(state.getError(), is(expectedError)); } @@ -215,7 +215,7 @@ void after_step_hook_scenario_contains_before_step_hook_failure_when_before_step void result_is_skipped_when_step_definition_throws_assumption_violated_exception() throws Throwable { doThrow(TestAbortedException.class).when(definitionMatch).runStep(any()); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(SKIPPED))); @@ -225,7 +225,7 @@ void result_is_skipped_when_step_definition_throws_assumption_violated_exception void result_is_failed_when_step_definition_throws_exception() throws Throwable { doThrow(RuntimeException.class).when(definitionMatch).runStep(any(TestCaseState.class)); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(FAILED))); @@ -235,7 +235,7 @@ void result_is_failed_when_step_definition_throws_exception() throws Throwable { void result_is_pending_when_step_definition_throws_pending_exception() throws Throwable { doThrow(TestPendingException.class).when(definitionMatch).runStep(any(TestCaseState.class)); - boolean skipNextStep = step.run(testCase, bus, state, false, testExecutionId); + boolean skipNextStep = step.run(testCase, bus, state, false); assertTrue(skipNextStep); assertThat(state.getStatus(), is(equalTo(PENDING))); @@ -256,14 +256,14 @@ void step_execution_time_is_measured() { definitionMatch ); when(bus.getInstant()).thenReturn(ofEpochMilli(234L), ofEpochMilli(1234L)); - step.run(testCase, bus, state, false, testExecutionId); + step.run(testCase, bus, state, false); ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(2)).send(captor.capture()); + verify(bus, times(4)).send(captor.capture()); List allValues = captor.getAllValues(); TestStepStarted started = (TestStepStarted) allValues.get(0); - TestStepFinished finished = (TestStepFinished) allValues.get(1); + TestStepFinished finished = (TestStepFinished) allValues.get(2); assertAll("Checking TestStep", () -> assertThat(started.getInstant(), is(equalTo(ofEpochMilli(234L)))), diff --git a/core/src/test/java/io/cucumber/core/runner/RunnerTest.java b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java index 361d06e4ee..e65e6b7f17 100644 --- a/core/src/test/java/io/cucumber/core/runner/RunnerTest.java +++ b/core/src/test/java/io/cucumber/core/runner/RunnerTest.java @@ -77,7 +77,7 @@ void steps_are_skipped_after_failure() { Pickle pickleMatchingStepDefinitions = createPickleMatchingStepDefinitions(stepDefinition); final HookDefinition failingBeforeHook = addBeforeHook(); - doThrow(RuntimeException.class).when(failingBeforeHook).execute(ArgumentMatchers.any()); + doThrow(new RuntimeException("Boom")).when(failingBeforeHook).execute(ArgumentMatchers.any()); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -106,21 +106,21 @@ public void execute(Object[] args) { Pickle pickleMatchingStepDefinitions = createPickleMatchingStepDefinitions(stepDefinition); - final HookDefinition afteStepHook = addAfterStepHook(); + final HookDefinition afterStepHook = addAfterStepHook(); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { - glue.addAfterHook(afteStepHook); + glue.addAfterHook(afterStepHook); glue.addStepDefinition(stepDefinition); } }; runnerSupplier.get().runPickle(pickleMatchingStepDefinitions); - InOrder inOrder = inOrder(afteStepHook, stepDefinition); + InOrder inOrder = inOrder(afterStepHook, stepDefinition); inOrder.verify(stepDefinition).execute(any(Object[].class)); - inOrder.verify(afteStepHook).execute(any(TestCaseState.class)); + inOrder.verify(afterStepHook).execute(any(TestCaseState.class)); } @Test @@ -151,7 +151,7 @@ public void loadGlue(Glue glue, List gluePaths) { @Test void hooks_execute_also_after_failure() { final HookDefinition failingBeforeHook = addBeforeHook(); - doThrow(RuntimeException.class).when(failingBeforeHook).execute(any(TestCaseState.class)); + doThrow(new RuntimeException("boom")).when(failingBeforeHook).execute(any(TestCaseState.class)); final HookDefinition beforeHook = addBeforeHook(); final HookDefinition afterHook = addAfterHook(); @@ -274,6 +274,7 @@ private HookDefinition addAfterStepHook() { private HookDefinition addHook() { HookDefinition hook = mock(HookDefinition.class); when(hook.getTagExpression()).thenReturn(""); + when(hook.getLocation()).thenReturn(""); return hook; } diff --git a/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java index c6fbc84580..b1b1622c86 100644 --- a/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java +++ b/core/src/test/java/io/cucumber/core/runner/StepDefinitionMatchTest.java @@ -21,6 +21,7 @@ import java.net.URI; import java.util.Collections; import java.util.List; +import java.util.UUID; import static java.util.Arrays.asList; import static java.util.Locale.ENGLISH; @@ -33,6 +34,7 @@ class StepDefinitionMatchTest { private final StepTypeRegistry stepTypeRegistry = new StepTypeRegistry(ENGLISH); + private final UUID id = UUID.randomUUID(); private final Located stubbedLocation = new Located() { @Override @@ -57,7 +59,7 @@ void executes_a_step() throws Throwable { Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); stepDefinitionMatch.runStep(null); @@ -73,7 +75,7 @@ void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_argume Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -99,7 +101,7 @@ void throws_arity_mismatch_exception_when_there_are_fewer_parameters_than_argume Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly"); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -129,7 +131,7 @@ void throws_arity_mismatch_exception_when_there_are_more_parameters_than_argumen Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {int} cukes in my belly", Integer.TYPE, Short.TYPE, List.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); PickleStepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -156,7 +158,7 @@ void throws_arity_mismatch_exception_when_there_are_more_parameters_and_no_argum ); Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have cukes in my belly", Integer.TYPE, Short.TYPE, List.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -183,7 +185,7 @@ void throws_register_type_in_configuration_exception_when_there_is_no_data_table "I have a data table", UndefinedDataTableType.class ); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch( arguments, @@ -216,7 +218,7 @@ void throws_could_not_convert_exception_for_transformer_and_capture_group_mismat ); Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -248,7 +250,7 @@ void rethrows_target_invocation_exceptions_from_parameter_type() { ); Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have {itemQuantity} in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -272,7 +274,7 @@ void throws_could_not_convert_exception_for_singleton_table_dimension_mismatch() Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -305,7 +307,7 @@ void rethrows_target_invocation_exceptions_from_data_table() { Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -331,7 +333,7 @@ void throws_could_not_convert_exception_for_docstring() { Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); @@ -362,7 +364,7 @@ void rethrows_target_invocation_exception_for_docstring() { Step step = feature.getPickles().get(0).getSteps().get(0); StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class); - CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry); + CoreStepDefinition coreStepDefinition = new CoreStepDefinition(id, stepDefinition, stepTypeRegistry); List arguments = coreStepDefinition.matchedArguments(step); StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step); diff --git a/core/src/test/java/io/cucumber/core/runner/TestCaseStateResultTest.java b/core/src/test/java/io/cucumber/core/runner/TestCaseStateResultTest.java index c2921b1079..6e9040039a 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestCaseStateResultTest.java +++ b/core/src/test/java/io/cucumber/core/runner/TestCaseStateResultTest.java @@ -43,6 +43,7 @@ class TestCaseStateResultTest { private final EventBus bus = mock(EventBus.class); private final TestCaseState s = new TestCaseState( bus, + UUID.randomUUID(), new TestCase( UUID.randomUUID(), Collections.emptyList(), @@ -56,6 +57,7 @@ class TestCaseStateResultTest { @BeforeEach void setup() { when(bus.getInstant()).thenReturn(Instant.now()); + s.setCurrentTestStepId(UUID.randomUUID()); } @Test diff --git a/core/src/test/java/io/cucumber/core/runner/TestCaseStateTest.java b/core/src/test/java/io/cucumber/core/runner/TestCaseStateTest.java index f74e61ff18..8a1e73e088 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestCaseStateTest.java +++ b/core/src/test/java/io/cucumber/core/runner/TestCaseStateTest.java @@ -82,7 +82,9 @@ void provides_the_uri_and_example_row_line_as_unique_id_for_scenarios_from_scena } private TestCaseState createTestCaseState(Feature feature) { - return new TestCaseState(mock(EventBus.class), new TestCase( + return new TestCaseState(mock(EventBus.class), + UUID.randomUUID(), + new TestCase( UUID.randomUUID(), Collections.emptyList(), Collections.emptyList(), diff --git a/core/src/test/java/io/cucumber/core/runner/TestHelper.java b/core/src/test/java/io/cucumber/core/runner/TestHelper.java index 7df9a83cbc..b0338343df 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestHelper.java +++ b/core/src/test/java/io/cucumber/core/runner/TestHelper.java @@ -379,6 +379,9 @@ private static void mockHook(final SimpleEntry hookEntry, final List beforeStepHooks, final List afterStepHooks) { HookDefinition hook = mock(HookDefinition.class); + if(hookLocation == null) { + throw new RuntimeException("hookLocation cannot be null"); + } when(hook.getTagExpression()).thenReturn(""); if (hookLocation != null) { when(hook.getLocation()).thenReturn(hookLocation); diff --git a/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java index d8aab3e609..9bcbed59dc 100644 --- a/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java +++ b/core/src/test/java/io/cucumber/core/runtime/RuntimeTest.java @@ -13,7 +13,6 @@ import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.options.CommandlineOptionsParser; import io.cucumber.core.options.RuntimeOptionsBuilder; -import io.cucumber.core.plugin.FormatterBuilder; import io.cucumber.core.plugin.FormatterSpy; import io.cucumber.core.runner.StepDurationTimeService; import io.cucumber.core.runner.TestBackendSupplier; @@ -66,81 +65,6 @@ class RuntimeTest { private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); - @Test - void runs_feature_with_json_formatter() { - final Feature feature = TestFeatureParser.parse("test.feature", "" + - "Feature: feature name\n" + - " Background: background name\n" + - " Given b\n" + - " Scenario: scenario name\n" + - " When s\n"); - StringBuilder out = new StringBuilder(); - - Plugin jsonFormatter = FormatterBuilder.jsonFormatter(out); - - FeatureSupplier featureSupplier = new TestFeatureSupplier(bus, feature); - Runtime.builder() - .withAdditionalPlugins(jsonFormatter) - .withEventBus(new TimeServiceEventBus(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC")), UUID::randomUUID)) - .withFeatureSupplier(featureSupplier) - .build() - .run(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"line\": 2,\n" + - " \"name\": \"background name\",\n" + - " \"description\": \"\",\n" + - " \"type\": \"background\",\n" + - " \"keyword\": \"Background\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"status\": \"undefined\"\n" + - " },\n" + - " \"line\": 3,\n" + - " \"name\": \"b\",\n" + - " \"match\": {},\n" + - " \"keyword\": \"Given \"\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"line\": 4,\n" + - " \"name\": \"scenario name\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"feature-name;scenario-name\",\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"status\": \"undefined\"\n" + - " },\n" + - " \"line\": 5,\n" + - " \"name\": \"s\",\n" + - " \"match\": {},\n" + - " \"keyword\": \"When \"\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"name\": \"feature name\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"feature-name\",\n" + - " \"keyword\": \"Feature\",\n" + - " \"uri\": \"file:test.feature\",\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertThat(out.toString(), sameJSONAs(expected)); - } - @Test void strict_with_passed_scenarios() { Runtime runtime = createStrictRuntime(); @@ -257,6 +181,7 @@ void should_make_scenario_name_available_to_hooks() { " When second step\n" + " Then third step\n"); final HookDefinition beforeHook = mock(HookDefinition.class); + when(beforeHook.getLocation()).thenReturn(""); when(beforeHook.getTagExpression()).thenReturn(""); TestBackendSupplier testBackendSupplier = createTestBackendSupplier(feature, beforeHook); diff --git a/core/src/test/resources/io/cucumber/core/plugin/HTMLFormatterTest.feature b/core/src/test/resources/io/cucumber/core/plugin/HTMLFormatterTest.feature deleted file mode 100644 index 1c260cf28f..0000000000 --- a/core/src/test/resources/io/cucumber/core/plugin/HTMLFormatterTest.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Hello - - Scenario: World - Given a' - Given b" - Given & \ No newline at end of file diff --git a/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature b/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature deleted file mode 100644 index 3f1873e675..0000000000 --- a/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.feature +++ /dev/null @@ -1,28 +0,0 @@ -Feature: Feature_3 - - Background: - Given bg_1 - When bg_2 - Then bg_3 - - Scenario: Scenario_1 - Given step_1 - When step_2 - Then step_3 - Then cliché - - Scenario Outline: ScenarioOutline_1 - Given so_1 - When so_2 cucumbers - Then so_3 - - Examples: - | a | b | c | - | 12 | 5 | 7 | - | 20 | 5 | 15 | - - Scenario: Scenario_2 - Given a - Then b - When c - diff --git a/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json b/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json deleted file mode 100644 index 9db6012f44..0000000000 --- a/core/src/test/resources/io/cucumber/core/plugin/JSONPrettyFormatterTest.json +++ /dev/null @@ -1,354 +0,0 @@ -[ - { - "line": 1, - "elements": [ - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 4, - "name": "bg_1", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 5, - "name": "bg_2", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 6, - "name": "bg_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:11.106Z", - "before": [ - { - "result": { - "duration": 1234000000, - "status": "passed" - }, - "match": {} - } - ], - "line": 8, - "name": "Scenario_1", - "description": "", - "id": "feature-3;scenario-1", - "type": "scenario", - "keyword": "Scenario", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 9, - "name": "step_1", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 10, - "name": "step_2", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 11, - "name": "step_3", - "match": {}, - "keyword": "Then " - }, - { - "result": { - "status": "undefined" - }, - "line": 12, - "name": "cliché", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 4, - "name": "bg_1", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 5, - "name": "bg_2", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 6, - "name": "bg_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:40.722Z", - "before": [ - { - "result": { - "duration": 1234000000, - "status": "passed" - }, - "match": {} - } - ], - "line": 21, - "name": "ScenarioOutline_1", - "description": "", - "id": "feature-3;scenariooutline-1;;2", - "type": "scenario", - "keyword": "Scenario Outline", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 15, - "name": "so_1 12", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 16, - "name": "so_2 7 cucumbers", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 17, - "name": "5 so_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 4, - "name": "bg_1", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 5, - "name": "bg_2", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 6, - "name": "bg_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:01:07.870Z", - "before": [ - { - "result": { - "duration": 1234000000, - "status": "passed" - }, - "match": {} - } - ], - "line": 22, - "name": "ScenarioOutline_1", - "description": "", - "id": "feature-3;scenariooutline-1;;3", - "type": "scenario", - "keyword": "Scenario Outline", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 15, - "name": "so_1 20", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 16, - "name": "so_2 15 cucumbers", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 17, - "name": "5 so_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 4, - "name": "bg_1", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 5, - "name": "bg_2", - "match": {}, - "keyword": "When " - }, - { - "result": { - "status": "undefined" - }, - "line": 6, - "name": "bg_3", - "match": {}, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:01:35.018Z", - "before": [ - { - "result": { - "duration": 1234000000, - "status": "passed" - }, - "match": {} - } - ], - "line": 24, - "name": "Scenario_2", - "description": "", - "id": "feature-3;scenario-2", - "type": "scenario", - "keyword": "Scenario", - "steps": [ - { - "result": { - "status": "undefined" - }, - "line": 25, - "name": "a", - "match": {}, - "keyword": "Given " - }, - { - "result": { - "status": "undefined" - }, - "line": 26, - "name": "b", - "match": {}, - "keyword": "Then " - }, - { - "result": { - "status": "undefined" - }, - "line": 27, - "name": "c", - "match": {}, - "keyword": "When " - } - ] - } - ], - "name": "Feature_3", - "description": "", - "id": "feature-3", - "keyword": "Feature", - "uri": "classpath:io/cucumber/core/plugin/JSONPrettyFormatterTest.feature", - "tags": [] - } -] diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberByCompositionTest.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberByCompositionTest.java index 1749d99f08..5550a77082 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberByCompositionTest.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberByCompositionTest.java @@ -14,7 +14,7 @@ * AbstractTestNGCucumberTests but still executes each scenario as a separate * TestNG test. */ -@CucumberOptions(strict = true, plugin = "json:target/cucumber-report-feature-composite.json") +@CucumberOptions(strict = true, plugin = "message:target/cucumber-report-feature-composite.ndjson") public class RunCucumberByCompositionTest extends RunCucumberByCompositionBase { private TestNGCucumberRunner testNGCucumberRunner; diff --git a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java index 067a8f84c5..d17b40d009 100644 --- a/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java +++ b/examples/java-calculator-testng/src/test/java/io/cucumber/examples/testng/RunCucumberTest.java @@ -4,7 +4,7 @@ import io.cucumber.testng.CucumberOptions; import org.testng.annotations.DataProvider; -@CucumberOptions(plugin = {"summary","json:target/cucumber-report.json"}) +@CucumberOptions(plugin = {"summary","message:target/cucumber-report.json"}) public class RunCucumberTest extends AbstractTestNGCucumberTests { @DataProvider(parallel = true) diff --git a/examples/java-calculator/pom.xml b/examples/java-calculator/pom.xml index fecebac0d9..4f5ac3137c 100644 --- a/examples/java-calculator/pom.xml +++ b/examples/java-calculator/pom.xml @@ -22,6 +22,11 @@ cucumber-java test + + io.cucumber + cucumber-gherkin-messages + test + io.cucumber cucumber-junit diff --git a/examples/java-calculator/src/test/java/io/cucumber/examples/java/RunCucumberTest.java b/examples/java-calculator/src/test/java/io/cucumber/examples/java/RunCucumberTest.java index 75b352a3fd..d48932996a 100644 --- a/examples/java-calculator/src/test/java/io/cucumber/examples/java/RunCucumberTest.java +++ b/examples/java-calculator/src/test/java/io/cucumber/examples/java/RunCucumberTest.java @@ -5,7 +5,7 @@ import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = {"json:target/cucumber-report.json", "pretty"}) +@CucumberOptions(plugin = {"html:target/results.html", "message:target/results.ndjson"}) public class RunCucumberTest { } diff --git a/examples/java-calculator/src/test/resources/io/cucumber/examples/java/basic_arithmetic.feature b/examples/java-calculator/src/test/resources/io/cucumber/examples/java/basic_arithmetic.feature index 9144af87f4..6ffef726c6 100644 --- a/examples/java-calculator/src/test/resources/io/cucumber/examples/java/basic_arithmetic.feature +++ b/examples/java-calculator/src/test/resources/io/cucumber/examples/java/basic_arithmetic.feature @@ -4,32 +4,32 @@ Feature: Basic Arithmetic Background: A Calculator Given a calculator I just turned on - Scenario: Addition + Scenario: Addition # Try to change one of the values below to provoke a failure - When I add 4 and 5 - Then the result is 9 + When I add 4 and 5 + Then the result is 9 - Scenario: Another Addition + Scenario: Another Addition # Try to change one of the values below to provoke a failure - When I add 4 and 7 - Then the result is 11 + When I add 4 and 7 + Then the result is 11 - Scenario Outline: Many additions - Given the previous entries: - | first | second | operation | - | 1 | 1 | + | - | 2 | 1 | + | - When I press + - And I add and - And I press + - Then the result is + Scenario Outline: Many additions + Given the previous entries: + | first | second | operation | + | 1 | 1 | + | + | 2 | 1 | + | + When I press + + And I add and + And I press + + Then the result is - Examples: Single digits - | a | b | c | - | 1 | 2 | 8 | - | 2 | 3 | 10 | + Examples: Single digits + | a | b | c | + | 1 | 2 | 8 | + | 2 | 3 | 10 | - Examples: Double digits - | a | b | c | - | 10 | 20 | 35 | - | 20 | 30 | 55 | + Examples: Double digits + | a | b | c | + | 10 | 20 | 35 | + | 20 | 30 | 55 | diff --git a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RunCucumberTest.java b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RunCucumberTest.java index ca2e72907f..663d9766ca 100644 --- a/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RunCucumberTest.java +++ b/examples/java8-calculator/src/test/java/io/cucumber/examples/java8/RunCucumberTest.java @@ -5,6 +5,6 @@ import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = "json:target/cucumber-report.json") +@CucumberOptions(plugin = "message:target/cucumber-report.ndjson") public class RunCucumberTest { } diff --git a/gherkin-messages/pom.xml b/gherkin-messages/pom.xml index b9954aa701..41eb3a4ef9 100644 --- a/gherkin-messages/pom.xml +++ b/gherkin-messages/pom.xml @@ -28,8 +28,9 @@ - io.cucumber - messages + org.junit.jupiter + junit-jupiter + test diff --git a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesDocStringArgument.java b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesDocStringArgument.java index c818483aee..4d3d93dc63 100644 --- a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesDocStringArgument.java +++ b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesDocStringArgument.java @@ -20,7 +20,16 @@ public String getContent() { @Override public String getContentType() { - return docString.getMediaType(); + return getMediaType(); + } + + @Override + public String getMediaType() { + String mediaType = docString.getMediaType(); + if ("".equals(mediaType)) { + return null; + } + return mediaType; } @Override diff --git a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeature.java b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeature.java index 3b03400023..0815ad2792 100644 --- a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeature.java +++ b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeature.java @@ -94,6 +94,11 @@ public String getSource() { return gherkinSource; } + @Override + public Iterable getParseEvents() { + return envelopes; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java index 411b5dc69f..24ba4107ff 100644 --- a/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java +++ b/gherkin-messages/src/main/java/io/cucumber/core/gherkin/messages/GherkinMessagesFeatureParser.java @@ -7,13 +7,11 @@ import io.cucumber.gherkin.Gherkin; import io.cucumber.gherkin.GherkinDialect; import io.cucumber.gherkin.GherkinDialectProvider; -import io.cucumber.gherkin.ParserException; import io.cucumber.messages.Messages; import io.cucumber.messages.Messages.Envelope; import io.cucumber.messages.Messages.GherkinDocument; import java.net.URI; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -27,60 +25,63 @@ public final class GherkinMessagesFeatureParser implements FeatureParser { @Override public Optional parse(URI path, String source, Supplier idGenerator) { - try { + List sources = singletonList( + makeSourceEnvelope(source, path.toString()) + ); - List sources = singletonList( - makeSourceEnvelope(source, path.toString()) - ); + List envelopes = Gherkin.fromSources( + sources, + true, + true, + true, + () -> idGenerator.get().toString() + ).collect(toList()); - List envelopes = Gherkin.fromSources( - sources, - true, - true, - true, - () -> idGenerator.get().toString() - ).collect(toList()); + GherkinDocument gherkinDocument = envelopes.stream() + .filter(Envelope::hasGherkinDocument) + .map(Envelope::getGherkinDocument) + .findFirst() + .orElse(null); - GherkinDocument gherkinDocument = envelopes.stream() - .filter(Envelope::hasGherkinDocument) - .map(Envelope::getGherkinDocument) - .findFirst() - .orElse(null); - - if (gherkinDocument == null || !gherkinDocument.hasFeature()) { - return Optional.empty(); + if (gherkinDocument == null || !gherkinDocument.hasFeature()) { + List errors = envelopes.stream() + .filter(Envelope::hasAttachment) + .map(Envelope::getAttachment) + .map(Messages.Attachment::getText) + .collect(toList()); + if (!errors.isEmpty()) { + throw new FeatureParserException("Failed to parse resource at: " + path.toString() + "\n" + String.join("\n", errors)); } + return Optional.empty(); + } - CucumberQuery cucumberQuery = new CucumberQuery(); - cucumberQuery.update(gherkinDocument); - GherkinDialectProvider dialectProvider = new GherkinDialectProvider(); - String language = gherkinDocument.getFeature().getLanguage(); - GherkinDialect dialect = dialectProvider.getDialect(language, null); + CucumberQuery cucumberQuery = new CucumberQuery(); + cucumberQuery.update(gherkinDocument); + GherkinDialectProvider dialectProvider = new GherkinDialectProvider(); + String language = gherkinDocument.getFeature().getLanguage(); + GherkinDialect dialect = dialectProvider.getDialect(language, null); - List pickleMessages = envelopes.stream() - .filter(Envelope::hasPickle) - .map(Envelope::getPickle) - .collect(toList()); + List pickleMessages = envelopes.stream() + .filter(Envelope::hasPickle) + .map(Envelope::getPickle) + .collect(toList()); - if (pickleMessages.isEmpty()) { - return Optional.empty(); - } + if (pickleMessages.isEmpty()) { + return Optional.empty(); + } - List pickles = pickleMessages.stream() - .map(pickle -> new GherkinMessagesPickle(pickle, path, dialect, cucumberQuery)) - .collect(toList()); + List pickles = pickleMessages.stream() + .map(pickle -> new GherkinMessagesPickle(pickle, path, dialect, cucumberQuery)) + .collect(toList()); - GherkinMessagesFeature feature = new GherkinMessagesFeature( - gherkinDocument, - path, - source, - pickles, - envelopes - ); - return Optional.of(feature); - } catch (ParserException e) { - throw new FeatureParserException("Failed to parse resource at: " + path.toString(), e); - } + GherkinMessagesFeature feature = new GherkinMessagesFeature( + gherkinDocument, + path, + source, + pickles, + envelopes + ); + return Optional.of(feature); } @Override diff --git a/gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java b/gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java index 30d7a97e90..b1568f0360 100644 --- a/gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java +++ b/gherkin-messages/src/test/java/io/cucumber/core/gherkin/messages/FeatureParserTest.java @@ -1,7 +1,9 @@ package io.cucumber.core.gherkin.messages; import io.cucumber.core.gherkin.DataTableArgument; +import io.cucumber.core.gherkin.DocStringArgument; import io.cucumber.core.gherkin.Feature; +import io.cucumber.core.gherkin.FeatureParserException; import io.cucumber.core.gherkin.Pickle; import io.cucumber.core.gherkin.Step; import org.junit.jupiter.api.Test; @@ -9,12 +11,16 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; import java.util.UUID; import static java.nio.file.Files.readAllBytes; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; class FeatureParserTest { @@ -47,4 +53,34 @@ void empty_table_is_parsed() throws IOException { assertEquals(5, argument.getLine()); } + @Test + void empty_doc_string_media_type_is_null() throws IOException { + URI uri = URI.create("classpath:com/example.feature"); + String source = new String(readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature"))); + Feature feature = parser.parse(uri, source, UUID::randomUUID).get(); + Pickle pickle = feature.getPickles().get(0); + List steps = pickle.getSteps(); + + assertAll(() -> { + assertNull(((DocStringArgument) steps.get(0).getArgument()).getContentType()); + assertEquals("text/plain", ((DocStringArgument) steps.get(1).getArgument()).getContentType()); + }); + } + + @Test + void lexer_error_throws_exception() throws IOException { + URI uri = URI.create("classpath:com/example.feature"); + String source = new String(readAllBytes(Paths.get("src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature"))); + FeatureParserException exception = assertThrows(FeatureParserException.class, () -> parser.parse(uri, source, UUID::randomUUID)); + assertEquals("" + + "Failed to parse resource at: classpath:com/example.feature\n" + + "(1:1): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Feature FA'\n" + + "(3:3): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Scenario SA'\n" + + "(4:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Given GA'\n" + + "(5:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'When GA'\n" + + "(6:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Then TA'", + exception.getMessage() + ); + } + } diff --git a/gherkin-messages/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService b/gherkin-messages/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService new file mode 100644 index 0000000000..00e8fc283b --- /dev/null +++ b/gherkin-messages/src/test/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService @@ -0,0 +1 @@ +io.cucumber.core.gherkin.messages.StubBackendProviderService diff --git a/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature b/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature new file mode 100644 index 0000000000..f91cb06b60 --- /dev/null +++ b/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/doc-string.feature @@ -0,0 +1,11 @@ +Feature: Doc String + + Scenario: This is valid Gherkin + Given an doc string + """ + This doc string has no content type + """ + Given an doc string with content type + """text/plain + This doc string has content a type + """ diff --git a/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature b/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature new file mode 100644 index 0000000000..9527c2c33d --- /dev/null +++ b/gherkin-messages/src/test/resources/io/cucumber/core/gherkin/messages/lexer-error.feature @@ -0,0 +1,6 @@ +Feature FA + + Scenario SA + Given GA + When GA + Then TA \ No newline at end of file diff --git a/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageDocStringArgument.java b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageDocStringArgument.java index 8bfb49b8d0..69610985e3 100644 --- a/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageDocStringArgument.java +++ b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageDocStringArgument.java @@ -21,6 +21,11 @@ public String getContentType() { return docString.getContentType(); } + @Override + public String getMediaType() { + return docString.getContentType(); + } + @Override public int getLine() { return docString.getLocation().getLine(); diff --git a/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageFeature.java b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageFeature.java index d91c107a1e..e12b43a4e7 100644 --- a/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageFeature.java +++ b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/GherkinVintageFeature.java @@ -15,6 +15,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static java.util.Collections.emptyList; + final class GherkinVintageFeature implements Feature { private final URI uri; private final List pickles; @@ -86,6 +88,11 @@ public String getSource() { return gherkinSource; } + @Override + public Iterable getParseEvents() { + return emptyList(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/JsonFormatter.java similarity index 95% rename from core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java rename to gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/JsonFormatter.java index e1df5b1ee1..27745c7598 100644 --- a/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java +++ b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/JsonFormatter.java @@ -1,4 +1,4 @@ -package io.cucumber.core.plugin; +package io.cucumber.core.gherkin.vintage; import gherkin.ast.Background; import gherkin.ast.Feature; @@ -27,9 +27,13 @@ import io.cucumber.plugin.event.TestStepStarted; import io.cucumber.plugin.event.WriteEvent; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -39,10 +43,9 @@ import java.util.List; import java.util.Map; -import static io.cucumber.core.plugin.TestSourcesModel.relativize; import static java.util.Locale.ROOT; -public final class JSONFormatter implements EventListener { +public final class JsonFormatter implements EventListener { private static final String before = "before"; private static final String after = "after"; private URI currentFeatureFile; @@ -54,12 +57,12 @@ public final class JSONFormatter implements EventListener { private Map currentStepOrHookMap; private final Map currentBeforeStepHookList = new HashMap<>(); private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private final NiceAppendable out; + private final OutputStreamWriter out; private final TestSourcesModel testSources = new TestSourcesModel(); @SuppressWarnings("WeakerAccess") // Used by PluginFactory - public JSONFormatter(Appendable out) { - this.out = new NiceAppendable(out); + public JsonFormatter(OutputStream out) { + this.out = new OutputStreamWriter(out, StandardCharsets.UTF_8); } @Override @@ -135,12 +138,16 @@ private void handleTestStepFinished(TestStepFinished event) { private void finishReport() { gson.toJson(featureMaps, out); - out.close(); + try { + out.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } private Map createFeatureMap(TestCase testCase) { Map featureMap = new HashMap<>(); - featureMap.put("uri", relativize(testCase.getUri())); + featureMap.put("uri", TestSourcesModel.relativize(testCase.getUri())); featureMap.put("elements", new ArrayList>()); Feature feature = testSources.getFeature(testCase.getUri()); if (feature != null) { @@ -236,7 +243,7 @@ private Map createDocStringMap(DocStringArgument docString) { Map docStringMap = new HashMap<>(); docStringMap.put("value", docString.getContent()); docStringMap.put("line", docString.getLine()); - docStringMap.put("content_type", docString.getContentType()); + docStringMap.put("content_type", docString.getMediaType()); return docStringMap; } diff --git a/core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/TestSourcesModel.java similarity index 98% rename from core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java rename to gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/TestSourcesModel.java index 0cc21506b0..6b246bd01a 100644 --- a/core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java +++ b/gherkin-vintage/src/main/java/io/cucumber/core/gherkin/vintage/TestSourcesModel.java @@ -1,4 +1,4 @@ -package io.cucumber.core.plugin; +package io.cucumber.core.gherkin.vintage; import gherkin.AstBuilder; import gherkin.Parser; @@ -13,7 +13,6 @@ import gherkin.ast.ScenarioOutline; import gherkin.ast.Step; import gherkin.ast.TableRow; -import io.cucumber.core.exception.CucumberException; import io.cucumber.plugin.event.TestSourceRead; import java.io.File; @@ -153,7 +152,7 @@ private void parseGherkinSource(URI path) { // parsing. So if we couldn't parse the feature, it will throw // before emitting the event. So if we can't parse it now, it was // not parsed by the Gherkin 5 parser. - throw new CucumberException("" + + throw new RuntimeException("" + "You are using a plugin that does not support Gherkin 8+.\n" + "Try to remove the html and/or json formatters. See the\n" + "Cucumber-JVM 5.0.0 release announcement for more information.", diff --git a/gherkin/src/main/java/io/cucumber/core/gherkin/DataTableArgument.java b/gherkin/src/main/java/io/cucumber/core/gherkin/DataTableArgument.java index 3c239fdbe1..ac39909374 100644 --- a/gherkin/src/main/java/io/cucumber/core/gherkin/DataTableArgument.java +++ b/gherkin/src/main/java/io/cucumber/core/gherkin/DataTableArgument.java @@ -3,7 +3,9 @@ import java.util.List; public interface DataTableArgument extends Argument, io.cucumber.plugin.event.DataTableArgument { + @Override List> cells(); + @Override int getLine(); } diff --git a/gherkin/src/main/java/io/cucumber/core/gherkin/DocStringArgument.java b/gherkin/src/main/java/io/cucumber/core/gherkin/DocStringArgument.java index 6a4ecfaa98..cf8b30eb08 100644 --- a/gherkin/src/main/java/io/cucumber/core/gherkin/DocStringArgument.java +++ b/gherkin/src/main/java/io/cucumber/core/gherkin/DocStringArgument.java @@ -1,9 +1,15 @@ package io.cucumber.core.gherkin; public interface DocStringArgument extends Argument, io.cucumber.plugin.event.DocStringArgument { + @Override String getContent(); + @Override String getContentType(); + @Override + String getMediaType(); + + @Override int getLine(); } diff --git a/gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java b/gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java index 4aee75b7e2..51055b3da2 100644 --- a/gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java +++ b/gherkin/src/main/java/io/cucumber/core/gherkin/Feature.java @@ -16,4 +16,5 @@ public interface Feature extends Node, Container { String getSource(); + Iterable getParseEvents(); } diff --git a/gherkin/src/main/java/io/cucumber/core/gherkin/Step.java b/gherkin/src/main/java/io/cucumber/core/gherkin/Step.java index 665b0972f3..bb71514888 100644 --- a/gherkin/src/main/java/io/cucumber/core/gherkin/Step.java +++ b/gherkin/src/main/java/io/cucumber/core/gherkin/Step.java @@ -1,17 +1,20 @@ package io.cucumber.core.gherkin; public interface Step extends io.cucumber.plugin.event.Step { - + @Override int getLine(); + @Override Argument getArgument(); + @Override String getKeyWord(); StepType getType(); String getPreviousGivenWhenThenKeyWord(); + @Override String getText(); String getId(); diff --git a/java/src/main/java/io/cucumber/java/ParameterType.java b/java/src/main/java/io/cucumber/java/ParameterType.java index 53732303d2..6ea8dfc77a 100644 --- a/java/src/main/java/io/cucumber/java/ParameterType.java +++ b/java/src/main/java/io/cucumber/java/ParameterType.java @@ -1,7 +1,6 @@ package io.cucumber.java; import io.cucumber.cucumberexpressions.GeneratedExpression; -import io.cucumber.cucumberexpressions.RegularExpression; import org.apiguardian.api.API; import java.lang.annotation.ElementType; @@ -58,7 +57,7 @@ /** * Indicates whether or not this is a preferential parameter type when matching text - * against a {@link RegularExpression}. In case there are multiple parameter types + * against a RegularExpression. In case there are multiple parameter types * with a regexp identical to the capture group's regexp, a preferential parameter type will * win. If there are more than 1 preferential ones, an error will be thrown. * diff --git a/junit-platform-engine/pom.xml b/junit-platform-engine/pom.xml index c624c37006..6dd3ef4eed 100644 --- a/junit-platform-engine/pom.xml +++ b/junit-platform-engine/pom.xml @@ -11,45 +11,23 @@ jar Cucumber-JVM: JUnit Platform Engine - - - - idea-exclude-gherkin-vintage - - - io.cucumber - cucumber-core - - - io.cucumber - gherkin - - - io.cucumber - cucumber-gherkin-vintage - - - - - - - io.cucumber cucumber-core + org.junit.platform junit-platform-engine + io.cucumber cucumber-gherkin-messages test + org.hamcrest hamcrest-core diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java index 9569b60e61..01a6e5f126 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/CucumberEngineExecutionContext.java @@ -18,6 +18,7 @@ import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; +import io.cucumber.messages.Messages; import io.cucumber.plugin.event.TestRunFinished; import io.cucumber.plugin.event.TestRunStarted; import io.cucumber.plugin.event.TestSourceRead; @@ -28,8 +29,10 @@ import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; import java.time.Clock; +import java.time.Instant; import java.util.UUID; import java.util.function.Supplier; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; @API(status = API.Status.STABLE) public final class CucumberEngineExecutionContext implements EngineExecutionContext { @@ -68,13 +71,21 @@ CucumberEngineOptions getOptions() { void startTestRun() { logger.debug(() -> "Sending run test started event"); - bus.send(new TestRunStarted(bus.getInstant())); + Instant instant = bus.getInstant(); + bus.send(new TestRunStarted(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build() + ); } void beforeFeature(Feature feature) { logger.debug(() -> "Sending test source read event for " + feature.getUri()); // Invoked concurrently. - getRunner().getBus().send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + EventBus bus = getRunner().getBus(); + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + bus.sendAll(feature.getParseEvents()); } void runTestCase(Pickle pickle) { @@ -89,7 +100,12 @@ void runTestCase(Pickle pickle) { void finishTestRun() { logger.debug(() -> "Sending test run finished event"); - bus.send(new TestRunFinished(bus.getInstant())); + Instant instant = bus.getInstant(); + bus.send(new TestRunFinished(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); } private Runner getRunner() { diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java index 288d850f60..1640ba28d3 100644 --- a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/CucumberEngineOptionsTest.java @@ -30,10 +30,10 @@ void getPluginNames() { ); CucumberEngineOptions htmlAndJson = new CucumberEngineOptions( - new MapConfigurationParameters(Constants.PLUGIN_PROPERTY_NAME, "html:path/with spaces/to/report.html, json:path/with spaces/to/report.json") + new MapConfigurationParameters(Constants.PLUGIN_PROPERTY_NAME, "html:path/with spaces/to/report.html, message:path/with spaces/to/report.ndjson") ); assertEquals( - asList("html:path/with spaces/to/report.html", "json:path/with spaces/to/report.json"), + asList("html:path/with spaces/to/report.html", "message:path/with spaces/to/report.ndjson"), htmlAndJson.plugins().stream() .map(Options.Plugin::pluginString) .collect(toList()) diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java index 2191bb4fdd..926df09fb0 100644 --- a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java @@ -141,6 +141,12 @@ public String getStepText() { public String getCodeLocation() { return null; } + + @Override + public UUID getId() { + return UUID.randomUUID(); + } + }; @Test diff --git a/junit/README.md b/junit/README.md index 74093b6568..1929d5d3b1 100644 --- a/junit/README.md +++ b/junit/README.md @@ -27,7 +27,7 @@ import io.cucumber.junit.Cucumber; import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = "json:target/cucumber-report.json") +@CucumberOptions(plugin = "message:target/cucumber-report.ndjson") public class RunCucumberTest { } ``` diff --git a/junit/src/main/java/io/cucumber/junit/Cucumber.java b/junit/src/main/java/io/cucumber/junit/Cucumber.java index 0606ac87ee..31df5479ec 100644 --- a/junit/src/main/java/io/cucumber/junit/Cucumber.java +++ b/junit/src/main/java/io/cucumber/junit/Cucumber.java @@ -25,6 +25,7 @@ import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; +import io.cucumber.messages.Messages; import io.cucumber.plugin.event.TestRunFinished; import io.cucumber.plugin.event.TestRunStarted; import io.cucumber.plugin.event.TestSourceRead; @@ -40,11 +41,13 @@ import org.junit.runners.model.Statement; import java.time.Clock; +import java.time.Instant; import java.util.List; import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; import static java.util.stream.Collectors.toList; /** @@ -212,13 +215,36 @@ public void evaluate() throws Throwable { plugins.setEventBusOnEventListenerPlugins(bus); } - bus.send(new TestRunStarted(bus.getInstant())); + emitTestRunStarted(); for (Feature feature : features) { - bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + emitTestSource(feature); } runFeatures.evaluate(); - bus.send(new TestRunFinished(bus.getInstant())); + emitTestRunFinished(); } + private void emitTestRunStarted() { + Instant instant = bus.getInstant(); + bus.send(new TestRunStarted(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); + } + + private void emitTestSource(Feature feature){ + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + bus.sendAll(feature.getParseEvents()); + } + + private void emitTestRunFinished() { + Instant instant = bus.getInstant(); + bus.send(new TestRunFinished(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); + } } + } diff --git a/junit/src/test/java/io/cucumber/junit/CucumberTest.java b/junit/src/test/java/io/cucumber/junit/CucumberTest.java index ea76ad844f..741ac4b8ff 100644 --- a/junit/src/test/java/io/cucumber/junit/CucumberTest.java +++ b/junit/src/test/java/io/cucumber/junit/CucumberTest.java @@ -28,7 +28,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.argThat; @@ -69,10 +68,15 @@ void finds_features_based_on_explicit_root_package() throws InitializationError void testThatParsingErrorsIsNicelyReported() { Executable testMethod = () -> new Cucumber(LexerErrorFeature.class); FeatureParserException actualThrown = assertThrows(FeatureParserException.class, testMethod); - assertAll("Checking Exception including cause", - () -> assertThat( - actualThrown.getMessage(), - is(equalTo("Failed to parse resource at: classpath:io/cucumber/error/lexer_error.feature")) + assertThat( + actualThrown.getMessage(), + equalTo("" + + "Failed to parse resource at: classpath:io/cucumber/error/lexer_error.feature\n" + + "(1:1): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Feature FA'\n" + + "(3:3): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Scenario SA'\n" + + "(4:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Given GA'\n" + + "(5:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'When GA'\n" + + "(6:5): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Then TA'" ) ); } @@ -83,7 +87,7 @@ void testThatFileIsNotCreatedOnParsingError() { () -> new Cucumber(FormatterWithLexerErrorFeature.class) ); assertFalse( - new File("target/lexor_error_feature.json").exists(), + new File("target/lexor_error_feature.ndjson").exists(), "File is created despite Lexor Error" ); } @@ -189,7 +193,7 @@ public static class LexerErrorFeature { } @SuppressWarnings("WeakerAccess") - @CucumberOptions(features = {"classpath:io/cucumber/error/lexer_error.feature"}, plugin = {"json:target/lexor_error_feature.json"}) + @CucumberOptions(features = {"classpath:io/cucumber/error/lexer_error.feature"}, plugin = {"message:target/lexor_error_feature.ndjson"}) public static class FormatterWithLexerErrorFeature { } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/Argument.java b/plugin/src/main/java/io/cucumber/plugin/event/Argument.java index ab0eb0a282..09ee54ebf8 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/Argument.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/Argument.java @@ -12,9 +12,14 @@ */ @API(status = API.Status.STABLE) public interface Argument { + + String getParameterTypeName(); + String getValue(); int getStart(); int getEnd(); + + Group getGroup(); } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/DocStringArgument.java b/plugin/src/main/java/io/cucumber/plugin/event/DocStringArgument.java index 29b732d7c4..9b908349e6 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/DocStringArgument.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/DocStringArgument.java @@ -9,7 +9,14 @@ public interface DocStringArgument extends StepArgument { String getContent(); + /** + * + * @deprecated use {@link #getMediaType()} instead. + */ + @Deprecated String getContentType(); + String getMediaType(); + int getLine(); } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/EmbedEvent.java b/plugin/src/main/java/io/cucumber/plugin/event/EmbedEvent.java index fded44de23..3592f9f5f5 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/EmbedEvent.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/EmbedEvent.java @@ -3,7 +3,8 @@ import org.apiguardian.api.API; import java.time.Instant; -import java.util.Objects; + +import static java.util.Objects.requireNonNull; @API(status = API.Status.STABLE) public final class EmbedEvent extends TestCaseEvent { @@ -12,16 +13,13 @@ public final class EmbedEvent extends TestCaseEvent { public final String name; public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mediaType) { - super(timeInstant, testCase); - this.data = Objects.requireNonNull(data); - this.mediaType = Objects.requireNonNull(mediaType); - this.name = null; + this(timeInstant, testCase, data, mediaType, null); } public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mediaType, String name) { super(timeInstant, testCase); - this.data = data; - this.mediaType = mediaType; + this.data = requireNonNull(data); + this.mediaType = requireNonNull(mediaType); this.name = name; } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/EventHandler.java b/plugin/src/main/java/io/cucumber/plugin/event/EventHandler.java index d114642b0c..2a6ba8ff4a 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/EventHandler.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/EventHandler.java @@ -3,7 +3,7 @@ import org.apiguardian.api.API; @API(status = API.Status.STABLE) -public interface EventHandler { +public interface EventHandler { void receive(T event); diff --git a/plugin/src/main/java/io/cucumber/plugin/event/EventPublisher.java b/plugin/src/main/java/io/cucumber/plugin/event/EventPublisher.java index 76495210ad..87a2e59e32 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/EventPublisher.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/EventPublisher.java @@ -29,7 +29,7 @@ public interface EventPublisher { * @param the event type * @see Event */ - void registerHandlerFor(Class eventType, EventHandler handler); + void registerHandlerFor(Class eventType, EventHandler handler); /** * Unregister an event handler for a specific event @@ -38,6 +38,6 @@ public interface EventPublisher { * @param handler the event handler * @param the event type */ - void removeHandlerFor(Class eventType, EventHandler handler); + void removeHandlerFor(Class eventType, EventHandler handler); } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/Group.java b/plugin/src/main/java/io/cucumber/plugin/event/Group.java new file mode 100644 index 0000000000..0df2dd5665 --- /dev/null +++ b/plugin/src/main/java/io/cucumber/plugin/event/Group.java @@ -0,0 +1,17 @@ +package io.cucumber.plugin.event; + +import java.util.Collection; + +/** + * A capture group in a Regular or Cucumber Expression. + */ +public interface Group { + + Collection getChildren(); + + String getValue(); + + int getStart(); + + int getEnd(); +} diff --git a/plugin/src/main/java/io/cucumber/plugin/event/HookTestStep.java b/plugin/src/main/java/io/cucumber/plugin/event/HookTestStep.java index 7211a04c06..27e74ed94d 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/HookTestStep.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/HookTestStep.java @@ -18,5 +18,4 @@ public interface HookTestStep extends TestStep { * @return the hook type. */ HookType getHookType(); - } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/TestCaseStarted.java b/plugin/src/main/java/io/cucumber/plugin/event/TestCaseStarted.java index 607b498b0a..d6832371f0 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/TestCaseStarted.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/TestCaseStarted.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.util.Objects; +import java.util.UUID; @API(status = API.Status.STABLE) public final class TestCaseStarted extends TestCaseEvent { @@ -12,7 +13,7 @@ public final class TestCaseStarted extends TestCaseEvent { public TestCaseStarted(Instant timeInstant, TestCase testCase) { super(timeInstant, testCase); this.testCase = Objects.requireNonNull(testCase); - } + } @Override public TestCase getTestCase() { diff --git a/plugin/src/main/java/io/cucumber/plugin/event/TestStep.java b/plugin/src/main/java/io/cucumber/plugin/event/TestStep.java index 156635a679..20e2a4cbec 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/TestStep.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/TestStep.java @@ -2,6 +2,8 @@ import org.apiguardian.api.API; +import java.util.UUID; + /** * A test step can either represent the execution of a hook * or a pickle step. Each step is tied to some glue code. @@ -20,5 +22,6 @@ public interface TestStep { */ String getCodeLocation(); + UUID getId(); } diff --git a/pom.xml b/pom.xml index dca8131302..d42997472e 100644 --- a/pom.xml +++ b/pom.xml @@ -48,13 +48,14 @@ 1.1.0 - 8.3.1 + 9.0.0 3.3.0 2.0.4 - 9.0.3 + 10.0.3 5.2.0 1.0.6 - 9.1.0 + 11.0.0 + 4.3.0 4.13 @@ -89,6 +90,11 @@ cucumber-expressions ${cucumber-expressions.version} + + io.cucumber + html-formatter + ${html-formatter.version} + io.cucumber datatable @@ -368,6 +374,12 @@ gherkin ${gherkin-messages.version} + + + io.cucumber + messages + ${messages.version} + diff --git a/testng/README.md b/testng/README.md index 00c5005af6..3399445e30 100644 --- a/testng/README.md +++ b/testng/README.md @@ -24,7 +24,7 @@ package io.cucumber.runtime.testng; import io.cucumber.testng.AbstractTestNGCucumberTests; import io.cucumber.testng.CucumberOptions; -@CucumberOptions(plugin = "json:target/cucumber-report.json") +@CucumberOptions(plugin = "message:target/cucumber-report.ndjson") public class RunCucumberTest extends AbstractTestNGCucumberTests { } ``` diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java index 0eedcc2f49..c210c56e35 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberRunner.java @@ -26,17 +26,21 @@ import io.cucumber.core.runtime.ThreadLocalRunnerSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.runtime.TypeRegistryConfigurerSupplier; +import io.cucumber.messages.Messages; import io.cucumber.plugin.event.TestRunFinished; import io.cucumber.plugin.event.TestRunStarted; import io.cucumber.plugin.event.TestSourceRead; import org.apiguardian.api.API; import java.time.Clock; +import java.time.Instant; +import java.time.Instant; import java.util.List; import java.util.UUID; import java.util.function.Predicate; import java.util.function.Supplier; +import static io.cucumber.messages.TimeConversion.javaInstantToTimestamp; import static java.util.stream.Collectors.toList; /** @@ -112,8 +116,8 @@ public TestNGCucumberRunner(Class clazz) { // Start test execution now. plugins.setSerialEventBusOnEventListenerPlugins(bus); features = featureSupplier.get(); - bus.send(new TestRunStarted(bus.getInstant())); - features.forEach(feature -> bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource()))); + emitTestRunStarted(); + features.forEach(this::emitTestSource); } public void runScenario(io.cucumber.testng.Pickle pickle) throws Throwable { @@ -130,7 +134,7 @@ public void runScenario(io.cucumber.testng.Pickle pickle) throws Throwable { * Finishes test execution by Cucumber. */ public void finish() { - bus.send(new TestRunFinished(bus.getInstant())); + emitTestRunFinished(); } /** @@ -153,4 +157,27 @@ public Object[][] provideScenarios() { return new Object[][]{new Object[]{new CucumberExceptionWrapper(e), null}}; } } + + private void emitTestRunStarted() { + Instant instant = bus.getInstant(); + bus.send(new TestRunStarted(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunStarted(Messages.TestRunStarted.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); + } + + private void emitTestSource(Feature feature) { + bus.send(new TestSourceRead(bus.getInstant(), feature.getUri(), feature.getSource())); + bus.sendAll(feature.getParseEvents()); + } + + private void emitTestRunFinished() { + Instant instant = bus.getInstant(); + bus.send(new TestRunFinished(instant)); + bus.send(Messages.Envelope.newBuilder() + .setTestRunFinished(Messages.TestRunFinished.newBuilder() + .setTimestamp(javaInstantToTimestamp(instant))) + .build()); + } } diff --git a/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java b/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java index 03dccc9e33..6660166ffd 100644 --- a/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java +++ b/testng/src/test/java/io/cucumber/testng/TestNGCucumberRunnerTest.java @@ -52,7 +52,10 @@ public void parse_error_propagated_to_testng_test_execution() { testNGCucumberRunner = new TestNGCucumberRunner(ParseError.class); Assert.fail("CucumberException not thrown"); } catch (FeatureParserException e) { - assertEquals(e.getMessage(), "Failed to parse resource at: classpath:io/cucumber/error/parse-error.feature"); + assertEquals(e.getMessage(), + "Failed to parse resource at: classpath:io/cucumber/error/parse-error.feature\n" + + "(1:1): expected: #EOF, #Language, #TagLine, #FeatureLine, #Comment, #Empty, got 'Invalid syntax'" + ); } }