From 8cb50c2875a0c6532773768334990ada136c795d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 18 Apr 2024 16:08:46 +0200 Subject: [PATCH 01/41] WIP --- cucumber-bom/pom.xml | 2 +- .../cucumber/core/plugin/JsonFormatter.java | 430 +---------- .../core/plugin/JsonFormatterOld.java | 442 +++++++++++ .../cucumber/core/plugin/JsonReportData.java | 13 + .../core/plugin/JsonReportWriter.java | 725 ++++++++++++++++++ .../core/plugin/MessagesToJsonWriter.java | 81 ++ .../cucumber/core/backend/StubLocation.java | 14 + .../core/plugin/JsonFormatterTest.java | 4 +- 8 files changed, 1293 insertions(+), 418 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index 9ffe3cd36a..28b3f65f63 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -19,7 +19,7 @@ 21.3.1 0.4.0 24.1.0 - 12.1.2 + 12.1.3-SNAPSHOT 6.1.0 0.1.0 diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java index f36526e4ea..264c6e2f04 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java @@ -1,442 +1,40 @@ package io.cucumber.core.plugin; -import io.cucumber.messages.types.Background; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.Scenario; -import io.cucumber.messages.types.Step; +import io.cucumber.messages.types.Envelope; import io.cucumber.plugin.EventListener; -import io.cucumber.plugin.event.Argument; -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.Status; -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.TestStep; -import io.cucumber.plugin.event.TestStepFinished; -import io.cucumber.plugin.event.TestStepStarted; -import io.cucumber.plugin.event.WriteEvent; import java.io.IOException; import java.io.OutputStream; -import java.io.Writer; -import java.net.URI; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static io.cucumber.core.exception.ExceptionUtils.printStackTrace; -import static io.cucumber.core.plugin.TestSourcesModel.getBackgroundForTestCase; -import static java.util.Collections.singletonList; -import static java.util.Locale.ROOT; -import static java.util.stream.Collectors.toList; public final class JsonFormatter implements EventListener { - private static final String before = "before"; - private static final String after = "after"; - private final List> featureMaps = new ArrayList<>(); - private final Map currentBeforeStepHookList = new HashMap<>(); - private final Writer writer; - private final TestSourcesModel testSources = new TestSourcesModel(); - private URI currentFeatureFile; - private List> currentElementsList; - private Map currentElementMap; - private Map currentTestCaseMap; - private List> currentStepsList; - private Map currentStepOrHookMap; + private final MessagesToJsonWriter writer; - @SuppressWarnings("WeakerAccess") // Used by PluginFactory public JsonFormatter(OutputStream out) { - this.writer = new UTF8OutputStreamWriter(out); + this.writer = new MessagesToJsonWriter(out, Jackson.OBJECT_MAPPER::writeValue); } @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(WriteEvent.class, this::handleWrite); - publisher.registerHandlerFor(EmbedEvent.class, this::handleEmbed); - publisher.registerHandlerFor(TestRunFinished.class, this::finishReport); - } - - private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.getUri(), event); - } - - @SuppressWarnings("unchecked") - private void handleTestCaseStarted(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { - currentFeatureFile = event.getTestCase().getUri(); - Map currentFeatureMap = createFeatureMap(event.getTestCase()); - featureMaps.add(currentFeatureMap); - currentElementsList = (List>) currentFeatureMap.get("elements"); - } - currentTestCaseMap = createTestCase(event); - if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLocation().getLine())) { - currentElementMap = createBackground(event.getTestCase()); - currentElementsList.add(currentElementMap); - } else { - currentElementMap = currentTestCaseMap; - } - currentElementsList.add(currentTestCaseMap); - currentStepsList = (List>) currentElementMap.get("steps"); - } - - @SuppressWarnings("unchecked") - private void handleTestStepStarted(TestStepStarted event) { - if (event.getTestStep() instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); - if (isFirstStepAfterBackground(testStep)) { - currentElementMap = currentTestCaseMap; - currentStepsList = (List>) currentElementMap.get("steps"); - } - currentStepOrHookMap = createTestStep(testStep); - // add beforeSteps list to current step - if (currentBeforeStepHookList.containsKey(before)) { - currentStepOrHookMap.put(before, currentBeforeStepHookList.get(before)); - currentBeforeStepHookList.clear(); - } - currentStepsList.add(currentStepOrHookMap); - } else if (event.getTestStep() instanceof HookTestStep) { - HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); - currentStepOrHookMap = createHookStep(hookTestStep); - addHookStepToTestCaseMap(currentStepOrHookMap, hookTestStep.getHookType()); - } else { - throw new IllegalStateException(); - } - } - - private void handleTestStepFinished(TestStepFinished event) { - currentStepOrHookMap.put("match", createMatchMap(event.getTestStep(), event.getResult())); - currentStepOrHookMap.put("result", createResultMap(event.getResult())); + publisher.registerHandlerFor(Envelope.class, this::write); } - private void handleWrite(WriteEvent event) { - addOutputToHookMap(event.getText()); - } - - private void handleEmbed(EmbedEvent event) { - addEmbeddingToHookMap(event.getData(), event.getMediaType(), event.getName()); - } - - private void finishReport(TestRunFinished event) { - Throwable exception = event.getResult().getError(); - if (exception != null) { - featureMaps.add(createDummyFeatureForFailure(event)); - } - + private void write(Envelope event) { try { - Jackson.OBJECT_MAPPER.writeValue(writer, featureMaps); - writer.close(); + writer.write(event); } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private Map createFeatureMap(TestCase testCase) { - Map featureMap = new HashMap<>(); - featureMap.put("uri", TestSourcesModel.relativize(testCase.getUri())); - featureMap.put("elements", new ArrayList>()); - 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() : ""); - featureMap.put("line", feature.getLocation().getLine()); - featureMap.put("id", TestSourcesModel.convertToId(feature.getName())); - featureMap.put("tags", feature.getTags().stream().map( - tag -> { - Map json = new LinkedHashMap<>(); - json.put("name", tag.getName()); - json.put("type", "Tag"); - Map location = new LinkedHashMap<>(); - location.put("line", tag.getLocation().getLine()); - location.put("column", tag.getLocation().getColumn()); - json.put("location", location); - return json; - }).collect(toList())); - - } - return featureMap; - } - - private Map createTestCase(TestCaseStarted event) { - Map testCaseMap = new HashMap<>(); - - testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); - - TestCase testCase = event.getTestCase(); - - testCaseMap.put("name", testCase.getName()); - testCaseMap.put("line", testCase.getLine()); - testCaseMap.put("type", "scenario"); - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); - if (astNode != null) { - testCaseMap.put("id", TestSourcesModel.calculateId(astNode)); - Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode); - testCaseMap.put("keyword", scenarioDefinition.getKeyword()); - testCaseMap.put("description", - scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : ""); - } - testCaseMap.put("steps", new ArrayList>()); - 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.getLocation().getLine()); - if (astNode != null) { - Background background = getBackgroundForTestCase(astNode).get(); - Map testCaseMap = new HashMap<>(); - testCaseMap.put("name", background.getName()); - testCaseMap.put("line", background.getLocation().getLine()); - testCaseMap.put("type", "background"); - testCaseMap.put("keyword", background.getKeyword()); - testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); - testCaseMap.put("steps", new ArrayList>()); - return testCaseMap; - } - return null; - } - - private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - if (astNode == null) { - return false; - } - return currentElementMap != currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode); - } - - private Map createTestStep(PickleStepTestStep testStep) { - Map stepMap = new HashMap<>(); - stepMap.put("name", testStep.getStepText()); - stepMap.put("line", testStep.getStepLine()); - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - 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)); - } - } - if (astNode != null) { - Step step = (Step) astNode.node; - stepMap.put("keyword", step.getKeyword()); + throw new IllegalStateException(e); } - return stepMap; - } - - private Map createHookStep(HookTestStep hookTestStep) { - return new HashMap<>(); - } - - private void addHookStepToTestCaseMap(Map currentStepOrHookMap, HookType hookType) { - String hookName; - if (hookType == HookType.AFTER || hookType == HookType.AFTER_STEP) - hookName = after; - else - hookName = before; - - Map mapToAddTo; - switch (hookType) { - case BEFORE: - mapToAddTo = currentTestCaseMap; - break; - case AFTER: - mapToAddTo = currentTestCaseMap; - break; - case BEFORE_STEP: - mapToAddTo = currentBeforeStepHookList; - break; - case AFTER_STEP: - mapToAddTo = currentStepsList.get(currentStepsList.size() - 1); - break; - default: - mapToAddTo = currentTestCaseMap; - } - - if (!mapToAddTo.containsKey(hookName)) { - mapToAddTo.put(hookName, new ArrayList>()); - } - ((List>) mapToAddTo.get(hookName)).add(currentStepOrHookMap); - } - - private Map createMatchMap(TestStep step, Result result) { - Map matchMap = new HashMap<>(); - if (step instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) step; - if (!testStep.getDefinitionArgument().isEmpty()) { - List> argumentList = new ArrayList<>(); - for (Argument argument : testStep.getDefinitionArgument()) { - Map argumentMap = new HashMap<>(); - if (argument.getValue() != null) { - argumentMap.put("val", argument.getValue()); - argumentMap.put("offset", argument.getStart()); - } - argumentList.add(argumentMap); - } - matchMap.put("arguments", argumentList); + // TODO: Plugins should implement the closable interface + // and be closed by Cucumber + if (event.getTestRunFinished().isPresent()) { + try { + writer.close(); + } catch (IOException e) { + throw new IllegalStateException(e); } } - if (!result.getStatus().is(Status.UNDEFINED)) { - matchMap.put("location", step.getCodeLocation()); - } - 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())); - } - if (!result.getDuration().isZero()) { - resultMap.put("duration", result.getDuration().toNanos()); - } - return resultMap; - } - - private void addOutputToHookMap(String text) { - if (!currentStepOrHookMap.containsKey("output")) { - currentStepOrHookMap.put("output", new ArrayList()); - } - ((List) currentStepOrHookMap.get("output")).add(text); - } - - private void addEmbeddingToHookMap(byte[] data, String mediaType, String name) { - if (!currentStepOrHookMap.containsKey("embeddings")) { - currentStepOrHookMap.put("embeddings", new ArrayList>()); - } - Map embedMap = createEmbeddingMap(data, mediaType, name); - ((List>) currentStepOrHookMap.get("embeddings")).add(embedMap); - } - - private Map createDummyFeatureForFailure(TestRunFinished event) { - Throwable exception = event.getResult().getError(); - - Map feature = new LinkedHashMap<>(); - feature.put("line", 1); - { - Map scenario = new LinkedHashMap<>(); - feature.put("elements", singletonList(scenario)); - - scenario.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); - scenario.put("line", 2); - scenario.put("name", "Failure while executing Cucumber"); - scenario.put("description", ""); - scenario.put("id", "failure;failure-while-executing-cucumber"); - scenario.put("type", "scenario"); - scenario.put("keyword", "Scenario"); - - Map when = new LinkedHashMap<>(); - Map then = new LinkedHashMap<>(); - scenario.put("steps", Arrays.asList(when, then)); - { - - { - Map whenResult = new LinkedHashMap<>(); - when.put("result", whenResult); - whenResult.put("duration", 0); - whenResult.put("status", "passed"); - } - when.put("line", 3); - when.put("name", "Cucumber failed while executing"); - Map whenMatch = new LinkedHashMap<>(); - when.put("match", whenMatch); - whenMatch.put("arguments", new ArrayList<>()); - whenMatch.put("location", "io.cucumber.core.Failure.failure_while_executing_cucumber()"); - when.put("keyword", "When "); - - { - Map thenResult = new LinkedHashMap<>(); - then.put("result", thenResult); - thenResult.put("duration", 0); - thenResult.put("error_message", printStackTrace(exception)); - thenResult.put("status", "failed"); - } - then.put("line", 4); - then.put("name", "Cucumber will report this error:"); - Map thenMatch = new LinkedHashMap<>(); - then.put("match", thenMatch); - thenMatch.put("arguments", new ArrayList<>()); - thenMatch.put("location", "io.cucumber.core.Failure.cucumber_reports_this_error()"); - then.put("keyword", "Then "); - } - - feature.put("name", "Test run failed"); - feature.put("description", "There were errors during the execution"); - feature.put("id", "failure"); - feature.put("keyword", "Feature"); - feature.put("uri", "classpath:io/cucumber/core/failure.feature"); - feature.put("tags", new ArrayList<>()); - } - - return feature; - } - - private String getDateTimeFromTimeStamp(Instant instant) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - .withZone(ZoneOffset.UTC); - return formatter.format(instant); - } - - private Map createDocStringMap(DocStringArgument docString) { - Map docStringMap = new HashMap<>(); - docStringMap.put("value", docString.getContent()); - docStringMap.put("line", docString.getLine()); - docStringMap.put("content_type", docString.getMediaType()); - return docStringMap; - } - - private List>> createDataTableList(DataTableArgument argument) { - List>> rowList = new ArrayList<>(); - for (List row : argument.cells()) { - Map> rowMap = new HashMap<>(); - rowMap.put("cells", new ArrayList<>(row)); - rowList.add(rowMap); - } - return rowList; - } - - private Map createEmbeddingMap(byte[] data, String mediaType, String name) { - Map embedMap = new HashMap<>(); - embedMap.put("mime_type", mediaType); // Should be media-type but not - // worth migrating for - embedMap.put("data", Base64.getEncoder().encodeToString(data)); - if (name != null) { - embedMap.put("name", name); - } - return embedMap; - } - } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java new file mode 100644 index 0000000000..8b6d943480 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java @@ -0,0 +1,442 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.Background; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.Step; +import io.cucumber.plugin.EventListener; +import io.cucumber.plugin.event.Argument; +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.Status; +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.TestStep; +import io.cucumber.plugin.event.TestStepFinished; +import io.cucumber.plugin.event.TestStepStarted; +import io.cucumber.plugin.event.WriteEvent; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.net.URI; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static io.cucumber.core.exception.ExceptionUtils.printStackTrace; +import static io.cucumber.core.plugin.TestSourcesModel.getBackgroundForTestCase; +import static java.util.Collections.singletonList; +import static java.util.Locale.ROOT; +import static java.util.stream.Collectors.toList; + +public final class JsonFormatterOld implements EventListener { + + private static final String before = "before"; + private static final String after = "after"; + private final List> featureMaps = new ArrayList<>(); + private final Map currentBeforeStepHookList = new HashMap<>(); + private final Writer writer; + private final TestSourcesModel testSources = new TestSourcesModel(); + private URI currentFeatureFile; + private List> currentElementsList; + private Map currentElementMap; + private Map currentTestCaseMap; + private List> currentStepsList; + private Map currentStepOrHookMap; + + @SuppressWarnings("WeakerAccess") // Used by PluginFactory + public JsonFormatterOld(OutputStream out) { + this.writer = new UTF8OutputStreamWriter(out); + } + + @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(WriteEvent.class, this::handleWrite); + publisher.registerHandlerFor(EmbedEvent.class, this::handleEmbed); + publisher.registerHandlerFor(TestRunFinished.class, this::finishReport); + } + + private void handleTestSourceRead(TestSourceRead event) { + testSources.addTestSourceReadEvent(event.getUri(), event); + } + + @SuppressWarnings("unchecked") + private void handleTestCaseStarted(TestCaseStarted event) { + if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { + currentFeatureFile = event.getTestCase().getUri(); + Map currentFeatureMap = createFeatureMap(event.getTestCase()); + featureMaps.add(currentFeatureMap); + currentElementsList = (List>) currentFeatureMap.get("elements"); + } + currentTestCaseMap = createTestCase(event); + if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLocation().getLine())) { + currentElementMap = createBackground(event.getTestCase()); + currentElementsList.add(currentElementMap); + } else { + currentElementMap = currentTestCaseMap; + } + currentElementsList.add(currentTestCaseMap); + currentStepsList = (List>) currentElementMap.get("steps"); + } + + @SuppressWarnings("unchecked") + private void handleTestStepStarted(TestStepStarted event) { + if (event.getTestStep() instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); + if (isFirstStepAfterBackground(testStep)) { + currentElementMap = currentTestCaseMap; + currentStepsList = (List>) currentElementMap.get("steps"); + } + currentStepOrHookMap = createTestStep(testStep); + // add beforeSteps list to current step + if (currentBeforeStepHookList.containsKey(before)) { + currentStepOrHookMap.put(before, currentBeforeStepHookList.get(before)); + currentBeforeStepHookList.clear(); + } + currentStepsList.add(currentStepOrHookMap); + } else if (event.getTestStep() instanceof HookTestStep) { + HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); + currentStepOrHookMap = createHookStep(hookTestStep); + addHookStepToTestCaseMap(currentStepOrHookMap, hookTestStep.getHookType()); + } else { + throw new IllegalStateException(); + } + } + + private void handleTestStepFinished(TestStepFinished event) { + currentStepOrHookMap.put("match", createMatchMap(event.getTestStep(), event.getResult())); + currentStepOrHookMap.put("result", createResultMap(event.getResult())); + } + + private void handleWrite(WriteEvent event) { + addOutputToHookMap(event.getText()); + } + + private void handleEmbed(EmbedEvent event) { + addEmbeddingToHookMap(event.getData(), event.getMediaType(), event.getName()); + } + + private void finishReport(TestRunFinished event) { + Throwable exception = event.getResult().getError(); + if (exception != null) { + featureMaps.add(createDummyFeatureForFailure(event)); + } + + try { + Jackson.OBJECT_MAPPER.writeValue(writer, featureMaps); + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Map createFeatureMap(TestCase testCase) { + Map featureMap = new HashMap<>(); + featureMap.put("uri", TestSourcesModel.relativize(testCase.getUri())); + featureMap.put("elements", new ArrayList>()); + 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() : ""); + featureMap.put("line", feature.getLocation().getLine()); + featureMap.put("id", TestSourcesModel.convertToId(feature.getName())); + featureMap.put("tags", feature.getTags().stream().map( + tag -> { + Map json = new LinkedHashMap<>(); + json.put("name", tag.getName()); + json.put("type", "Tag"); + Map location = new LinkedHashMap<>(); + location.put("line", tag.getLocation().getLine()); + location.put("column", tag.getLocation().getColumn()); + json.put("location", location); + return json; + }).collect(toList())); + + } + return featureMap; + } + + private Map createTestCase(TestCaseStarted event) { + Map testCaseMap = new HashMap<>(); + + testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); + + TestCase testCase = event.getTestCase(); + + testCaseMap.put("name", testCase.getName()); + testCaseMap.put("line", testCase.getLine()); + testCaseMap.put("type", "scenario"); + TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); + if (astNode != null) { + testCaseMap.put("id", TestSourcesModel.calculateId(astNode)); + Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode); + testCaseMap.put("keyword", scenarioDefinition.getKeyword()); + testCaseMap.put("description", + scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : ""); + } + testCaseMap.put("steps", new ArrayList>()); + 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.getLocation().getLine()); + if (astNode != null) { + Background background = getBackgroundForTestCase(astNode).get(); + Map testCaseMap = new HashMap<>(); + testCaseMap.put("name", background.getName()); + testCaseMap.put("line", background.getLocation().getLine()); + testCaseMap.put("type", "background"); + testCaseMap.put("keyword", background.getKeyword()); + testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); + testCaseMap.put("steps", new ArrayList>()); + return testCaseMap; + } + return null; + } + + private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { + TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); + if (astNode == null) { + return false; + } + return currentElementMap != currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode); + } + + private Map createTestStep(PickleStepTestStep testStep) { + Map stepMap = new HashMap<>(); + stepMap.put("name", testStep.getStepText()); + stepMap.put("line", testStep.getStepLine()); + TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); + 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)); + } + } + if (astNode != null) { + Step step = (Step) astNode.node; + stepMap.put("keyword", step.getKeyword()); + } + + return stepMap; + } + + private Map createHookStep(HookTestStep hookTestStep) { + return new HashMap<>(); + } + + private void addHookStepToTestCaseMap(Map currentStepOrHookMap, HookType hookType) { + String hookName; + if (hookType == HookType.AFTER || hookType == HookType.AFTER_STEP) + hookName = after; + else + hookName = before; + + Map mapToAddTo; + switch (hookType) { + case BEFORE: + mapToAddTo = currentTestCaseMap; + break; + case AFTER: + mapToAddTo = currentTestCaseMap; + break; + case BEFORE_STEP: + mapToAddTo = currentBeforeStepHookList; + break; + case AFTER_STEP: + mapToAddTo = currentStepsList.get(currentStepsList.size() - 1); + break; + default: + mapToAddTo = currentTestCaseMap; + } + + if (!mapToAddTo.containsKey(hookName)) { + mapToAddTo.put(hookName, new ArrayList>()); + } + ((List>) mapToAddTo.get(hookName)).add(currentStepOrHookMap); + } + + private Map createMatchMap(TestStep step, Result result) { + Map matchMap = new HashMap<>(); + if (step instanceof PickleStepTestStep) { + PickleStepTestStep testStep = (PickleStepTestStep) step; + if (!testStep.getDefinitionArgument().isEmpty()) { + List> argumentList = new ArrayList<>(); + for (Argument argument : testStep.getDefinitionArgument()) { + Map argumentMap = new HashMap<>(); + if (argument.getValue() != null) { + argumentMap.put("val", argument.getValue()); + argumentMap.put("offset", argument.getStart()); + } + argumentList.add(argumentMap); + } + matchMap.put("arguments", argumentList); + } + } + if (!result.getStatus().is(Status.UNDEFINED)) { + matchMap.put("location", step.getCodeLocation()); + } + 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())); + } + if (!result.getDuration().isZero()) { + resultMap.put("duration", result.getDuration().toNanos()); + } + return resultMap; + } + + private void addOutputToHookMap(String text) { + if (!currentStepOrHookMap.containsKey("output")) { + currentStepOrHookMap.put("output", new ArrayList()); + } + ((List) currentStepOrHookMap.get("output")).add(text); + } + + private void addEmbeddingToHookMap(byte[] data, String mediaType, String name) { + if (!currentStepOrHookMap.containsKey("embeddings")) { + currentStepOrHookMap.put("embeddings", new ArrayList>()); + } + Map embedMap = createEmbeddingMap(data, mediaType, name); + ((List>) currentStepOrHookMap.get("embeddings")).add(embedMap); + } + + private Map createDummyFeatureForFailure(TestRunFinished event) { + Throwable exception = event.getResult().getError(); + + Map feature = new LinkedHashMap<>(); + feature.put("line", 1); + { + Map scenario = new LinkedHashMap<>(); + feature.put("elements", singletonList(scenario)); + + scenario.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); + scenario.put("line", 2); + scenario.put("name", "Failure while executing Cucumber"); + scenario.put("description", ""); + scenario.put("id", "failure;failure-while-executing-cucumber"); + scenario.put("type", "scenario"); + scenario.put("keyword", "Scenario"); + + Map when = new LinkedHashMap<>(); + Map then = new LinkedHashMap<>(); + scenario.put("steps", Arrays.asList(when, then)); + { + + { + Map whenResult = new LinkedHashMap<>(); + when.put("result", whenResult); + whenResult.put("duration", 0); + whenResult.put("status", "passed"); + } + when.put("line", 3); + when.put("name", "Cucumber failed while executing"); + Map whenMatch = new LinkedHashMap<>(); + when.put("match", whenMatch); + whenMatch.put("arguments", new ArrayList<>()); + whenMatch.put("location", "io.cucumber.core.Failure.failure_while_executing_cucumber()"); + when.put("keyword", "When "); + + { + Map thenResult = new LinkedHashMap<>(); + then.put("result", thenResult); + thenResult.put("duration", 0); + thenResult.put("error_message", printStackTrace(exception)); + thenResult.put("status", "failed"); + } + then.put("line", 4); + then.put("name", "Cucumber will report this error:"); + Map thenMatch = new LinkedHashMap<>(); + then.put("match", thenMatch); + thenMatch.put("arguments", new ArrayList<>()); + thenMatch.put("location", "io.cucumber.core.Failure.cucumber_reports_this_error()"); + then.put("keyword", "Then "); + } + + feature.put("name", "Test run failed"); + feature.put("description", "There were errors during the execution"); + feature.put("id", "failure"); + feature.put("keyword", "Feature"); + feature.put("uri", "classpath:io/cucumber/core/failure.feature"); + feature.put("tags", new ArrayList<>()); + } + + return feature; + } + + private String getDateTimeFromTimeStamp(Instant instant) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + .withZone(ZoneOffset.UTC); + return formatter.format(instant); + } + + private Map createDocStringMap(DocStringArgument docString) { + Map docStringMap = new HashMap<>(); + docStringMap.put("value", docString.getContent()); + docStringMap.put("line", docString.getLine()); + docStringMap.put("content_type", docString.getMediaType()); + return docStringMap; + } + + private List>> createDataTableList(DataTableArgument argument) { + List>> rowList = new ArrayList<>(); + for (List row : argument.cells()) { + Map> rowMap = new HashMap<>(); + rowMap.put("cells", new ArrayList<>(row)); + rowList.add(rowMap); + } + return rowList; + } + + private Map createEmbeddingMap(byte[] data, String mediaType, String name) { + Map embedMap = new HashMap<>(); + embedMap.put("mime_type", mediaType); // Should be media-type but not + // worth migrating for + embedMap.put("data", Base64.getEncoder().encodeToString(data)); + if (name != null) { + embedMap.put("name", name); + } + return embedMap; + } + +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java new file mode 100644 index 0000000000..de71b21cae --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java @@ -0,0 +1,13 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.Envelope; +import io.cucumber.query.Query; + +class JsonReportData { + + private final Query query = new Query(); + + void collect(Envelope envelope) { + query.update(envelope); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java new file mode 100644 index 0000000000..61ae71e519 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -0,0 +1,725 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.Convertor; +import io.cucumber.messages.types.Background; +import io.cucumber.messages.types.DataTable; +import io.cucumber.messages.types.DocString; +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Group; +import io.cucumber.messages.types.JavaMethod; +import io.cucumber.messages.types.JavaStackTraceElement; +import io.cucumber.messages.types.Location; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.PickleStep; +import io.cucumber.messages.types.PickleTag; +import io.cucumber.messages.types.Rule; +import io.cucumber.messages.types.RuleChild; +import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.SourceReference; +import io.cucumber.messages.types.Step; +import io.cucumber.messages.types.StepDefinition; +import io.cucumber.messages.types.StepMatchArgumentsList; +import io.cucumber.messages.types.TableCell; +import io.cucumber.messages.types.TableRow; +import io.cucumber.messages.types.Tag; +import io.cucumber.messages.types.TestCaseStarted; +import io.cucumber.messages.types.TestStep; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import io.cucumber.messages.types.TestStepResultStatus; +import io.cucumber.messages.types.Timestamp; +import io.cucumber.query.NamingStrategy; +import io.cucumber.query.Query; + +import java.net.URI; +import java.time.Duration; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; + +import static io.cucumber.core.plugin.TestSourcesModel.convertToId; +import static java.util.Locale.ROOT; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + +class JsonReportWriter { + private final Query query; + + JsonReportWriter(Query query) { + this.query = query; + } + + List writeJsonReport() { + return query.findAllTestCaseStartedGroupedByFeature() + .entrySet() + .stream() + .map(this::createFeatureMap) + .collect(toList()); + } + + private JvmFeature createFeatureMap(Entry, List> entry) { + LinkedHashMap featureMap = new LinkedHashMap<>(); + GherkinDocument document = getGherkinDocument(entry); + Feature feature = entry.getKey().get(); + + return new JvmFeature( + TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), // TODO: Relativize, optional?, null? + convertToId(feature.getName()), + feature.getLocation().getLine(), + feature.getKeyword(), + feature.getName(), + feature.getDescription() != null ? feature.getDescription() : "", // TODO: Can this be null? + writeElementsReport(entry), + feature.getTags().stream() + .map(JsonReportWriter::createLocationTag) + .collect(toList()) + ); + } + + private static JvmLocationTag createLocationTag(Tag tag) { + return new JvmLocationTag( + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L) + ) + ); + } + + private GherkinDocument getGherkinDocument(Entry, List> entry) { + return entry.getValue().stream().findAny().flatMap(query::findGherkinDocumentBy).orElseThrow(() -> new IllegalArgumentException("No Gherking document")); + } + + private List writeElementsReport(Entry, List> entry) { + return entry.getValue().stream() + .map(this::createTestCaseAndBackGround) + .flatMap(Collection::stream) + .collect(toList()); + } + + private List createTestCaseAndBackGround(TestCaseStarted testCaseStarted) { + return query.findTestStepsFinishedBy(testCaseStarted) + .stream() + .collect(groupByBackground(testCaseStarted)) + .entrySet().stream() + .map(entry -> entry.getKey().map(background -> createBackground(background, entry.getValue())) + .orElseGet(() -> createTestCase(testCaseStarted, entry.getValue()))).collect(toList()); + } + + private Collector, List>> groupByBackground(TestCaseStarted testCaseStarted) { + List backgrounds = query.findFeatureBy(testCaseStarted) + .map(JsonReportWriter::getBackgroundForTestCase) + .orElseGet(Collections::emptyList); + + Function> grouping = testStepFinished -> query.findTestStepBy(testStepFinished) + .flatMap(query::findPickleStepBy) + .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); + + return groupingBy(grouping, LinkedHashMap::new, toList()); + } + + private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { + return backgrounds.stream() + .filter(background -> background.getSteps().stream() + .map(Step::getId) + .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) + .findFirst(); + } + + static List getBackgroundForTestCase(Feature feature) { + return feature.getChildren() + .stream() + .map(featureChild -> { + List backgrounds = new ArrayList<>(); + featureChild.getBackground().ifPresent(backgrounds::add); + featureChild.getRule() + .map(Rule::getChildren) + .map(Collection::stream) + .orElseGet(Stream::empty) + .map(RuleChild::getBackground) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(backgrounds::add); + return backgrounds; + }) + .flatMap(Collection::stream) + .collect(toList()); + } + + private JvmElement createBackground(Background background, List testStepsFinished) { + Map testCaseMap = new HashMap<>(); + testCaseMap.put("name", background.getName()); + testCaseMap.put("line", background.getLocation().getLine()); + testCaseMap.put("type", "background"); + testCaseMap.put("keyword", background.getKeyword()); + testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); + testCaseMap.put("steps", testStepsFinished.stream().map(this::createTestStep).collect(toList())); + return testCaseMap; + } + + private class IdNamingVisitor implements NamingStrategy.NamingVisitor { + @Override + public String accept(Feature feature) { + return convertToId(feature.getName()); + } + + @Override + public String accept(Rule rule) { + return convertToId(rule.getName()); + } + + @Override + public String accept(Scenario scenario) { + return convertToId(scenario.getName()); + } + + @Override + public String accept(Examples examples) { + return convertToId(examples.getName()); + } + + @Override + public String accept(int examplesIndex, int exampleIndex) { + return (examplesIndex + 1) + ";" + (examplesIndex + 1); + } + + @Override + public String accept(Pickle pickle) { + return convertToId(pickle.getName()); + } + } + + + private JvmElement createTestCase(TestCaseStarted event, List testStepsFinished) { + Map testCaseMap = new HashMap<>(); + + testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getTimestamp())); + + Pickle pickle = query.findPickleBy(event).orElseThrow(); + Location location = query.findLocationOf(pickle).orElseThrow(); + + testCaseMap.put("name", pickle.getName()); + testCaseMap.put("line", location.getLine()); + testCaseMap.put("type", "scenario"); + + query.findScenarioBy(event).ifPresent(scenario -> { + testCaseMap.put("id", query.findNameOf(pickle, NamingStrategy.strategy(NamingStrategy.Strategy.LONG).delimiter(";").namingVisitor(new IdNamingVisitor()).build())); + testCaseMap.put("keyword", scenario.getKeyword()); + // TODO: Should not be nullable? + testCaseMap.put("description", scenario.getDescription() != null ? scenario.getDescription() : ""); + }); + + // TODO: Create steps + testCaseMap.put("steps", testStepsFinished.stream() + .map(this::createTestStep) + .collect(toList())); + + if (!pickle.getTags().isEmpty()) { + List> tagList = new ArrayList<>(); + for (PickleTag tag : pickle.getTags()) { + Map tagMap = new HashMap<>(); + tagMap.put("name", tag.getName()); + tagList.add(tagMap); + } + testCaseMap.put("tags", tagList); + } + return testCaseMap; + } + + private Map createTestStep(TestStepFinished testStepFinished) { + Map stepMap = new HashMap<>(); + TestStep testStep = query.findTestStepBy(testStepFinished).orElseThrow(); + query.findPickleStepBy(testStep).ifPresent(pickleStep -> { + Step step = query.findStepBy(pickleStep).orElseThrow(); + stepMap.put("name", step.getText()); + stepMap.put("line", step.getLocation().getLine()); + + step.getDocString().ifPresent(docString -> { + stepMap.put("doc_string", createDocStringMap(docString)); + }); + step.getDataTable().ifPresent(dataTable -> { + stepMap.put("rows", createDataTableList(dataTable)); + }); + stepMap.put("keyword", step.getKeyword()); + stepMap.put("match", createMatchMap(testStep, testStepFinished.getTestStepResult())); + stepMap.put("result", createResultMap(testStepFinished.getTestStepResult())); + + }); + return stepMap; + } + + private Map createMatchMap(TestStep step, TestStepResult result) { + Map matchMap = new HashMap<>(); + + step.getStepMatchArgumentsLists() + .map(argumentsLists -> argumentsLists.stream() + .map(StepMatchArgumentsList::getStepMatchArguments) + .flatMap(Collection::stream) + .map(argument -> { + Map argumentMap = new HashMap<>(); + Group group = argument.getGroup(); + group.getValue().ifPresent(value -> argumentMap.put("val", value)); + group.getStart().ifPresent(offset -> argumentMap.put("offset", offset)); + return argumentMap; + }).collect(toList())) + .filter(maps -> !maps.isEmpty()) + .ifPresent(argumentList -> matchMap.put("arguments", argumentList)); + + if (result.getStatus() != TestStepResultStatus.UNDEFINED) { + Optional source = query.findStepDefinitionBy(step) + .stream() + .findFirst() + .map(StepDefinition::getSourceReference); + source + .ifPresent(sourceReference -> { + sourceReference.getUri() + .map(uri -> renderLocationString(sourceReference, uri)) + .ifPresent(location -> matchMap.put("location", location)); + sourceReference.getJavaMethod() + .map(JsonReportWriter::renderLocationString) + .ifPresent(location -> matchMap.put("location", location)); + sourceReference.getJavaStackTraceElement() + .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)) + .ifPresent(location -> matchMap.put("location", location)); + }); + } + return matchMap; + } + + private static String renderLocationString(SourceReference sourceReference, String uri) { + String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); + return uri + locationLine; + } + + private static String renderLocationString(SourceReference sourceReference, JavaStackTraceElement javaStackTraceElement) { + String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); + String argumentList = String.join(",", javaStackTraceElement.getFileName()); + return String.format( + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine + ); + } + + private static String renderLocationString(JavaMethod javaMethod) { + return String.format( + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes()) + ); + } + + private Map createResultMap(TestStepResult result) { + Map resultMap = new HashMap<>(); + resultMap.put("status", result.getStatus().name().toLowerCase(ROOT)); + result.getException().ifPresent(exception -> { + resultMap.put("error_message", exception.getStackTrace()); + }); + + Duration duration = Convertor.toDuration(result.getDuration()); + if (!duration.isZero()) { + resultMap.put("duration", duration.toNanos()); + } + return resultMap; + } + + private Map createDocStringMap(DocString docString) { + Map docStringMap = new HashMap<>(); + docStringMap.put("value", docString.getContent()); + docStringMap.put("line", docString.getLocation().getLine()); + docStringMap.put("content_type", docString.getMediaType()); + return docStringMap; + } + + private List>> createDataTableList(DataTable argument) { + List>> rowList = new ArrayList<>(); + for (TableRow row : argument.getRows()) { + Map> rowMap = new HashMap<>(); + rowMap.put("cells", row.getCells().stream().map(TableCell::getValue).collect(toList())); + rowList.add(rowMap); + } + return rowList; + } + + private String getDateTimeFromTimeStamp(Timestamp instant) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + .withZone(ZoneOffset.UTC); + return formatter.format(Convertor.toInstant(instant)); + } + + static class JvmFeature { + private final String uri; + private final String id; + private final Long line; + private final String keyword; + private final String name; + private final String description; + private final List elements; + private final List tags; + + JvmFeature(String uri, String id, Long line, String keyword, String name, String description, List elements, List tags) { + this.uri = requireNonNull(uri); + this.id = requireNonNull(id); + this.line = requireNonNull(line); + this.keyword = requireNonNull(keyword); + this.name = requireNonNull(name); + this.description = requireNonNull(description); + this.elements = requireNonNull(elements); + this.tags = tags; + } + + public String getUri() { + return uri; + } + + public String getId() { + return id; + } + + public Long getLine() { + return line; + } + + public String getKeyword() { + return keyword; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getElements() { + return elements; + } + + public List getTags() { + return tags; + } + } + + enum JvmElementType { + background, scenario + } + static class JvmElement { + private final String start_timestamp; + private final Integer line; + private final String id; + private final JvmElementType type; + private final String keyword; + private final String name; + private final String description; + private final List steps; + private final List before; + private final List after; + private final List tags; + + JvmElement(String start_timestamp, Integer line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags) { + this.start_timestamp = start_timestamp; + this.line = requireNonNull(line); + this.id = id; + this.type = requireNonNull(type); + this.keyword = requireNonNull(keyword); + this.name = requireNonNull(name); + this.description = requireNonNull(description); + this.steps = requireNonNull(steps); + this.before = before; + this.after = after; + this.tags = tags; + } + + public String getStart_timestamp() { + return start_timestamp; + } + + public Integer getLine() { + return line; + } + + public String getId() { + return id; + } + + public JvmElementType getType() { + return type; + } + + public String getKeyword() { + return keyword; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getSteps() { + return steps; + } + + public List getBefore() { + return before; + } + + public List getAfter() { + return after; + } + + public List getTags() { + return tags; + } + } + + static class JvmStep { + private final String keyword; + private final Integer line; + private final JvmMatch match; + private final String name; + private final JvmResult result; + private final JvmDocString doc_string; + private final List rows; + + JvmStep(String keyword, Integer line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows) { + this.keyword = requireNonNull(keyword); + this.line = requireNonNull(line); + this.match = match; + this.name = requireNonNull(name); + this.result = requireNonNull(result); + this.doc_string = doc_string; + this.rows = rows; + } + + public String getKeyword() { + return keyword; + } + + public Integer getLine() { + return line; + } + + public JvmMatch getMatch() { + return match; + } + + public String getName() { + return name; + } + + public JvmResult getResult() { + return result; + } + + public JvmDocString getDoc_string() { + return doc_string; + } + + public List getRows() { + return rows; + } + } + + static class JvmMatch { + private final String location; + private final List arguments; + + JvmMatch(String location, List arguments) { + this.location = location; + this.arguments = arguments; + } + + public String getLocation() { + return location; + } + + public List getArguments() { + return arguments; + } + } + + static class JvmArgument { + private final String val; + private final Number offset; + + JvmArgument(String val, Number offset) { + this.val = requireNonNull(val); + this.offset = requireNonNull(offset); + } + + public String getVal() { + return val; + } + + public Number getOffset() { + return offset; + } + } + + static class JvmResult { + private final Integer duration; + private final JvmStatus status; + private final String error_message; + + JvmResult(Integer duration, JvmStatus status, String error_message) { + this.duration = duration; + this.status = requireNonNull(status); + this.error_message = error_message; + } + + public Integer getDuration() { + return duration; + } + + public JvmStatus getStatus() { + return status; + } + + public String getError_message() { + return error_message; + } + } + enum JvmStatus { + passed, + failed, + skipped, + undefined, + pending + } + + + static class JvmDocString { + private final Integer line; + private final String value; + private final String content_type; + + JvmDocString(Integer line, String value, String content_type) { + this.line = requireNonNull(line); + this.value = requireNonNull(value); + this.content_type = content_type; + } + + public Integer getLine() { + return line; + } + + public String getValue() { + return value; + } + + public String getContent_type() { + return content_type; + } + } + + static class JvmDataTableRow { + private final List cells; + + JvmDataTableRow(List cells) { + this.cells = requireNonNull(cells); + } + + public List getCells() { + return cells; + } + } + + static class JvmHook { + private final JvmMatch match; + private final JvmResult result; + + JvmHook(JvmMatch match, JvmResult result) { + this.match = requireNonNull(match); + this.result = requireNonNull(result); + } + + public JvmMatch getMatch() { + return match; + } + + public JvmResult getResult() { + return result; + } + } + + static class JvmTag { + private final String name; + + JvmTag(String name) { + this.name = requireNonNull(name); + } + + public String getName() { + return name; + } + } + + static class JvmLocationTag { + private final String name; + private final String type; + private final JvmLocation location; + + JvmLocationTag(String name, String type, JvmLocation location) { + this.name = requireNonNull(name); + this.type = requireNonNull(type); + this.location = requireNonNull(location); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public JvmLocation getLocation() { + return location; + } + } + + static class JvmLocation { + private final Long line; + private final Long column; + + JvmLocation(Long line, Long column) { + this.line = requireNonNull(line); + this.column = requireNonNull(column); + } + + public Long getLine() { + return line; + } + + public Long getColumn() { + return column; + } + } + + + + + +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java new file mode 100644 index 0000000000..3205a1700f --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java @@ -0,0 +1,81 @@ +package io.cucumber.core.plugin; + + +import io.cucumber.messages.types.Envelope; +import io.cucumber.query.Query; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +/** + * Writes the message output of a test run as single json report. + *

+ * Note: Messages are first collected and only written once the stream is closed. + */ +public class MessagesToJsonWriter implements AutoCloseable { + + private final OutputStreamWriter out; + private final Query query = new Query(); + private final Serializer serializer; + private boolean streamClosed = false; + + public MessagesToJsonWriter(OutputStream out, Serializer serializer) { + this.out = new OutputStreamWriter( + requireNonNull(out), + StandardCharsets.UTF_8 + ); + this.serializer = serializer; + } + + + /** + * Writes a cucumber message to the xml output. + * + * @param envelope the message + * @throws IOException if an IO error occurs + */ + public void write(Envelope envelope) throws IOException { + if (streamClosed) { + throw new IOException("Stream closed"); + } + query.update(envelope); + } + + /** + * Closes the stream, flushing it first. Once closed further write() + * invocations will cause an IOException to be thrown. Closing a closed + * stream has no effect. + * + * @throws IOException if an IO error occurs + */ + @Override + public void close() throws IOException { + if (streamClosed) { + return; + } + try { + List report = new JsonReportWriter(query).writeJsonReport(); + serializer.writeValue(out, report); + } finally { + try { + out.close(); + } finally { + streamClosed = true; + } + } + } + + @FunctionalInterface + public interface Serializer { + + void writeValue(Writer writer, Object value) throws IOException; + + } +} + diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java index 63b03f418b..fc6361b27e 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java @@ -1,11 +1,20 @@ package io.cucumber.core.backend; +import java.util.Optional; + public class StubLocation implements Located { private final String location; + private final SourceReference sourceReference; public StubLocation(String location) { this.location = location; + this.sourceReference = null; + } + + public StubLocation(SourceReference location) { + this.location = null; + this.sourceReference = location; } @Override @@ -13,6 +22,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; } + @Override + public Optional getSourceReference() { + return Optional.ofNullable(sourceReference); + } + @Override public String getLocation() { return location; diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index c96d7aca1a..43fa059d11 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -1,7 +1,9 @@ package io.cucumber.core.plugin; +import io.cucumber.core.backend.SourceReference; import io.cucumber.core.backend.StubHookDefinition; import io.cucumber.core.backend.StubStepDefinition; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.options.RuntimeOptionsBuilder; @@ -168,7 +170,7 @@ void should_format_scenario_with_a_passed_step() throws JSONException { Runtime.builder() .withFeatureSupplier(new StubFeatureSupplier(feature)) .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withEventBus(new TimeServiceEventBus(timeService, new IncrementingUuidGenerator())) .withBackendSupplier(new StubBackendSupplier( new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"))) .build() From d8ea3b4a2796f051d9dd96c3256f41970531d848 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 18 Apr 2024 20:01:10 +0200 Subject: [PATCH 02/41] WIP --- .../cucumber/core/plugin/IdNamingVisitor.java | 42 +++++ .../core/plugin/JsonReportWriter.java | 144 +++++++----------- 2 files changed, 100 insertions(+), 86 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java new file mode 100644 index 0000000000..00e8f81f05 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java @@ -0,0 +1,42 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.Rule; +import io.cucumber.messages.types.Scenario; +import io.cucumber.query.NamingStrategy; + +import static io.cucumber.core.plugin.TestSourcesModel.convertToId; + +class IdNamingVisitor implements NamingStrategy.NamingVisitor { + @Override + public String accept(Feature feature) { + return convertToId(feature.getName()); + } + + @Override + public String accept(Rule rule) { + return convertToId(rule.getName()); + } + + @Override + public String accept(Scenario scenario) { + return convertToId(scenario.getName()); + } + + @Override + public String accept(Examples examples) { + return convertToId(examples.getName()); + } + + @Override + public String accept(int examplesIndex, int exampleIndex) { + return (examplesIndex + 1) + ";" + (examplesIndex + 1); + } + + @Override + public String accept(Pickle pickle) { + return convertToId(pickle.getName()); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 61ae71e519..0bdb02efe6 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -4,7 +4,6 @@ import io.cucumber.messages.types.Background; import io.cucumber.messages.types.DataTable; import io.cucumber.messages.types.DocString; -import io.cucumber.messages.types.Examples; import io.cucumber.messages.types.Feature; import io.cucumber.messages.types.GherkinDocument; import io.cucumber.messages.types.Group; @@ -16,7 +15,6 @@ import io.cucumber.messages.types.PickleTag; import io.cucumber.messages.types.Rule; import io.cucumber.messages.types.RuleChild; -import io.cucumber.messages.types.Scenario; import io.cucumber.messages.types.SourceReference; import io.cucumber.messages.types.Step; import io.cucumber.messages.types.StepDefinition; @@ -102,7 +100,7 @@ private static JvmLocationTag createLocationTag(Tag tag) { } private GherkinDocument getGherkinDocument(Entry, List> entry) { - return entry.getValue().stream().findAny().flatMap(query::findGherkinDocumentBy).orElseThrow(() -> new IllegalArgumentException("No Gherking document")); + return entry.getValue().stream().findAny().flatMap(query::findGherkinDocumentBy).orElseThrow(() -> new IllegalArgumentException("No Gherkin document")); } private List writeElementsReport(Entry, List> entry) { @@ -162,46 +160,20 @@ static List getBackgroundForTestCase(Feature feature) { } private JvmElement createBackground(Background background, List testStepsFinished) { - Map testCaseMap = new HashMap<>(); - testCaseMap.put("name", background.getName()); - testCaseMap.put("line", background.getLocation().getLine()); - testCaseMap.put("type", "background"); - testCaseMap.put("keyword", background.getKeyword()); - testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); - testCaseMap.put("steps", testStepsFinished.stream().map(this::createTestStep).collect(toList())); - return testCaseMap; - } - - private class IdNamingVisitor implements NamingStrategy.NamingVisitor { - @Override - public String accept(Feature feature) { - return convertToId(feature.getName()); - } - - @Override - public String accept(Rule rule) { - return convertToId(rule.getName()); - } - - @Override - public String accept(Scenario scenario) { - return convertToId(scenario.getName()); - } - - @Override - public String accept(Examples examples) { - return convertToId(examples.getName()); - } - - @Override - public String accept(int examplesIndex, int exampleIndex) { - return (examplesIndex + 1) + ";" + (examplesIndex + 1); - } - - @Override - public String accept(Pickle pickle) { - return convertToId(pickle.getName()); - } + //String start_timestamp, Integer line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags + return new JvmElement( + null, + background.getLocation().getLine(), + null, + "background", + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + testStepsFinished.stream().map(this::createTestStep).collect(toList()), + null, + null, + null + ); } @@ -230,37 +202,38 @@ private JvmElement createTestCase(TestCaseStarted event, List .collect(toList())); if (!pickle.getTags().isEmpty()) { - List> tagList = new ArrayList<>(); - for (PickleTag tag : pickle.getTags()) { - Map tagMap = new HashMap<>(); - tagMap.put("name", tag.getName()); - tagList.add(tagMap); - } - testCaseMap.put("tags", tagList); + testCaseMap.put("tags", createTags(pickle)); } return testCaseMap; } - private Map createTestStep(TestStepFinished testStepFinished) { - Map stepMap = new HashMap<>(); - TestStep testStep = query.findTestStepBy(testStepFinished).orElseThrow(); - query.findPickleStepBy(testStep).ifPresent(pickleStep -> { - Step step = query.findStepBy(pickleStep).orElseThrow(); - stepMap.put("name", step.getText()); - stepMap.put("line", step.getLocation().getLine()); - - step.getDocString().ifPresent(docString -> { - stepMap.put("doc_string", createDocStringMap(docString)); - }); - step.getDataTable().ifPresent(dataTable -> { - stepMap.put("rows", createDataTableList(dataTable)); - }); - stepMap.put("keyword", step.getKeyword()); - stepMap.put("match", createMatchMap(testStep, testStepFinished.getTestStepResult())); - stepMap.put("result", createResultMap(testStepFinished.getTestStepResult())); + private static List> createTags(Pickle pickle) { + List> tagList = new ArrayList<>(); + for (PickleTag tag : pickle.getTags()) { + Map tagMap = new HashMap<>(); + tagMap.put("name", tag.getName()); + tagList.add(tagMap); + } + return tagList; + } - }); - return stepMap; + private JvmStep createTestStep(TestStepFinished testStepFinished) { + TestStep testStep = query.findTestStepBy(testStepFinished).orElseThrow(); + return query.findPickleStepBy(testStep) + .map(pickleStep -> { + Step step = query.findStepBy(pickleStep).orElseThrow(); + //String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows + return new JvmStep( + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + step.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null) + ); + }).get(); + // TODO: Hook steps } private Map createMatchMap(TestStep step, TestStepResult result) { @@ -341,7 +314,7 @@ private Map createResultMap(TestStepResult result) { return resultMap; } - private Map createDocStringMap(DocString docString) { + private JvmDocString createDocStringMap(DocString docString) { Map docStringMap = new HashMap<>(); docStringMap.put("value", docString.getContent()); docStringMap.put("line", docString.getLocation().getLine()); @@ -349,7 +322,7 @@ private Map createDocStringMap(DocString docString) { return docStringMap; } - private List>> createDataTableList(DataTable argument) { + private List createDataTableList(DataTable argument) { List>> rowList = new ArrayList<>(); for (TableRow row : argument.getRows()) { Map> rowMap = new HashMap<>(); @@ -373,9 +346,9 @@ static class JvmFeature { private final String name; private final String description; private final List elements; - private final List tags; + private final List tags; - JvmFeature(String uri, String id, Long line, String keyword, String name, String description, List elements, List tags) { + JvmFeature(String uri, String id, Long line, String keyword, String name, String description, List elements, List tags) { this.uri = requireNonNull(uri); this.id = requireNonNull(id); this.line = requireNonNull(line); @@ -414,7 +387,7 @@ public List getElements() { return elements; } - public List getTags() { + public List getTags() { return tags; } } @@ -422,9 +395,10 @@ public List getTags() { enum JvmElementType { background, scenario } + static class JvmElement { private final String start_timestamp; - private final Integer line; + private final Long line; private final String id; private final JvmElementType type; private final String keyword; @@ -435,7 +409,7 @@ static class JvmElement { private final List after; private final List tags; - JvmElement(String start_timestamp, Integer line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags) { + JvmElement(String start_timestamp, Long line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags) { this.start_timestamp = start_timestamp; this.line = requireNonNull(line); this.id = id; @@ -453,7 +427,7 @@ public String getStart_timestamp() { return start_timestamp; } - public Integer getLine() { + public Long getLine() { return line; } @@ -496,14 +470,14 @@ public List getTags() { static class JvmStep { private final String keyword; - private final Integer line; + private final Long line; private final JvmMatch match; private final String name; private final JvmResult result; private final JvmDocString doc_string; private final List rows; - JvmStep(String keyword, Integer line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows) { + JvmStep(String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows) { this.keyword = requireNonNull(keyword); this.line = requireNonNull(line); this.match = match; @@ -517,7 +491,7 @@ public String getKeyword() { return keyword; } - public Integer getLine() { + public Long getLine() { return line; } @@ -601,6 +575,7 @@ public String getError_message() { return error_message; } } + enum JvmStatus { passed, failed, @@ -611,17 +586,17 @@ enum JvmStatus { static class JvmDocString { - private final Integer line; + private final Long line; private final String value; private final String content_type; - JvmDocString(Integer line, String value, String content_type) { + JvmDocString(Long line, String value, String content_type) { this.line = requireNonNull(line); this.value = requireNonNull(value); this.content_type = content_type; } - public Integer getLine() { + public Long getLine() { return line; } @@ -719,7 +694,4 @@ public Long getColumn() { } - - - } From 799cea73213ab19c148a8a32271b5cd15cdf8145 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 18 Apr 2024 21:07:01 +0200 Subject: [PATCH 03/41] WIP --- .../cucumber/core/plugin/CucumberJvmJson.java | 363 +++++++++++ .../core/plugin/JsonReportWriter.java | 568 ++++-------------- 2 files changed, 467 insertions(+), 464 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java new file mode 100644 index 0000000000..31fad26f15 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -0,0 +1,363 @@ +package io.cucumber.core.plugin; + +import java.util.List; + +import static java.util.Objects.requireNonNull; + +/** + * Object representation of cucumber-jvm.json schema. + */ +class CucumberJvmJson { + enum JvmElementType { + background, scenario + } + enum JvmStatus { + passed, + failed, + skipped, + undefined, + pending + } + + static class JvmFeature { + private final String uri; + private final String id; + private final Long line; + private final String keyword; + private final String name; + private final String description; + private final List elements; + private final List tags; + + JvmFeature(String uri, String id, Long line, String keyword, String name, String description, List elements, List tags) { + this.uri = requireNonNull(uri); + this.id = requireNonNull(id); + this.line = requireNonNull(line); + this.keyword = requireNonNull(keyword); + this.name = requireNonNull(name); + this.description = requireNonNull(description); + this.elements = requireNonNull(elements); + this.tags = tags; + } + + public String getUri() { + return uri; + } + + public String getId() { + return id; + } + + public Long getLine() { + return line; + } + + public String getKeyword() { + return keyword; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getElements() { + return elements; + } + + public List getTags() { + return tags; + } + } + + static class JvmElement { + private final String start_timestamp; + private final Long line; + private final String id; + private final JvmElementType type; + private final String keyword; + private final String name; + private final String description; + private final List steps; + private final List before; + private final List after; + private final List tags; + + JvmElement(String start_timestamp, Long line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags) { + this.start_timestamp = start_timestamp; + this.line = requireNonNull(line); + this.id = id; + this.type = requireNonNull(type); + this.keyword = requireNonNull(keyword); + this.name = requireNonNull(name); + this.description = requireNonNull(description); + this.steps = requireNonNull(steps); + this.before = before; + this.after = after; + this.tags = tags; + } + + public String getStart_timestamp() { + return start_timestamp; + } + + public Long getLine() { + return line; + } + + public String getId() { + return id; + } + + public JvmElementType getType() { + return type; + } + + public String getKeyword() { + return keyword; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getSteps() { + return steps; + } + + public List getBefore() { + return before; + } + + public List getAfter() { + return after; + } + + public List getTags() { + return tags; + } + } + + static class JvmStep { + private final String keyword; + private final Long line; + private final JvmMatch match; + private final String name; + private final JvmResult result; + private final JvmDocString doc_string; + private final List rows; + + JvmStep(String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows) { + this.keyword = requireNonNull(keyword); + this.line = requireNonNull(line); + this.match = match; + this.name = requireNonNull(name); + this.result = requireNonNull(result); + this.doc_string = doc_string; + this.rows = rows; + } + + public String getKeyword() { + return keyword; + } + + public Long getLine() { + return line; + } + + public JvmMatch getMatch() { + return match; + } + + public String getName() { + return name; + } + + public JvmResult getResult() { + return result; + } + + public JvmDocString getDoc_string() { + return doc_string; + } + + public List getRows() { + return rows; + } + } + + static class JvmMatch { + private final String location; + private final List arguments; + + JvmMatch(String location, List arguments) { + this.location = location; + this.arguments = arguments; + } + + public String getLocation() { + return location; + } + + public List getArguments() { + return arguments; + } + } + + static class JvmArgument { + private final String val; + private final Number offset; + + JvmArgument(String val, Number offset) { + this.val = requireNonNull(val); + this.offset = requireNonNull(offset); + } + + public String getVal() { + return val; + } + + public Number getOffset() { + return offset; + } + } + + static class JvmResult { + private final Long duration; + private final JvmStatus status; + private final String error_message; + + JvmResult(Long duration, JvmStatus status, String error_message) { + this.duration = duration; + this.status = requireNonNull(status); + this.error_message = error_message; + } + + public Long getDuration() { + return duration; + } + + public JvmStatus getStatus() { + return status; + } + + public String getError_message() { + return error_message; + } + } + + static class JvmDocString { + private final Long line; + private final String value; + private final String content_type; + + JvmDocString(Long line, String value, String content_type) { + this.line = requireNonNull(line); + this.value = requireNonNull(value); + this.content_type = content_type; + } + + public Long getLine() { + return line; + } + + public String getValue() { + return value; + } + + public String getContent_type() { + return content_type; + } + } + + static class JvmDataTableRow { + private final List cells; + + JvmDataTableRow(List cells) { + this.cells = requireNonNull(cells); + } + + public List getCells() { + return cells; + } + } + + static class JvmHook { + private final JvmMatch match; + private final JvmResult result; + + JvmHook(JvmMatch match, JvmResult result) { + this.match = requireNonNull(match); + this.result = requireNonNull(result); + } + + public JvmMatch getMatch() { + return match; + } + + public JvmResult getResult() { + return result; + } + } + + static class JvmTag { + private final String name; + + JvmTag(String name) { + this.name = requireNonNull(name); + } + + public String getName() { + return name; + } + } + + static class JvmLocationTag { + private final String name; + private final String type; + private final JvmLocation location; + + JvmLocationTag(String name, String type, JvmLocation location) { + this.name = requireNonNull(name); + this.type = requireNonNull(type); + this.location = requireNonNull(location); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public JvmLocation getLocation() { + return location; + } + } + + static class JvmLocation { + private final Long line; + private final Long column; + + JvmLocation(Long line, Long column) { + this.line = requireNonNull(line); + this.column = requireNonNull(column); + } + + public Long getLine() { + return line; + } + + public Long getColumn() { + return column; + } + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 0bdb02efe6..67cb851148 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -1,26 +1,38 @@ package io.cucumber.core.plugin; +import io.cucumber.core.plugin.CucumberJvmJson.JvmArgument; +import io.cucumber.core.plugin.CucumberJvmJson.JvmDataTableRow; +import io.cucumber.core.plugin.CucumberJvmJson.JvmDocString; +import io.cucumber.core.plugin.CucumberJvmJson.JvmElement; +import io.cucumber.core.plugin.CucumberJvmJson.JvmElementType; +import io.cucumber.core.plugin.CucumberJvmJson.JvmFeature; +import io.cucumber.core.plugin.CucumberJvmJson.JvmLocation; +import io.cucumber.core.plugin.CucumberJvmJson.JvmLocationTag; +import io.cucumber.core.plugin.CucumberJvmJson.JvmMatch; +import io.cucumber.core.plugin.CucumberJvmJson.JvmResult; +import io.cucumber.core.plugin.CucumberJvmJson.JvmStatus; +import io.cucumber.core.plugin.CucumberJvmJson.JvmStep; +import io.cucumber.core.plugin.CucumberJvmJson.JvmTag; import io.cucumber.messages.Convertor; import io.cucumber.messages.types.Background; import io.cucumber.messages.types.DataTable; import io.cucumber.messages.types.DocString; +import io.cucumber.messages.types.Exception; import io.cucumber.messages.types.Feature; import io.cucumber.messages.types.GherkinDocument; import io.cucumber.messages.types.Group; import io.cucumber.messages.types.JavaMethod; import io.cucumber.messages.types.JavaStackTraceElement; -import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.PickleStep; -import io.cucumber.messages.types.PickleTag; import io.cucumber.messages.types.Rule; import io.cucumber.messages.types.RuleChild; +import io.cucumber.messages.types.Scenario; import io.cucumber.messages.types.SourceReference; import io.cucumber.messages.types.Step; import io.cucumber.messages.types.StepDefinition; import io.cucumber.messages.types.StepMatchArgumentsList; import io.cucumber.messages.types.TableCell; -import io.cucumber.messages.types.TableRow; import io.cucumber.messages.types.Tag; import io.cucumber.messages.types.TestCaseStarted; import io.cucumber.messages.types.TestStep; @@ -38,7 +50,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -50,7 +61,6 @@ import static io.cucumber.core.plugin.TestSourcesModel.convertToId; import static java.util.Locale.ROOT; -import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; @@ -100,7 +110,10 @@ private static JvmLocationTag createLocationTag(Tag tag) { } private GherkinDocument getGherkinDocument(Entry, List> entry) { - return entry.getValue().stream().findAny().flatMap(query::findGherkinDocumentBy).orElseThrow(() -> new IllegalArgumentException("No Gherkin document")); + return entry.getValue().stream() + .findFirst() + .flatMap(query::findGherkinDocumentBy) + .orElseThrow(() -> new IllegalArgumentException("No Gherkin document")); } private List writeElementsReport(Entry, List> entry) { @@ -165,11 +178,11 @@ private JvmElement createBackground(Background background, List testStepsFinished) { - Map testCaseMap = new HashMap<>(); - - testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getTimestamp())); - Pickle pickle = query.findPickleBy(event).orElseThrow(); - Location location = query.findLocationOf(pickle).orElseThrow(); - - testCaseMap.put("name", pickle.getName()); - testCaseMap.put("line", location.getLine()); - testCaseMap.put("type", "scenario"); - - query.findScenarioBy(event).ifPresent(scenario -> { - testCaseMap.put("id", query.findNameOf(pickle, NamingStrategy.strategy(NamingStrategy.Strategy.LONG).delimiter(";").namingVisitor(new IdNamingVisitor()).build())); - testCaseMap.put("keyword", scenario.getKeyword()); - // TODO: Should not be nullable? - testCaseMap.put("description", scenario.getDescription() != null ? scenario.getDescription() : ""); - }); + Scenario scenario = query.findScenarioBy(event).orElseThrow(); + NamingStrategy idStrategy = NamingStrategy.strategy(NamingStrategy.Strategy.LONG).delimiter(";").namingVisitor(new IdNamingVisitor()).build(); + return new JvmElement( + getDateTimeFromTimeStamp(event.getTimestamp()), + query.findLocationOf(pickle).orElseThrow().getLine(), + query.findNameOf(pickle, idStrategy), + JvmElementType.scenario, + scenario.getKeyword(), + pickle.getName(), + scenario.getDescription() != null ? scenario.getDescription() : "", + createTestSteps(testStepsFinished), + null, // TODO: Hooks + null, // TODO: Hooks + pickle.getTags().isEmpty() ? null : createTags(pickle) + ); + } - // TODO: Create steps - testCaseMap.put("steps", testStepsFinished.stream() + private List createTestSteps(List testStepsFinished) { + return testStepsFinished.stream() .map(this::createTestStep) - .collect(toList())); - - if (!pickle.getTags().isEmpty()) { - testCaseMap.put("tags", createTags(pickle)); - } - return testCaseMap; + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toList()); } - private static List> createTags(Pickle pickle) { - List> tagList = new ArrayList<>(); - for (PickleTag tag : pickle.getTags()) { - Map tagMap = new HashMap<>(); - tagMap.put("name", tag.getName()); - tagList.add(tagMap); - } - return tagList; + private static List createTags(Pickle pickle) { + return pickle.getTags().stream().map(pickleTag -> new JvmTag(pickleTag.getName())).collect(toList()); } - private JvmStep createTestStep(TestStepFinished testStepFinished) { - TestStep testStep = query.findTestStepBy(testStepFinished).orElseThrow(); - return query.findPickleStepBy(testStep) - .map(pickleStep -> { - Step step = query.findStepBy(pickleStep).orElseThrow(); - //String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows - return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - step.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null) - ); - }).get(); - // TODO: Hook steps + private Optional createTestStep(TestStepFinished testStepFinished) { + return query.findTestStepBy(testStepFinished) + .flatMap(testStep -> query.findPickleStepBy(testStep) + .flatMap(query::findStepBy) + .map(step -> new JvmStep( + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + step.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null) + )) + ); } - private Map createMatchMap(TestStep step, TestStepResult result) { - Map matchMap = new HashMap<>(); + private JvmMatch createMatchMap(TestStep step, TestStepResult result) { + Optional source = query.findStepDefinitionBy(step) + .stream() + .findFirst() + .map(StepDefinition::getSourceReference); + + Optional location = source.flatMap(sourceReference -> { + Optional fromUri = sourceReference.getUri() + .map(uri -> renderLocationString(sourceReference, uri)); + + Optional fromJavaMethod = sourceReference.getJavaMethod() + .map(JsonReportWriter::renderLocationString); + + Optional fromStackTrace = sourceReference.getJavaStackTraceElement() + .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)); + + return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent).map(Optional::get).findFirst(); + }); - step.getStepMatchArgumentsLists() + Optional> argumentList = step.getStepMatchArgumentsLists() .map(argumentsLists -> argumentsLists.stream() .map(StepMatchArgumentsList::getStepMatchArguments) .flatMap(Collection::stream) .map(argument -> { - Map argumentMap = new HashMap<>(); Group group = argument.getGroup(); - group.getValue().ifPresent(value -> argumentMap.put("val", value)); - group.getStart().ifPresent(offset -> argumentMap.put("offset", offset)); - return argumentMap; + return new JvmArgument( + // TODO: Nullable + group.getValue().get(), + group.getStart().get() + ); }).collect(toList())) - .filter(maps -> !maps.isEmpty()) - .ifPresent(argumentList -> matchMap.put("arguments", argumentList)); - - if (result.getStatus() != TestStepResultStatus.UNDEFINED) { - Optional source = query.findStepDefinitionBy(step) - .stream() - .findFirst() - .map(StepDefinition::getSourceReference); - source - .ifPresent(sourceReference -> { - sourceReference.getUri() - .map(uri -> renderLocationString(sourceReference, uri)) - .ifPresent(location -> matchMap.put("location", location)); - sourceReference.getJavaMethod() - .map(JsonReportWriter::renderLocationString) - .ifPresent(location -> matchMap.put("location", location)); - sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)) - .ifPresent(location -> matchMap.put("location", location)); - }); - } - return matchMap; + .filter(maps -> !maps.isEmpty()); + + return new JvmMatch( + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null) + ); } private static String renderLocationString(SourceReference sourceReference, String uri) { @@ -300,36 +302,29 @@ private static String renderLocationString(JavaMethod javaMethod) { ); } - private Map createResultMap(TestStepResult result) { - Map resultMap = new HashMap<>(); - resultMap.put("status", result.getStatus().name().toLowerCase(ROOT)); - result.getException().ifPresent(exception -> { - resultMap.put("error_message", exception.getStackTrace()); - }); - + private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); - if (!duration.isZero()) { - resultMap.put("duration", duration.toNanos()); - } - return resultMap; + return new JvmResult( + duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null) + ); } private JvmDocString createDocStringMap(DocString docString) { - Map docStringMap = new HashMap<>(); - docStringMap.put("value", docString.getContent()); - docStringMap.put("line", docString.getLocation().getLine()); - docStringMap.put("content_type", docString.getMediaType()); - return docStringMap; + return new JvmDocString( + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null) + ); } private List createDataTableList(DataTable argument) { - List>> rowList = new ArrayList<>(); - for (TableRow row : argument.getRows()) { - Map> rowMap = new HashMap<>(); - rowMap.put("cells", row.getCells().stream().map(TableCell::getValue).collect(toList())); - rowList.add(rowMap); - } - return rowList; + return argument.getRows().stream() + .map(row -> new JvmDataTableRow(row.getCells().stream() + .map(TableCell::getValue) + .collect(toList()))) + .collect(toList()); } private String getDateTimeFromTimeStamp(Timestamp instant) { @@ -338,360 +333,5 @@ private String getDateTimeFromTimeStamp(Timestamp instant) { return formatter.format(Convertor.toInstant(instant)); } - static class JvmFeature { - private final String uri; - private final String id; - private final Long line; - private final String keyword; - private final String name; - private final String description; - private final List elements; - private final List tags; - - JvmFeature(String uri, String id, Long line, String keyword, String name, String description, List elements, List tags) { - this.uri = requireNonNull(uri); - this.id = requireNonNull(id); - this.line = requireNonNull(line); - this.keyword = requireNonNull(keyword); - this.name = requireNonNull(name); - this.description = requireNonNull(description); - this.elements = requireNonNull(elements); - this.tags = tags; - } - - public String getUri() { - return uri; - } - - public String getId() { - return id; - } - - public Long getLine() { - return line; - } - - public String getKeyword() { - return keyword; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public List getElements() { - return elements; - } - - public List getTags() { - return tags; - } - } - - enum JvmElementType { - background, scenario - } - - static class JvmElement { - private final String start_timestamp; - private final Long line; - private final String id; - private final JvmElementType type; - private final String keyword; - private final String name; - private final String description; - private final List steps; - private final List before; - private final List after; - private final List tags; - - JvmElement(String start_timestamp, Long line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags) { - this.start_timestamp = start_timestamp; - this.line = requireNonNull(line); - this.id = id; - this.type = requireNonNull(type); - this.keyword = requireNonNull(keyword); - this.name = requireNonNull(name); - this.description = requireNonNull(description); - this.steps = requireNonNull(steps); - this.before = before; - this.after = after; - this.tags = tags; - } - - public String getStart_timestamp() { - return start_timestamp; - } - - public Long getLine() { - return line; - } - - public String getId() { - return id; - } - - public JvmElementType getType() { - return type; - } - - public String getKeyword() { - return keyword; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public List getSteps() { - return steps; - } - - public List getBefore() { - return before; - } - - public List getAfter() { - return after; - } - - public List getTags() { - return tags; - } - } - - static class JvmStep { - private final String keyword; - private final Long line; - private final JvmMatch match; - private final String name; - private final JvmResult result; - private final JvmDocString doc_string; - private final List rows; - - JvmStep(String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, List rows) { - this.keyword = requireNonNull(keyword); - this.line = requireNonNull(line); - this.match = match; - this.name = requireNonNull(name); - this.result = requireNonNull(result); - this.doc_string = doc_string; - this.rows = rows; - } - - public String getKeyword() { - return keyword; - } - - public Long getLine() { - return line; - } - - public JvmMatch getMatch() { - return match; - } - - public String getName() { - return name; - } - - public JvmResult getResult() { - return result; - } - - public JvmDocString getDoc_string() { - return doc_string; - } - - public List getRows() { - return rows; - } - } - - static class JvmMatch { - private final String location; - private final List arguments; - - JvmMatch(String location, List arguments) { - this.location = location; - this.arguments = arguments; - } - - public String getLocation() { - return location; - } - - public List getArguments() { - return arguments; - } - } - - static class JvmArgument { - private final String val; - private final Number offset; - - JvmArgument(String val, Number offset) { - this.val = requireNonNull(val); - this.offset = requireNonNull(offset); - } - - public String getVal() { - return val; - } - - public Number getOffset() { - return offset; - } - } - - static class JvmResult { - private final Integer duration; - private final JvmStatus status; - private final String error_message; - - JvmResult(Integer duration, JvmStatus status, String error_message) { - this.duration = duration; - this.status = requireNonNull(status); - this.error_message = error_message; - } - - public Integer getDuration() { - return duration; - } - - public JvmStatus getStatus() { - return status; - } - - public String getError_message() { - return error_message; - } - } - - enum JvmStatus { - passed, - failed, - skipped, - undefined, - pending - } - - - static class JvmDocString { - private final Long line; - private final String value; - private final String content_type; - - JvmDocString(Long line, String value, String content_type) { - this.line = requireNonNull(line); - this.value = requireNonNull(value); - this.content_type = content_type; - } - - public Long getLine() { - return line; - } - - public String getValue() { - return value; - } - - public String getContent_type() { - return content_type; - } - } - - static class JvmDataTableRow { - private final List cells; - - JvmDataTableRow(List cells) { - this.cells = requireNonNull(cells); - } - - public List getCells() { - return cells; - } - } - - static class JvmHook { - private final JvmMatch match; - private final JvmResult result; - - JvmHook(JvmMatch match, JvmResult result) { - this.match = requireNonNull(match); - this.result = requireNonNull(result); - } - - public JvmMatch getMatch() { - return match; - } - - public JvmResult getResult() { - return result; - } - } - - static class JvmTag { - private final String name; - - JvmTag(String name) { - this.name = requireNonNull(name); - } - - public String getName() { - return name; - } - } - - static class JvmLocationTag { - private final String name; - private final String type; - private final JvmLocation location; - - JvmLocationTag(String name, String type, JvmLocation location) { - this.name = requireNonNull(name); - this.type = requireNonNull(type); - this.location = requireNonNull(location); - } - - public String getName() { - return name; - } - - public String getType() { - return type; - } - - public JvmLocation getLocation() { - return location; - } - } - - static class JvmLocation { - private final Long line; - private final Long column; - - JvmLocation(Long line, Long column) { - this.line = requireNonNull(line); - this.column = requireNonNull(column); - } - - public Long getLine() { - return line; - } - - public Long getColumn() { - return column; - } - } - } From 209fc9fdb59e8437e3d5b74556e787a720459b55 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 18 Apr 2024 21:20:43 +0200 Subject: [PATCH 04/41] WIP --- .../src/main/java/io/cucumber/core/plugin/JsonFormatter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java index 264c6e2f04..922929b0a6 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java @@ -1,13 +1,14 @@ package io.cucumber.core.plugin; import io.cucumber.messages.types.Envelope; +import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.EventListener; import io.cucumber.plugin.event.EventPublisher; import java.io.IOException; import java.io.OutputStream; -public final class JsonFormatter implements EventListener { +public final class JsonFormatter implements ConcurrentEventListener { private final MessagesToJsonWriter writer; From 73e07631ba6e9cac5a3a54ff5283ab4a33928a53 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 19 Apr 2024 13:19:47 +0200 Subject: [PATCH 05/41] Some greens --- .../core/plugin/JsonReportWriter.java | 2 - .../core/backend/StubHookDefinition.java | 21 ++- .../cucumber/core/backend/StubLocation.java | 5 +- .../core/backend/StubStepDefinition.java | 19 +++ .../core/plugin/JsonFormatterTest.java | 132 ++++++++++++------ 5 files changed, 126 insertions(+), 53 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 67cb851148..d05935d92c 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -80,10 +80,8 @@ List writeJsonReport() { } private JvmFeature createFeatureMap(Entry, List> entry) { - LinkedHashMap featureMap = new LinkedHashMap<>(); GherkinDocument document = getGherkinDocument(entry); Feature feature = entry.getKey().get(); - return new JvmFeature( TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), // TODO: Relativize, optional?, null? convertToId(feature.getName()), diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java index 2ae7a4a5ed..67a1e4bc17 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java @@ -1,16 +1,23 @@ package io.cucumber.core.backend; +import java.lang.reflect.Method; +import java.util.Optional; import java.util.function.Consumer; public class StubHookDefinition implements HookDefinition { private static final String STUBBED_LOCATION_WITH_DETAILS = "{stubbed location with details}"; - private final String location; + private final StubLocation location; private final RuntimeException exception; private final Consumer action; + public StubHookDefinition(Method location, RuntimeException exception, Consumer action) { + this.location = new StubLocation(location); + this.exception = exception; + this.action = action; + } public StubHookDefinition(String location, RuntimeException exception, Consumer action) { - this.location = location; + this.location = new StubLocation(location); this.exception = exception; this.action = action; } @@ -35,6 +42,10 @@ public StubHookDefinition(String location) { this(location, null, null); } + public StubHookDefinition(Method location) { + this(location, null, null); + } + @Override public void execute(TestCaseState state) { if (action != null) { @@ -62,7 +73,11 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { @Override public String getLocation() { - return location; + return location.getLocation(); } + @Override + public Optional getSourceReference() { + return location.getSourceReference(); + } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java index fc6361b27e..dc63ac69ee 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java @@ -1,5 +1,6 @@ package io.cucumber.core.backend; +import java.lang.reflect.Method; import java.util.Optional; public class StubLocation implements Located { @@ -12,9 +13,9 @@ public StubLocation(String location) { this.sourceReference = null; } - public StubLocation(SourceReference location) { + public StubLocation(Method method) { this.location = null; - this.sourceReference = location; + this.sourceReference = SourceReference.fromMethod(method); } @Override diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java index c09c631296..48084e7a9c 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java @@ -1,8 +1,10 @@ package io.cucumber.core.backend; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -20,6 +22,10 @@ public StubStepDefinition(String pattern, String location, Type... types) { this(pattern, location, null, types); } + public StubStepDefinition(String pattern, Method location, Type... types) { + this(pattern, location, null, types); + } + public StubStepDefinition(String pattern, Type... types) { this(pattern, STUBBED_LOCATION_WITH_DETAILS, null, types); } @@ -35,6 +41,14 @@ public StubStepDefinition(String pattern, String location, Throwable exception, this.exception = exception; } + + public StubStepDefinition(String pattern, Method location, Throwable exception, Type... types) { + this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); + this.expression = pattern; + this.location = new StubLocation(location); + this.exception = exception; + } + @Override public boolean isDefinedAt(StackTraceElement stackTraceElement) { return false; @@ -45,6 +59,11 @@ public String getLocation() { return location.getLocation(); } + @Override + public Optional getSourceReference() { + return location.getSourceReference(); + } + @Override public void execute(Object[] args) { if (exception != null) { diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 43fa059d11..6baf0175f7 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -1,6 +1,5 @@ package io.cucumber.core.plugin; -import io.cucumber.core.backend.SourceReference; import io.cucumber.core.backend.StubHookDefinition; import io.cucumber.core.backend.StubStepDefinition; import io.cucumber.core.eventbus.IncrementingUuidGenerator; @@ -20,6 +19,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.lang.reflect.Method; import java.util.Scanner; import java.util.UUID; @@ -35,6 +35,23 @@ class JsonFormatterTest { + final Method monkeyArrives = getMethod("monkey_arrives"); + final Method thereAreBananas = getMethod("there_are_bananas"); + final Method thereAreOranges = getMethod("there_are_oranges"); + final Method beforeHook1 = getMethod("before_hook_1"); + final Method afterHook1 = getMethod("after_hook_1"); + + final Method monkeyEatsBananas = getMethod("monkey_eats_bananas"); + final Method monkeyEatsMoreBananas = getMethod("monkey_eats_more_bananas"); + + private static Method getMethod(String name) { + try { + return StepDefs.class.getMethod(name); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + @Test void featureWithOutlineTest() throws JSONException { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -172,7 +189,7 @@ void should_format_scenario_with_a_passed_step() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, new IncrementingUuidGenerator())) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"))) + new StubStepDefinition("there are bananas", thereAreBananas))) .build() .run(); @@ -200,7 +217,7 @@ void should_format_scenario_with_a_passed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -231,7 +248,7 @@ void should_format_scenario_with_a_failed_step() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()", + new StubStepDefinition("there are bananas", thereAreBananas, new StubException()))) .build() .run(); @@ -260,7 +277,7 @@ void should_format_scenario_with_a_failed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"failed\",\n" + @@ -293,7 +310,7 @@ void should_format_scenario_with_a_rule() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"))) + new StubStepDefinition("there are bananas", thereAreBananas))) .build() .run(); @@ -319,7 +336,7 @@ void should_format_scenario_with_a_rule() throws JSONException { " \"line\": 5,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -360,7 +377,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"))) + new StubStepDefinition("there are bananas", thereAreBananas))) .build() .run(); @@ -384,7 +401,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + " },\n" + @@ -396,7 +413,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 9,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -419,7 +436,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 12,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -455,7 +472,7 @@ void should_format_scenario_outline_with_one_example() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"))) + new StubStepDefinition("there are bananas", thereAreBananas))) .build() .run(); @@ -483,7 +500,7 @@ void should_format_scenario_outline_with_one_example() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -520,9 +537,9 @@ void should_format_feature_with_background() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"), - new StubStepDefinition("the monkey eats bananas", "StepDefs.monkey_eats_bananas()"), - new StubStepDefinition("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"))) + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("the monkey eats bananas", monkeyEatsBananas), + new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) .build() .run(); @@ -548,7 +565,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -571,7 +588,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats bananas\",\n" + " \"line\": 7,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -592,7 +609,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -615,7 +632,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats more bananas\",\n" + " \"line\": 10,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_more_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_more_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -647,7 +664,7 @@ void should_format_feature_and_scenario_with_tags() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("the monkey eats more bananas", "StepDefs.monkey_eats_more_bananas()"))) + new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) .build() .run(); @@ -673,7 +690,7 @@ void should_format_feature_and_scenario_with_tags() throws JSONException { " \"line\": 5,\n" + " \"name\": \"the monkey eats more bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_eats_more_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_more_bananas()\"\n" + " },\n" + " \"keyword\": \"Then \"\n" + " }\n" + @@ -734,9 +751,9 @@ void should_format_scenario_with_hooks() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition("Hooks.before_hook_1()")), - singletonList(new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()")), - singletonList(new StubHookDefinition("Hooks.after_hook_1()")))) + singletonList(new StubHookDefinition(beforeHook1)), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + singletonList(new StubHookDefinition(afterHook1)))) .build() .run(); @@ -775,7 +792,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -821,8 +838,8 @@ void should_add_step_hooks_to_step() throws JSONException { emptyList(), singletonList(new StubHookDefinition("Hooks.beforestep_hooks_1()")), asList( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"), - new StubStepDefinition("monkey arrives", "StepDefs.monkey_arrives()")), + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("monkey arrives", monkeyArrives)), asList( new StubHookDefinition("Hooks.afterstep_hooks_1()"), new StubHookDefinition("Hooks.afterstep_hooks_2()")), @@ -863,7 +880,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"after\": [\n" + " {\n" + @@ -906,7 +923,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 5,\n" + " \"name\": \"monkey arrives\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_arrives()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_arrives()\"\n" + " },\n" + " \"after\": [\n" + " {\n" + @@ -961,7 +978,7 @@ void should_handle_write_from_a_hook() throws JSONException { .withBackendSupplier(new StubBackendSupplier( singletonList(new StubHookDefinition("Hooks.before_hook_1()", testCaseState -> testCaseState.log("printed from hook"))), - singletonList(new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()")), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) .build() .run(); @@ -1004,7 +1021,7 @@ void should_handle_write_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1038,7 +1055,7 @@ void should_handle_embed_from_a_hook() throws JSONException { singletonList(new StubHookDefinition("Hooks.before_hook_1()", testCaseState -> testCaseState .attach(new byte[] { 1, 2, 3 }, "mime-type;base64", null))), - singletonList(new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()")), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) .build() .run(); @@ -1084,7 +1101,7 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1118,7 +1135,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { singletonList(new StubHookDefinition("Hooks.before_hook_1()", testCaseState -> testCaseState.attach(new byte[] { 1, 2, 3 }, "mime-type;base64", "someEmbedding"))), - singletonList(new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()")), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) .build() .run(); @@ -1165,7 +1182,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1199,7 +1216,7 @@ void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()", String.class))) + new StubStepDefinition("there are bananas", thereAreBananas, String.class))) .build() .run(); @@ -1231,7 +1248,7 @@ void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1265,7 +1282,7 @@ void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() thr .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()", DocString.class))) + new StubStepDefinition("there are bananas", thereAreBananas, DocString.class))) .build() .run(); @@ -1298,7 +1315,7 @@ void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() thr " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1331,7 +1348,7 @@ void should_format_scenario_with_a_step_with_a_data_table() throws JSONException .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()", DataTable.class))) + new StubStepDefinition("there are bananas", thereAreBananas, DataTable.class))) .build() .run(); @@ -1373,7 +1390,7 @@ void should_format_scenario_with_a_step_with_a_data_table() throws JSONException " }\n" + " ],\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1409,8 +1426,8 @@ void should_handle_several_features() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", "StepDefs.there_are_bananas()"), - new StubStepDefinition("there are oranges", "StepDefs.there_are_oranges()"))) + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("there are oranges", thereAreOranges))) .build() .run(); @@ -1438,7 +1455,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1472,7 +1489,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are oranges\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_oranges()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_oranges()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1488,4 +1505,27 @@ void should_handle_several_features() throws JSONException { assertJsonEquals(expected, out); } + static class StepDefs { + public void before_hook_1(){ + + } + public void after_hook_1(){ + + } + public void there_are_bananas(){ + + } + public void there_are_oranges(){ + + } + public void monkey_eats_bananas(){ + + } + public void monkey_eats_more_bananas(){ + + } + public void monkey_arrives(){ + + } + } } From eeaa600e642630f38e7ca88268c29040c111d46e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 19 Apr 2024 13:46:53 +0200 Subject: [PATCH 06/41] Some greens --- .../core/plugin/JsonReportWriter.java | 21 ++++++++++++------- .../core/plugin/JsonFormatterTest.java | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index d05935d92c..a253f8e9b4 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -61,6 +61,7 @@ import static io.cucumber.core.plugin.TestSourcesModel.convertToId; import static java.util.Locale.ROOT; +import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; @@ -122,24 +123,29 @@ private List writeElementsReport(Entry, List createTestCaseAndBackGround(TestCaseStarted testCaseStarted) { - return query.findTestStepsFinishedBy(testCaseStarted) + return query.findTestStepFinishedAndTestStepBy(testCaseStarted) .stream() + .filter(entry -> entry.getValue().getPickleStepId().isPresent()) .collect(groupByBackground(testCaseStarted)) .entrySet().stream() - .map(entry -> entry.getKey().map(background -> createBackground(background, entry.getValue())) + .map(entry -> entry.getKey() + .map(background -> createBackground(background, entry.getValue())) .orElseGet(() -> createTestCase(testCaseStarted, entry.getValue()))).collect(toList()); } - private Collector, List>> groupByBackground(TestCaseStarted testCaseStarted) { + private Collector, ?, Map, List>> groupByBackground(TestCaseStarted testCaseStarted) { List backgrounds = query.findFeatureBy(testCaseStarted) .map(JsonReportWriter::getBackgroundForTestCase) .orElseGet(Collections::emptyList); - Function> grouping = testStepFinished -> query.findTestStepBy(testStepFinished) - .flatMap(query::findPickleStepBy) - .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); + Function, Optional> grouping = + entry -> query.findPickleStepBy(entry.getValue()) + .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); - return groupingBy(grouping, LinkedHashMap::new, toList()); + Function>, List> extractKey = entries -> entries.stream() + .map(Entry::getKey).collect(toList()); + + return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -171,7 +177,6 @@ static List getBackgroundForTestCase(Feature feature) { } private JvmElement createBackground(Background background, List testStepsFinished) { - //String start_timestamp, Integer line, String id, JvmElementType type, String keyword, String name, String description, List steps, List before, List after, List tags return new JvmElement( null, background.getLocation().getLine(), diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 6baf0175f7..4cb9a9472b 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -324,7 +324,7 @@ void should_format_scenario_with_a_rule() throws JSONException { " \"line\": 4,\n" + " \"name\": \"Monkey eats bananas\",\n" + " \"description\": \"\",\n" + - " \"id\": \";monkey-eats-bananas\",\n" + + " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + " \"type\": \"scenario\",\n" + " \"keyword\": \"Scenario\",\n" + " \"steps\": [\n" + From 352549c97bc70e561e6e602f35920d02687b2091 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 19 Apr 2024 14:47:04 +0200 Subject: [PATCH 07/41] Some greens --- .../core/plugin/JsonReportWriter.java | 70 +++++++++++++------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index a253f8e9b4..7a68134733 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -55,7 +55,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.function.BinaryOperator; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Stream; @@ -123,19 +125,43 @@ private List writeElementsReport(Entry, List createTestCaseAndBackGround(TestCaseStarted testCaseStarted) { - return query.findTestStepFinishedAndTestStepBy(testCaseStarted) + // TODO: Clean up + Predicate, List>> isBackGround = entry -> entry.getKey().isPresent(); + Predicate, List>> isTestCase = isBackGround.negate(); + BinaryOperator, List>> mergeSteps = (a, b) -> { + a.getValue().addAll(b.getValue()); + return a; + }; + Predicate> isPickleStep = entry -> entry.getValue().getPickleStepId().isPresent(); + + Map, List> stepsByBackground = query.findTestStepFinishedAndTestStepBy(testCaseStarted) .stream() - .filter(entry -> entry.getValue().getPickleStepId().isPresent()) - .collect(groupByBackground(testCaseStarted)) - .entrySet().stream() - .map(entry -> entry.getKey() - .map(background -> createBackground(background, entry.getValue())) - .orElseGet(() -> createTestCase(testCaseStarted, entry.getValue()))).collect(toList()); + .filter(isPickleStep) + .collect(groupByBackground(testCaseStarted)); + + // There can be multiple backgrounds, but historically the json format + // only ever had one. So we group all other backgrounds steps with the + // first. + Optional background = stepsByBackground.entrySet().stream() + .filter(isBackGround) + .reduce(mergeSteps) + .flatMap(entry -> entry.getKey().map(bg -> createBackground(bg, entry.getValue()))); + + Optional testCase = stepsByBackground.entrySet().stream() + .filter(isTestCase) + .reduce(mergeSteps) + .map(Entry::getValue) + .map(testStepFinished -> createTestCase(testCaseStarted, testStepFinished)); + + return Stream.of(background, testCase) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toList()); } private Collector, ?, Map, List>> groupByBackground(TestCaseStarted testCaseStarted) { List backgrounds = query.findFeatureBy(testCaseStarted) - .map(JsonReportWriter::getBackgroundForTestCase) + .map(this::getBackgroundsBy) .orElseGet(Collections::emptyList); Function, Optional> grouping = @@ -143,7 +169,8 @@ private List createTestCaseAndBackGround(TestCaseStarted testCaseSta .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); Function>, List> extractKey = entries -> entries.stream() - .map(Entry::getKey).collect(toList()); + .map(Entry::getKey) + .collect(toList()); return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); } @@ -156,7 +183,7 @@ private static Optional findBackgroundBy(List background .findFirst(); } - static List getBackgroundForTestCase(Feature feature) { + private List getBackgroundsBy(Feature feature) { return feature.getChildren() .stream() .map(featureChild -> { @@ -227,16 +254,17 @@ private static List createTags(Pickle pickle) { private Optional createTestStep(TestStepFinished testStepFinished) { return query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findPickleStepBy(testStep) - .flatMap(query::findStepBy) - .map(step -> new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - step.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null) - )) + .flatMap(pickleStep -> query.findStepBy(pickleStep) + .map(step -> new JvmStep( + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null) + )) + ) ); } @@ -308,7 +336,7 @@ private static String renderLocationString(JavaMethod javaMethod) { private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.toNanos(), + duration.isZero() ? null : duration.toNanos(), JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), result.getException().flatMap(Exception::getStackTrace).orElse(null) ); From e9c9e9b85f103141c542434dcbacc59776807f39 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 20 Apr 2024 15:21:28 +0200 Subject: [PATCH 08/41] Some greens --- .../main/java/io/cucumber/core/plugin/JsonReportWriter.java | 1 + .../java/io/cucumber/core/backend/StubHookDefinition.java | 4 ++++ .../test/java/io/cucumber/core/plugin/JsonFormatterTest.java | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 7a68134733..44b02e6e49 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java index 67a1e4bc17..3431066e6a 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java @@ -26,6 +26,10 @@ public StubHookDefinition(String location, Consumer action) { this(location, null, action); } + public StubHookDefinition(Method location, Consumer action) { + this(location, null, action); + } + public StubHookDefinition() { this(STUBBED_LOCATION_WITH_DETAILS, null, null); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 4cb9a9472b..f7842c05e1 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -976,7 +976,7 @@ void should_handle_write_from_a_hook() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition("Hooks.before_hook_1()", + singletonList(new StubHookDefinition(beforeHook1, testCaseState -> testCaseState.log("printed from hook"))), singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) From d93b0a0d53588c5683ffa4c46ab72c7024c4e3e7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 24 May 2024 20:05:07 +0200 Subject: [PATCH 09/41] WIP --- cucumber-bom/pom.xml | 2 +- .../cucumber/core/plugin/CucumberJvmJson.java | 33 +++++++++++- .../core/plugin/JsonReportWriter.java | 52 ++++++++++++++++--- .../io/cucumber/core/runner/CachingGlue.java | 18 ++++--- .../core/plugin/HtmlFormatterTest.java | 2 +- 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index 2c8454bd9a..7d19b4bbe8 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -18,7 +18,7 @@ 28.0.0 21.3.1 0.4.0 - 24.1.0 + 24.1.1-SNAPSHOT 12.1.3-SNAPSHOT 6.1.0 0.1.0 diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java index 31fad26f15..0f1504ddeb 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -1,6 +1,7 @@ package io.cucumber.core.plugin; import java.util.List; +import java.util.Objects; import static java.util.Objects.requireNonNull; @@ -292,10 +293,12 @@ public List getCells() { static class JvmHook { private final JvmMatch match; private final JvmResult result; + private final List embeddings; - JvmHook(JvmMatch match, JvmResult result) { + JvmHook(JvmMatch match, JvmResult result, List embeddings) { this.match = requireNonNull(match); this.result = requireNonNull(result); + this.embeddings = embeddings; } public JvmMatch getMatch() { @@ -305,6 +308,34 @@ public JvmMatch getMatch() { public JvmResult getResult() { return result; } + + public List getEmbeddings() { + return embeddings; + } + } + + static class JvmEmbedding { + private final String mime_type; + private final String data; + private final String name; + + JvmEmbedding(String mime_type, String data, String name) { + this.mime_type = requireNonNull(mime_type); + this.data = requireNonNull(data); + this.name = name; + } + + public String getData() { + return data; + } + + public String getMime_type() { + return mime_type; + } + + public String getName() { + return name; + } } static class JvmTag { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 44b02e6e49..290c9b4cb0 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -14,6 +14,7 @@ import io.cucumber.core.plugin.CucumberJvmJson.JvmStep; import io.cucumber.core.plugin.CucumberJvmJson.JvmTag; import io.cucumber.messages.Convertor; +import io.cucumber.messages.types.Attachment; import io.cucumber.messages.types.Background; import io.cucumber.messages.types.DataTable; import io.cucumber.messages.types.DocString; @@ -21,6 +22,8 @@ import io.cucumber.messages.types.Feature; import io.cucumber.messages.types.GherkinDocument; import io.cucumber.messages.types.Group; +import io.cucumber.messages.types.Hook; +import io.cucumber.messages.types.HookType; import io.cucumber.messages.types.JavaMethod; import io.cucumber.messages.types.JavaStackTraceElement; import io.cucumber.messages.types.Pickle; @@ -48,9 +51,9 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -63,6 +66,8 @@ import java.util.stream.Stream; import static io.cucumber.core.plugin.TestSourcesModel.convertToId; +import static io.cucumber.messages.types.HookType.AFTER; +import static io.cucumber.messages.types.HookType.BEFORE; import static java.util.Locale.ROOT; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; @@ -133,11 +138,8 @@ private List createTestCaseAndBackGround(TestCaseStarted testCaseSta a.getValue().addAll(b.getValue()); return a; }; - Predicate> isPickleStep = entry -> entry.getValue().getPickleStepId().isPresent(); - Map, List> stepsByBackground = query.findTestStepFinishedAndTestStepBy(testCaseStarted) .stream() - .filter(isPickleStep) .collect(groupByBackground(testCaseStarted)); // There can be multiple backgrounds, but historically the json format @@ -225,6 +227,8 @@ private JvmElement createTestCase(TestCaseStarted event, List Pickle pickle = query.findPickleBy(event).orElseThrow(); Scenario scenario = query.findScenarioBy(event).orElseThrow(); NamingStrategy idStrategy = NamingStrategy.strategy(NamingStrategy.Strategy.LONG).delimiter(";").namingVisitor(new IdNamingVisitor()).build(); + List beforeHooks = createHookSteps(testStepsFinished, include(BEFORE)); + List afterHooks = createHookSteps(testStepsFinished, include(AFTER)); return new JvmElement( getDateTimeFromTimeStamp(event.getTimestamp()), query.findLocationOf(pickle).orElseThrow().getLine(), @@ -234,12 +238,39 @@ private JvmElement createTestCase(TestCaseStarted event, List pickle.getName(), scenario.getDescription() != null ? scenario.getDescription() : "", createTestSteps(testStepsFinished), - null, // TODO: Hooks - null, // TODO: Hooks + beforeHooks.isEmpty() ? null : beforeHooks, + afterHooks.isEmpty() ? null : afterHooks, pickle.getTags().isEmpty() ? null : createTags(pickle) ); } + private List createHookSteps(List testStepsFinished, Predicate predicate) { + return testStepsFinished.stream() + .map(testStepFinished -> query.findTestStepBy(testStepFinished) + .flatMap(testStep -> query.findHookBy(testStep) + .filter(predicate) + .map(hook -> new CucumberJvmJson.JvmHook( + createMatchMap(testStep, testStepFinished.getTestStepResult()), + createResultMap(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAllAttachmentsBy(testStep)) + )))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toList()); + } + + private List createEmbeddings(List attachments) { + if (attachments.isEmpty()) { + return null; + } + return attachments.stream() + .map(attachment -> new CucumberJvmJson.JvmEmbedding( + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null) + )).collect(toList()); + } + private List createTestSteps(List testStepsFinished) { return testStepsFinished.stream() .map(this::createTestStep) @@ -269,6 +300,15 @@ private Optional createTestStep(TestStepFinished testStepFinished) { ); } + private static Predicate include(HookType... hookTypes) { + List keep = Arrays.asList(hookTypes); + return hook -> hook.getType().map(keep::contains).orElse(false); + } + + private static Predicate exclude(HookType... hookTypes) { + return include(hookTypes).negate(); + } + private JvmMatch createMatchMap(TestStep step, TestStepResult result) { Optional source = query.findStepDefinitionBy(step) .stream() diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java b/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java index 5962758d5b..e3a818003e 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java @@ -28,6 +28,7 @@ import io.cucumber.datatable.TableEntryByTypeTransformer; import io.cucumber.messages.types.Envelope; import io.cucumber.messages.types.Hook; +import io.cucumber.messages.types.HookType; import io.cucumber.messages.types.JavaMethod; import io.cucumber.messages.types.JavaStackTraceElement; import io.cucumber.messages.types.Location; @@ -47,6 +48,11 @@ import java.util.Map; import java.util.TreeMap; +import static io.cucumber.messages.types.HookType.AFTER; +import static io.cucumber.messages.types.HookType.AFTER_STEP; +import static io.cucumber.messages.types.HookType.BEFORE; +import static io.cucumber.messages.types.HookType.BEFORE_STEP; + final class CachingGlue implements Glue { private static final Comparator HOOK_ORDER_ASCENDING = Comparator @@ -266,8 +272,8 @@ void prepareGlue(StepTypeRegistry stepTypeRegistry) throws DuplicateStepDefiniti // TODO: Redefine hooks for each scenario, similar to how we're doing // for CoreStepDefinition - beforeHooks.forEach(this::emitHook); - beforeStepHooks.forEach(this::emitHook); + beforeHooks.forEach(hook -> emitHook(hook, BEFORE)); + beforeStepHooks.forEach(hook -> emitHook(hook, BEFORE_STEP)); stepDefinitions.forEach(stepDefinition -> { StepExpression expression = stepExpressionFactory.createExpression(stepDefinition); @@ -281,8 +287,8 @@ void prepareGlue(StepTypeRegistry stepTypeRegistry) throws DuplicateStepDefiniti emitStepDefined(coreStepDefinition); }); - afterStepHooks.forEach(this::emitHook); - afterHooks.forEach(this::emitHook); + afterStepHooks.forEach(hook -> emitHook(hook, AFTER_STEP)); + afterHooks.forEach(hook -> emitHook(hook, AFTER)); } private void emitParameterTypeDefined(ParameterTypeDefinition parameterTypeDefinition) { @@ -299,14 +305,14 @@ private void emitParameterTypeDefined(ParameterTypeDefinition parameterTypeDefin bus.send(Envelope.of(messagesParameterType)); } - private void emitHook(CoreHookDefinition coreHook) { + private void emitHook(CoreHookDefinition coreHook, HookType type) { Hook messagesHook = new Hook( coreHook.getId().toString(), null, coreHook.getDefinitionLocation() .map(this::createSourceReference) .orElseGet(this::emptySourceReference), - coreHook.getTagExpression()); + coreHook.getTagExpression(), type); bus.send(Envelope.of(messagesHook)); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java index 1e5132b263..562e0361cd 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/HtmlFormatterTest.java @@ -64,7 +64,7 @@ void ignores_step_definitions() throws Throwable { Hook hook = new Hook("", null, SourceReference.of("https://example.com"), - null); + null, null); bus.send(Envelope.of(hook)); // public ParameterType(String name, List regularExpressions, From 89d2775073a2d6d2856c6b926b29be1f3aaa8892 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 5 Jul 2025 17:34:09 +0200 Subject: [PATCH 10/41] Fixup --- .../main/java/io/cucumber/core/plugin/IdNamingVisitor.java | 6 +++--- .../java/io/cucumber/core/plugin/JsonFormatterTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java index 8ebdef81b9..ffc53e05b8 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java @@ -35,12 +35,12 @@ public void add(Scenario scenario) { @Override public void add(Examples examples, int index) { parts.add(convertToId(examples.getName())); - parts.add(String.valueOf(index + 1)); - } + } @Override public void add(TableRow example, int index) { - parts.add(String.valueOf(index + 1)); + // json report uses base-1 indexing, and skips the first row + parts.add(String.valueOf(index + 2)); } @Override diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index ba10849c10..785087adbc 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -453,7 +453,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " ],\n" + " \"name\": \"Banana party\",\n" + " \"description\": \"\",\n" + - " \"id\": \"banana-party\",\n" + + " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + " \"keyword\": \"Feature\",\n" + " \"uri\": \"file:path/test.feature\",\n" + " \"tags\": []\n" + From c37446032a432b4ad1849aee0799b7e61fac073d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sat, 5 Jul 2025 18:34:39 +0200 Subject: [PATCH 11/41] Fix a few tests --- .../cucumber/core/plugin/CucumberJvmJson.java | 9 +++- .../core/plugin/JsonReportWriter.java | 42 +++++++++++++++--- .../core/backend/StubHookDefinition.java | 16 +------ .../core/plugin/JsonFormatterTest.java | 43 +++++++++++++------ .../core/plugin/JsonPrettyFormatterTest.json | 8 ++-- 5 files changed, 78 insertions(+), 40 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java index 623fa0f4fd..796eef30e8 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -1,7 +1,6 @@ package io.cucumber.core.plugin; import java.util.List; -import java.util.Objects; import static java.util.Objects.requireNonNull; @@ -305,11 +304,13 @@ static class JvmHook { private final JvmMatch match; private final JvmResult result; private final List embeddings; + private final List output; - JvmHook(JvmMatch match, JvmResult result, List embeddings) { + JvmHook(JvmMatch match, JvmResult result, List embeddings, List output) { this.match = requireNonNull(match); this.result = requireNonNull(result); this.embeddings = embeddings; + this.output = output; } public JvmMatch getMatch() { @@ -323,6 +324,10 @@ public JvmResult getResult() { public List getEmbeddings() { return embeddings; } + + public List getOutput() { + return output; + } } static class JvmEmbedding { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index a8f3d794a3..f159fbdbcd 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -67,6 +67,8 @@ import java.util.stream.Stream; import static io.cucumber.core.plugin.TestSourcesModel.convertToId; +import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; +import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; import static java.util.Locale.ROOT; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; @@ -263,7 +265,8 @@ private List createHookSteps( .map(hook -> new CucumberJvmJson.JvmHook( createMatchMap(testStep, testStepFinished.getTestStepResult()), createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)))))) + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -273,12 +276,33 @@ private List createEmbeddings(List att if (attachments.isEmpty()) { return null; } - return attachments.stream() + List embeddings = attachments.stream() + .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); + + if (embeddings.isEmpty()) { + return null; + } + return embeddings; + } + + private List createOutput(List attachments) { + if (attachments.isEmpty()) { + return null; + } + List outputs = attachments.stream() + .filter(attachment -> attachment.getContentEncoding() == IDENTITY) + .map(Attachment::getBody) + .collect(toList()); + + if (outputs.isEmpty()) { + return null; + } + return outputs; } private List createTestSteps(List testStepsFinished) { @@ -322,7 +346,13 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .findFirst() .map(StepDefinition::getSourceReference); - Optional location = source.flatMap(sourceReference -> { + Optional hookSource = query.findHookBy(step) + .map(Hook::getSourceReference); + + Optional location = Stream.of(source, hookSource) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst().flatMap(sourceReference -> { Optional fromUri = sourceReference.getUri() .map(uri -> renderLocationString(sourceReference, uri)); diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java index 4a864ee03f..af8cab27ba 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java @@ -20,16 +20,8 @@ public StubHookDefinition( this.hookType = hookType; } - public StubHookDefinition(String location, Consumer action) { - this(new StubLocation(location), null, action, null); - } - - public StubHookDefinition(SourceReference location, Consumer action) { - this(new StubLocation(location), null, action, null); - } - - public StubHookDefinition() { - this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), null, null, null); + public StubHookDefinition(SourceReference location, HookType hookType, Consumer action) { + this(new StubLocation(location), null, action, hookType); } public StubHookDefinition(Consumer action) { @@ -40,10 +32,6 @@ public StubHookDefinition(RuntimeException exception) { this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), exception, null, null); } - public StubHookDefinition(String location) { - this(new StubLocation(location), null, null, null); - } - public StubHookDefinition(SourceReference sourceReference, HookType hookType) { this(new StubLocation(sourceReference), null, null, hookType); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 785087adbc..bca370be7d 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -21,10 +21,12 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.lang.reflect.Method; import java.util.Scanner; import java.util.UUID; +import static io.cucumber.core.backend.HookDefinition.HookType.AFTER_STEP; +import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE; +import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE_STEP; import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.Clock.fixed; import static java.time.Duration.ofMillis; @@ -42,6 +44,9 @@ class JsonFormatterTest { final SourceReference thereAreOranges = getMethod("there_are_oranges"); final SourceReference beforeHook1 = getMethod("before_hook_1"); final SourceReference afterHook1 = getMethod("after_hook_1"); + final SourceReference beforeStepHook1 = getMethod("beforestep_hook_1"); + final SourceReference afterStepHook1 = getMethod("afterstep_hook_1"); + final SourceReference afterStepHook2 = getMethod("afterstep_hook_2"); final SourceReference monkeyEatsBananas = getMethod("monkey_eats_bananas"); final SourceReference monkeyEatsMoreBananas = getMethod("monkey_eats_more_bananas"); @@ -78,7 +83,7 @@ private Builder createRuntime(ByteArrayOutputStream out) { .withFeatureSupplier(new StubFeatureSupplier(feature)) .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition()), + singletonList(new StubHookDefinition(beforeHook1, BEFORE)), asList( new StubStepDefinition("bg_1"), new StubStepDefinition("bg_2"), @@ -431,7 +436,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 11,\n" + " \"name\": \"Monkey eats bananas\",\n" + " \"description\": \"\",\n" + - " \"id\": \";monkey-eats-bananas\",\n" + + " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + " \"type\": \"scenario\",\n" + " \"keyword\": \"Scenario\",\n" + " \"steps\": [\n" + @@ -453,7 +458,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " ],\n" + " \"name\": \"Banana party\",\n" + " \"description\": \"\",\n" + - " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + + " \"id\": \"banana-party\",\n" + " \"keyword\": \"Feature\",\n" + " \"uri\": \"file:path/test.feature\",\n" + " \"tags\": []\n" + @@ -792,7 +797,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -851,13 +856,13 @@ void should_add_step_hooks_to_step() throws JSONException { .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( emptyList(), - singletonList(new StubHookDefinition("Hooks.beforestep_hooks_1()")), + singletonList(new StubHookDefinition(beforeStepHook1, BEFORE_STEP)), asList( new StubStepDefinition("there are bananas", thereAreBananas), new StubStepDefinition("monkey arrives", monkeyArrives)), asList( - new StubHookDefinition("Hooks.afterstep_hooks_1()"), - new StubHookDefinition("Hooks.afterstep_hooks_2()")), + new StubHookDefinition(afterStepHook1, AFTER_STEP), + new StubHookDefinition(afterStepHook2, AFTER_STEP)), emptyList())) .build() .run(); @@ -993,7 +998,7 @@ void should_handle_write_from_a_hook() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, + singletonList(new StubHookDefinition(beforeHook1, BEFORE, testCaseState -> testCaseState.log("printed from hook"))), singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) @@ -1021,7 +1026,7 @@ void should_handle_write_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + " },\n" + " \"output\": [\n" + " \"printed from hook\"\n" + @@ -1070,7 +1075,8 @@ void should_handle_embed_from_a_hook() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition("Hooks.before_hook_1()", + singletonList(new StubHookDefinition(beforeHook1, + BEFORE, testCaseState -> testCaseState .attach(new byte[] { 1, 2, 3 }, "mime-type;base64", null))), singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), @@ -1099,7 +1105,7 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1151,7 +1157,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { .withAdditionalPlugins(timeService, new JsonFormatter(out)) .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition("Hooks.before_hook_1()", + singletonList(new StubHookDefinition(beforeHook1, BEFORE, testCaseState -> testCaseState.attach(new byte[] { 1, 2, 3 }, "mime-type;base64", "someEmbedding"))), singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), @@ -1180,7 +1186,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1539,6 +1545,15 @@ public void after_hook_1() { } + public void beforestep_hook_1() { + + } + public void afterstep_hook_1() { + + } + public void afterstep_hook_2() { + + } public void there_are_bananas() { } diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json index b86942ff95..f05bb93a84 100644 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json +++ b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json @@ -52,7 +52,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" } } ], @@ -159,7 +159,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" } } ], @@ -273,7 +273,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" } } ], @@ -387,7 +387,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" } } ], From 5eb1e2e14c9583c0e34ff9f63d428d8dfec2cd90 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 7 Jul 2025 16:52:45 +0200 Subject: [PATCH 12/41] WIP --- .../cucumber/core/plugin/CucumberJvmJson.java | 14 +- .../core/plugin/JsonReportWriter.java | 274 +++++++++--------- 2 files changed, 157 insertions(+), 131 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java index 796eef30e8..b0e484679b 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -161,10 +161,12 @@ static class JvmStep { private final JvmResult result; private final JvmDocString doc_string; private final List rows; + private final List before; + private final List after; JvmStep( String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, - List rows + List rows, List before, List after ) { this.keyword = requireNonNull(keyword); this.line = requireNonNull(line); @@ -173,6 +175,8 @@ static class JvmStep { this.result = requireNonNull(result); this.doc_string = doc_string; this.rows = rows; + this.before = before; + this.after = after; } public String getKeyword() { @@ -202,6 +206,14 @@ public JvmDocString getDoc_string() { public List getRows() { return rows; } + + public List getBefore() { + return before; + } + + public List getAfter() { + return after; + } } static class JvmMatch { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index f159fbdbcd..66ec24cc81 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -51,6 +51,7 @@ import java.time.Duration; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -81,6 +82,62 @@ class JsonReportWriter { this.query = query; } + private static JvmLocationTag createLocationTag(Tag tag) { + return new JvmLocationTag( + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); + } + + private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { + return backgrounds.stream() + .filter(background -> background.getSteps().stream() + .map(Step::getId) + .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) + .findFirst(); + } + + private static List createTags(Pickle pickle) { + return pickle.getTags().stream().map(pickleTag -> new JvmTag(pickleTag.getName())).collect(toList()); + } + + private static Predicate include(HookType... hookTypes) { + List keep = Arrays.asList(hookTypes); + return hook -> hook.getType().map(keep::contains).orElse(false); + } + + private static Predicate exclude(HookType... hookTypes) { + return include(hookTypes).negate(); + } + + private static String renderLocationString(SourceReference sourceReference, String uri) { + String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); + return uri + locationLine; + } + + private static String renderLocationString( + SourceReference sourceReference, JavaStackTraceElement javaStackTraceElement + ) { + String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); + String argumentList = String.join(",", javaStackTraceElement.getFileName()); + return String.format( + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); + } + + private static String renderLocationString(JavaMethod javaMethod) { + return String.format( + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); + } + List writeJsonReport() { // TODO: Replace with findAllTestCaseStartedB return query.findAllTestCaseStartedGroupedByFeature() @@ -94,32 +151,23 @@ private JvmFeature createFeatureMap(Entry, List, List> entry) { @@ -190,14 +238,6 @@ private List createTestCaseAndBackGround(TestCaseStarted testCaseSta return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); } - private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { - return backgrounds.stream() - .filter(background -> background.getSteps().stream() - .map(Step::getId) - .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) - .findFirst(); - } - private List getBackgroundsBy(Feature feature) { return feature.getChildren() .stream() @@ -220,39 +260,40 @@ private List getBackgroundsBy(Feature feature) { private JvmElement createBackground(Background background, List testStepsFinished) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(testStepsFinished), - null, - null, - null); + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(testStepsFinished), + null, + null, + null); } private JvmElement createTestCase(TestCaseStarted event, List testStepsFinished) { Pickle pickle = query.findPickleBy(event).get(); Scenario scenario = query.findLineageBy(event).flatMap(Lineage::scenario).get(); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - List beforeHooks = createHookSteps(testStepsFinished, - include(HookType.BEFORE_TEST_CASE)); - List afterHooks = createHookSteps(testStepsFinished, - include(HookType.AFTER_TEST_CASE)); + + // TODO: Push down + List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType(testStepsFinished); + List beforeHooks = createHookSteps(testStepsFinished, include(HookType.BEFORE_TEST_CASE)); + List afterHooks = createHookSteps(testStepsFinished, include(HookType.AFTER_TEST_CASE)); return new JvmElement( - getDateTimeFromTimeStamp(event.getTimestamp()), - query.findLocationOf(pickle).get().getLine(), - query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), - JvmElementType.scenario, - scenario.getKeyword(), - pickle.getName(), - scenario.getDescription() != null ? scenario.getDescription() : "", - createTestSteps(testStepsFinished), - beforeHooks.isEmpty() ? null : beforeHooks, - afterHooks.isEmpty() ? null : afterHooks, - pickle.getTags().isEmpty() ? null : createTags(pickle)); + getDateTimeFromTimeStamp(event.getTimestamp()), + query.findLocationOf(pickle).get().getLine(), + query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), + JvmElementType.scenario, + scenario.getKeyword(), + pickle.getName(), + scenario.getDescription() != null ? scenario.getDescription() : "", + createTestSteps(testStepsFinished), + beforeHooks.isEmpty() ? null : beforeHooks, + afterHooks.isEmpty() ? null : afterHooks, + pickle.getTags().isEmpty() ? null : createTags(pickle)); } private List createHookSteps( @@ -263,10 +304,10 @@ private List createHookSteps( .flatMap(testStep -> query.findHookBy(testStep) .filter(predicate) .map(hook -> new CucumberJvmJson.JvmHook( - createMatchMap(testStep, testStepFinished.getTestStepResult()), - createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createMatchMap(testStep, testStepFinished.getTestStepResult()), + createResultMap(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -289,7 +330,7 @@ private List createEmbeddings(List att } return embeddings; } - + private List createOutput(List attachments) { if (attachments.isEmpty()) { return null; @@ -298,7 +339,7 @@ private List createOutput(List attachments) { .filter(attachment -> attachment.getContentEncoding() == IDENTITY) .map(Attachment::getBody) .collect(toList()); - + if (outputs.isEmpty()) { return null; } @@ -313,8 +354,12 @@ private List createTestSteps(List testStepsFinished) .collect(toList()); } - private static List createTags(Pickle pickle) { - return pickle.getTags().stream().map(pickleTag -> new JvmTag(pickleTag.getName())).collect(toList()); + private List, TestStepFinished>> mapTestStepsFinishedToHookType(List testStepsFinished) { + return testStepsFinished.stream() + .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) + .flatMap(query::findHookBy) + .flatMap(Hook::getType), testStepFinished)) + .collect(toList()); } private Optional createTestStep(TestStepFinished testStepFinished) { @@ -322,22 +367,17 @@ private Optional createTestStep(TestStepFinished testStepFinished) { .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null))))); - } - - private static Predicate include(HookType... hookTypes) { - List keep = Arrays.asList(hookTypes); - return hook -> hook.getType().map(keep::contains).orElse(false); - } - - private static Predicate exclude(HookType... hookTypes) { - return include(hookTypes).negate(); + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null), + // TODO: You are here + null, // createHookSteps(testStepsFinished, include(HookType.BEFORE_TEST_STEP)), + null // createHookSteps(testStepsFinished, include(HookType.AFTER_TEST_STEP)) + )))); } private JvmMatch createMatchMap(TestStep step, TestStepResult result) { @@ -353,18 +393,18 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .filter(Optional::isPresent) .map(Optional::get) .findFirst().flatMap(sourceReference -> { - Optional fromUri = sourceReference.getUri() - .map(uri -> renderLocationString(sourceReference, uri)); + Optional fromUri = sourceReference.getUri() + .map(uri -> renderLocationString(sourceReference, uri)); - Optional fromJavaMethod = sourceReference.getJavaMethod() - .map(JsonReportWriter::renderLocationString); + Optional fromJavaMethod = sourceReference.getJavaMethod() + .map(JsonReportWriter::renderLocationString); - Optional fromStackTrace = sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)); + Optional fromStackTrace = sourceReference.getJavaStackTraceElement() + .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)); - return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent).map(Optional::get) - .findFirst(); - }); + return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent).map(Optional::get) + .findFirst(); + }); Optional> argumentList = step.getStepMatchArgumentsLists() .map(argumentsLists -> argumentsLists.stream() @@ -373,56 +413,30 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); }).collect(toList())) .filter(maps -> !maps.isEmpty()); return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); - } - - private static String renderLocationString(SourceReference sourceReference, String uri) { - String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); - return uri + locationLine; - } - - private static String renderLocationString( - SourceReference sourceReference, JavaStackTraceElement javaStackTraceElement - ) { - String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); - String argumentList = String.join(",", javaStackTraceElement.getFileName()); - return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); - } - - private static String renderLocationString(JavaMethod javaMethod) { - return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null)); } private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private JvmDocString createDocStringMap(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } private List createDataTableList(DataTable argument) { From f57370197acf0ed78d373ee63b21508dc0c51872 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 13 Jul 2025 20:32:14 +0200 Subject: [PATCH 13/41] Fixed hooks --- .../core/plugin/JsonReportWriter.java | 96 ++++++++++++++----- .../core/plugin/JsonFormatterTest.java | 16 ++-- 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 50ed0022fd..cee1b60c45 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -152,18 +152,13 @@ private JvmFeature createFeatureMap(Entry, List Pickle pickle = query.findPickleBy(event).get(); Scenario scenario = query.findLineageBy(event).flatMap(Lineage::scenario).get(); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - - // TODO: Push down - List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType(testStepsFinished); List beforeHooks = createHookSteps(testStepsFinished, include(HookType.BEFORE_TEST_CASE)); List afterHooks = createHookSteps(testStepsFinished, include(HookType.AFTER_TEST_CASE)); return new JvmElement( @@ -347,13 +339,64 @@ private List createOutput(List attachments) { } private List createTestSteps(List testStepsFinished) { - return testStepsFinished.stream() - .map(this::createTestStep) + List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType(testStepsFinished); + return testStepsFinishedWithHookType + .stream() + .filter(testStepFinished -> !testStepFinished.getKey().isPresent()) + .map(Entry::getValue) + .map(testStepFinished1 -> { + List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, testStepFinished1, HookType.BEFORE_TEST_STEP); + List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, testStepFinished1, HookType.AFTER_TEST_STEP); + return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); + }) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); } + private List findHooksFor(List, TestStepFinished>> withHookType, TestStepFinished testStepFinished, HookType hookType) { + int stepIndex = findIndexOf(withHookType, testStepFinished); + if (stepIndex < 0) { + return Collections.emptyList(); + } + if (hookType == HookType.BEFORE_TEST_STEP) { + List beforeStepHooks = new ArrayList<>(); + for (int hookIndex = stepIndex - 1; hookIndex >= 0; hookIndex--) { + Entry, TestStepFinished> candidate = withHookType.get(hookIndex); + boolean isBeforeStepHook = candidate.getKey().map(hookType1 -> hookType1 == hookType).orElse(false); + if (!isBeforeStepHook) { + break; + } + beforeStepHooks.add(candidate.getValue()); + } + return beforeStepHooks; + } else if (hookType == HookType.AFTER_TEST_STEP) { + List afterStepHooks = new ArrayList<>(); + for (int hookIndex = stepIndex + 1; hookIndex < withHookType.size(); hookIndex++) { + Entry, TestStepFinished> candidate = withHookType.get(hookIndex); + boolean isAfterStepHook = candidate.getKey().map(hookType1 -> hookType1 == hookType).orElse(false); + if (!isAfterStepHook) { + break; + } + afterStepHooks.add(candidate.getValue()); + } + return afterStepHooks; + } + + return Collections.emptyList(); + } + + private static int findIndexOf(List, TestStepFinished>> withHookType, TestStepFinished testStepFinished) { + for (int i = 0; i < withHookType.size(); i++) { + TestStepFinished candidate = withHookType.get(i).getValue(); + if (testStepFinished.equals(candidate)) { + return i; + } + + } + return -1; + } + private List, TestStepFinished>> mapTestStepsFinishedToHookType(List testStepsFinished) { return testStepsFinished.stream() .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) @@ -362,22 +405,25 @@ private List, TestStepFinished>> mapTestStepsFinishedTo .collect(toList()); } - private Optional createTestStep(TestStepFinished testStepFinished) { + private Optional createTestStep(TestStepFinished testStepFinished, List beforeStepHooks, List afterStepHooks) { return query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) - .map(step -> new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null), - // TODO: You are here - null, // createHookSteps(testStepsFinished, include(HookType.BEFORE_TEST_STEP)), - null // createHookSteps(testStepsFinished, include(HookType.AFTER_TEST_STEP)) - )))); + .map(step -> { + List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, include(HookType.BEFORE_TEST_STEP)); + List jvmAfterStepHooks = createHookSteps(afterStepHooks, include(HookType.AFTER_TEST_STEP)); + return new JvmStep( + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null), + jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, + jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks + ); + }))); } private JvmMatch createMatchMap(TestStep step, TestStepResult result) { diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index bca370be7d..0077eb0448 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -772,7 +772,7 @@ void should_format_scenario_with_hooks() throws JSONException { .withBackendSupplier(new StubBackendSupplier( singletonList(new StubHookDefinition(beforeHook1, HookDefinition.HookType.BEFORE)), singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.BEFORE)))) + singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.AFTER)))) .build() .run(); @@ -823,7 +823,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"after\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.after_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#after_hook_1()\"\n" + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -893,7 +893,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.beforestep_hooks_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + " }\n" + " }\n" + " ],\n" + @@ -910,7 +910,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_2()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + " }\n" + " },\n" + " {\n" + @@ -919,7 +919,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + " }\n" + " }\n" + " ],\n" + @@ -937,7 +937,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.beforestep_hooks_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + " }\n" + " }\n" + " ],\n" + @@ -954,7 +954,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_2()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + " }\n" + " },\n" + " {\n" + @@ -963,7 +963,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"Hooks.afterstep_hooks_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + " }\n" + " }\n" + " ],\n" + From 465f9d6f15bccc76af45c375504a40acbd186272 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 13 Jul 2025 21:14:36 +0200 Subject: [PATCH 14/41] Add methods --- .../cucumber/core/plugin/CucumberJvmJson.java | 2 +- .../cucumber/core/plugin/JsonReportData.java | 13 ------ .../core/plugin/JsonFormatterTest.java | 41 +++++++++++++------ 3 files changed, 29 insertions(+), 27 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java index b0e484679b..4fef8e2f81 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -22,9 +22,9 @@ enum JvmStatus { } static class JvmFeature { + private final Long line; private final String uri; private final String id; - private final Long line; private final String keyword; private final String name; private final String description; diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java deleted file mode 100644 index de71b21cae..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportData.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Envelope; -import io.cucumber.query.Query; - -class JsonReportData { - - private final Query query = new Query(); - - void collect(Envelope envelope) { - query.update(envelope); - } -} diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 0077eb0448..9c96910420 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -85,19 +85,19 @@ private Builder createRuntime(ByteArrayOutputStream out) { .withBackendSupplier(new StubBackendSupplier( singletonList(new StubHookDefinition(beforeHook1, BEFORE)), asList( - new StubStepDefinition("bg_1"), - new StubStepDefinition("bg_2"), - new StubStepDefinition("bg_3"), - new StubStepDefinition("step_1"), - new StubStepDefinition("step_2"), - new StubStepDefinition("step_3"), - new StubStepDefinition("cliché"), - new StubStepDefinition("so_1 {int}", Integer.class), - new StubStepDefinition("so_2 {int} cucumbers", Integer.class), - new StubStepDefinition("{int} so_3", Integer.class), - new StubStepDefinition("a"), - new StubStepDefinition("b"), - new StubStepDefinition("c")), + new StubStepDefinition("bg_1", getMethod("bg_1")), + new StubStepDefinition("bg_2", getMethod("bg_2")), + new StubStepDefinition("bg_3", getMethod("bg_3")), + new StubStepDefinition("step_1", getMethod("step_1")), + new StubStepDefinition("step_2", getMethod("step_2")), + new StubStepDefinition("step_3", getMethod("step_3")), + new StubStepDefinition("cliché", getMethod("cliche")), + new StubStepDefinition("so_1 {int}", getMethod("so_1"), Integer.class), + new StubStepDefinition("so_2 {int} cucumbers", getMethod("so_2"), Integer.class), + new StubStepDefinition("{int} so_3", getMethod("so_3"), Integer.class), + new StubStepDefinition("a", getMethod("a")), + new StubStepDefinition("b", getMethod("b")), + new StubStepDefinition("c", getMethod("c"))), emptyList())) .withAdditionalPlugins(new JsonFormatter(out)); } @@ -1537,6 +1537,21 @@ void should_handle_several_features() throws JSONException { } static class StepDefs { + + public void bg_1() {} + public void bg_2() {} + public void bg_3() {} + public void step_1() {} + public void step_2() {} + public void step_3() {} + public void cliche() {} + public void so_1 () {} + public void so_2 () {} + public void so_3 () {} + public void a() {} + public void b() {} + public void c() {} + public void before_hook_1() { } From 3fbce35238d0b1fd31784fab1483c99120569176 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 13 Jul 2025 21:16:01 +0200 Subject: [PATCH 15/41] Spotless --- .../cucumber/core/plugin/IdNamingVisitor.java | 2 +- .../core/plugin/JsonReportWriter.java | 198 ++++++++++-------- .../core/backend/StubStepDefinition.java | 1 + .../core/plugin/JsonFormatterTest.java | 74 +++++-- 4 files changed, 164 insertions(+), 111 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java index ffc53e05b8..db7930d9b1 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java @@ -35,7 +35,7 @@ public void add(Scenario scenario) { @Override public void add(Examples examples, int index) { parts.add(convertToId(examples.getName())); - } + } @Override public void add(TableRow example, int index) { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index cee1b60c45..840b60ee33 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -84,11 +84,11 @@ class JsonReportWriter { private static JvmLocationTag createLocationTag(Tag tag) { return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -123,19 +123,19 @@ private static String renderLocationString( String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); String argumentList = String.join(",", javaStackTraceElement.getFileName()); return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); } private static String renderLocationString(JavaMethod javaMethod) { return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); } List writeJsonReport() { @@ -151,18 +151,18 @@ private JvmFeature createFeatureMap(Entry, List, List> entry) { @@ -255,37 +255,39 @@ private List getBackgroundsBy(Feature feature) { private JvmElement createBackground(Background background, List testStepsFinished) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(testStepsFinished), - null, - null, - null); + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(testStepsFinished), + null, + null, + null); } private JvmElement createTestCase(TestCaseStarted event, List testStepsFinished) { Pickle pickle = query.findPickleBy(event).get(); Scenario scenario = query.findLineageBy(event).flatMap(Lineage::scenario).get(); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - List beforeHooks = createHookSteps(testStepsFinished, include(HookType.BEFORE_TEST_CASE)); - List afterHooks = createHookSteps(testStepsFinished, include(HookType.AFTER_TEST_CASE)); + List beforeHooks = createHookSteps(testStepsFinished, + include(HookType.BEFORE_TEST_CASE)); + List afterHooks = createHookSteps(testStepsFinished, + include(HookType.AFTER_TEST_CASE)); return new JvmElement( - getDateTimeFromTimeStamp(event.getTimestamp()), - query.findLocationOf(pickle).get().getLine(), - query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), - JvmElementType.scenario, - scenario.getKeyword(), - pickle.getName(), - scenario.getDescription() != null ? scenario.getDescription() : "", - createTestSteps(testStepsFinished), - beforeHooks.isEmpty() ? null : beforeHooks, - afterHooks.isEmpty() ? null : afterHooks, - pickle.getTags().isEmpty() ? null : createTags(pickle)); + getDateTimeFromTimeStamp(event.getTimestamp()), + query.findLocationOf(pickle).get().getLine(), + query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), + JvmElementType.scenario, + scenario.getKeyword(), + pickle.getName(), + scenario.getDescription() != null ? scenario.getDescription() : "", + createTestSteps(testStepsFinished), + beforeHooks.isEmpty() ? null : beforeHooks, + afterHooks.isEmpty() ? null : afterHooks, + pickle.getTags().isEmpty() ? null : createTags(pickle)); } private List createHookSteps( @@ -296,10 +298,10 @@ private List createHookSteps( .flatMap(testStep -> query.findHookBy(testStep) .filter(predicate) .map(hook -> new CucumberJvmJson.JvmHook( - createMatchMap(testStep, testStepFinished.getTestStepResult()), - createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createMatchMap(testStep, testStepFinished.getTestStepResult()), + createResultMap(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -312,9 +314,9 @@ private List createEmbeddings(List att List embeddings = attachments.stream() .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); if (embeddings.isEmpty()) { @@ -339,14 +341,17 @@ private List createOutput(List attachments) { } private List createTestSteps(List testStepsFinished) { - List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType(testStepsFinished); + List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType( + testStepsFinished); return testStepsFinishedWithHookType .stream() .filter(testStepFinished -> !testStepFinished.getKey().isPresent()) .map(Entry::getValue) .map(testStepFinished1 -> { - List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, testStepFinished1, HookType.BEFORE_TEST_STEP); - List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, testStepFinished1, HookType.AFTER_TEST_STEP); + List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, + testStepFinished1, HookType.BEFORE_TEST_STEP); + List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, + testStepFinished1, HookType.AFTER_TEST_STEP); return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); }) .filter(Optional::isPresent) @@ -354,7 +359,10 @@ private List createTestSteps(List testStepsFinished) .collect(toList()); } - private List findHooksFor(List, TestStepFinished>> withHookType, TestStepFinished testStepFinished, HookType hookType) { + private List findHooksFor( + List, TestStepFinished>> withHookType, TestStepFinished testStepFinished, + HookType hookType + ) { int stepIndex = findIndexOf(withHookType, testStepFinished); if (stepIndex < 0) { return Collections.emptyList(); @@ -382,11 +390,13 @@ private List findHooksFor(List, TestS } return afterStepHooks; } - + return Collections.emptyList(); } - private static int findIndexOf(List, TestStepFinished>> withHookType, TestStepFinished testStepFinished) { + private static int findIndexOf( + List, TestStepFinished>> withHookType, TestStepFinished testStepFinished + ) { for (int i = 0; i < withHookType.size(); i++) { TestStepFinished candidate = withHookType.get(i).getValue(); if (testStepFinished.equals(candidate)) { @@ -397,32 +407,39 @@ private static int findIndexOf(List, TestStepFinished>> return -1; } - private List, TestStepFinished>> mapTestStepsFinishedToHookType(List testStepsFinished) { + private List, TestStepFinished>> mapTestStepsFinishedToHookType( + List testStepsFinished + ) { return testStepsFinished.stream() .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) .flatMap(query::findHookBy) - .flatMap(Hook::getType), testStepFinished)) + .flatMap(Hook::getType), + testStepFinished)) .collect(toList()); } - private Optional createTestStep(TestStepFinished testStepFinished, List beforeStepHooks, List afterStepHooks) { + private Optional createTestStep( + TestStepFinished testStepFinished, List beforeStepHooks, + List afterStepHooks + ) { return query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { - List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, include(HookType.BEFORE_TEST_STEP)); - List jvmAfterStepHooks = createHookSteps(afterStepHooks, include(HookType.AFTER_TEST_STEP)); + List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, + include(HookType.BEFORE_TEST_STEP)); + List jvmAfterStepHooks = createHookSteps(afterStepHooks, + include(HookType.AFTER_TEST_STEP)); return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null), - jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, - jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks - ); + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null), + jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, + jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); }))); } @@ -446,7 +463,8 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { Optional fromStackTrace = sourceReference.getJavaStackTraceElement() .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)); - return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent).map(Optional::get) + return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent) + .map(Optional::get) .findFirst(); }); @@ -457,30 +475,30 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); }).collect(toList())) .filter(maps -> !maps.isEmpty()); return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null)); } private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private JvmDocString createDocStringMap(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } private List createDataTableList(DataTable argument) { diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java index 7cbb88661e..afa3830dde 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java @@ -36,6 +36,7 @@ public StubStepDefinition(String pattern, Throwable exception, Type... types) { public StubStepDefinition(String pattern, SourceReference location, Throwable exception, Type... types) { this(pattern, new StubLocation(location), exception, types); } + private StubStepDefinition(String pattern, StubLocation location, Throwable exception, Type... types) { this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList()); this.expression = pattern; diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 9c96910420..0ea56f4e09 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -893,7 +893,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -910,7 +911,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + + " }\n" + " },\n" + " {\n" + @@ -919,7 +921,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -937,7 +940,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -954,7 +958,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + + " }\n" + " },\n" + " {\n" + @@ -963,7 +968,8 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -1537,20 +1543,45 @@ void should_handle_several_features() throws JSONException { } static class StepDefs { - - public void bg_1() {} - public void bg_2() {} - public void bg_3() {} - public void step_1() {} - public void step_2() {} - public void step_3() {} - public void cliche() {} - public void so_1 () {} - public void so_2 () {} - public void so_3 () {} - public void a() {} - public void b() {} - public void c() {} + + public void bg_1() { + } + + public void bg_2() { + } + + public void bg_3() { + } + + public void step_1() { + } + + public void step_2() { + } + + public void step_3() { + } + + public void cliche() { + } + + public void so_1() { + } + + public void so_2() { + } + + public void so_3() { + } + + public void a() { + } + + public void b() { + } + + public void c() { + } public void before_hook_1() { @@ -1563,12 +1594,15 @@ public void after_hook_1() { public void beforestep_hook_1() { } + public void afterstep_hook_1() { } + public void afterstep_hook_2() { } + public void there_are_bananas() { } From bb6c6fc697c593f548fb6d2f24ef37c5f7289b9d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Jul 2025 00:42:23 +0200 Subject: [PATCH 16/41] Fix ordering --- .../core/plugin/JsonReportWriter.java | 250 ++++++++++-------- 1 file changed, 134 insertions(+), 116 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 840b60ee33..4b9ed1f561 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -26,6 +26,7 @@ import io.cucumber.messages.types.HookType; import io.cucumber.messages.types.JavaMethod; import io.cucumber.messages.types.JavaStackTraceElement; +import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.PickleStep; import io.cucumber.messages.types.Rule; @@ -56,6 +57,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -84,11 +86,11 @@ class JsonReportWriter { private static JvmLocationTag createLocationTag(Tag tag) { return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -123,64 +125,79 @@ private static String renderLocationString( String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); String argumentList = String.join(",", javaStackTraceElement.getFileName()); return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); } private static String renderLocationString(JavaMethod javaMethod) { return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); + } + + private static int findIndexOf( + List, TestStepFinished>> withHookType, TestStepFinished testStepFinished + ) { + for (int i = 0; i < withHookType.size(); i++) { + TestStepFinished candidate = withHookType.get(i).getValue(); + if (testStepFinished.equals(candidate)) { + return i; + } + + } + return -1; } List writeJsonReport() { - // TODO: Replace with findAllTestCaseStartedB - return query.findAllTestCaseStartedGroupedByFeature() - .entrySet() + return query.findAllTestCaseStarted() + .stream() + .map(testCaseStarted -> query + .findPickleBy(testCaseStarted) + .flatMap(pickle -> query + .findLineageBy(pickle) + .flatMap(lineage -> query.findLocationOf(pickle) + .map(location -> new Data(testCaseStarted, lineage, pickle, location))))) + .filter(Optional::isPresent) + .map(Optional::get) + .sorted(Comparator.comparing((Data data) -> data.pickle.getUri()).thenComparing(data -> data.location.getLine())) + .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) + .values() .stream() .map(this::createFeatureMap) .collect(toList()); } - private JvmFeature createFeatureMap(Entry, List> entry) { - GherkinDocument document = getGherkinDocument(entry); - Feature feature = entry.getKey().get(); + private JvmFeature createFeatureMap(List entries) { + GherkinDocument document = entries.get(0).lineage.document(); + Feature feature = entries.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); return new JvmFeature( - TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), // TODO: - // Relativize, optional?, null? - convertToId(feature.getName()), - feature.getLocation().getLine(), - feature.getKeyword(), - feature.getName(), - feature.getDescription() != null ? feature.getDescription() : "", // TODO: - // Can this be null? - writeElementsReport(entry), - feature.getTags().stream() - .map(JsonReportWriter::createLocationTag) - .collect(toList())); - } - - private GherkinDocument getGherkinDocument(Entry, List> entry) { - return entry.getValue().stream() - .findFirst() - .flatMap(query::findLineageBy) - .map(Lineage::document) - .orElseThrow(() -> new IllegalArgumentException("No Gherkin document")); + TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), // TODO: + // Relativize, optional?, null? + convertToId(feature.getName()), + feature.getLocation().getLine(), + feature.getKeyword(), + feature.getName(), + feature.getDescription() != null ? feature.getDescription() : "", // TODO: + // Can this be null? + writeElementsReport(entries), + feature.getTags().stream() + .map(JsonReportWriter::createLocationTag) + .collect(toList())); } - private List writeElementsReport(Entry, List> entry) { - return entry.getValue().stream() + private List writeElementsReport(List entries) { + return entries.stream() .map(this::createTestCaseAndBackGround) .flatMap(Collection::stream) .collect(toList()); } - private List createTestCaseAndBackGround(TestCaseStarted testCaseStarted) { + private List createTestCaseAndBackGround(Data data) { // TODO: Clean up Predicate, List>> isBackGround = entry -> entry.getKey() .isPresent(); @@ -190,9 +207,9 @@ private List createTestCaseAndBackGround(TestCaseStarted testCaseSta return a; }; Map, List> stepsByBackground = query - .findTestStepFinishedAndTestStepBy(testCaseStarted) + .findTestStepFinishedAndTestStepBy(data.testCaseStarted) .stream() - .collect(groupByBackground(testCaseStarted)); + .collect(groupByBackground(data.testCaseStarted)); // There can be multiple backgrounds, but historically the json format // only ever had one. So we group all other backgrounds steps with the @@ -206,7 +223,7 @@ private List createTestCaseAndBackGround(TestCaseStarted testCaseSta .filter(isTestCase) .reduce(mergeSteps) .map(Entry::getValue) - .map(testStepFinished -> createTestCase(testCaseStarted, testStepFinished)); + .map(testStepFinished -> createTestCase(data.testCaseStarted, testStepFinished)); return Stream.of(background, testCase) .filter(Optional::isPresent) @@ -255,17 +272,17 @@ private List getBackgroundsBy(Feature feature) { private JvmElement createBackground(Background background, List testStepsFinished) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(testStepsFinished), - null, - null, - null); + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(testStepsFinished), + null, + null, + null); } private JvmElement createTestCase(TestCaseStarted event, List testStepsFinished) { @@ -273,21 +290,21 @@ private JvmElement createTestCase(TestCaseStarted event, List Scenario scenario = query.findLineageBy(event).flatMap(Lineage::scenario).get(); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); List beforeHooks = createHookSteps(testStepsFinished, - include(HookType.BEFORE_TEST_CASE)); + include(HookType.BEFORE_TEST_CASE)); List afterHooks = createHookSteps(testStepsFinished, - include(HookType.AFTER_TEST_CASE)); + include(HookType.AFTER_TEST_CASE)); return new JvmElement( - getDateTimeFromTimeStamp(event.getTimestamp()), - query.findLocationOf(pickle).get().getLine(), - query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), - JvmElementType.scenario, - scenario.getKeyword(), - pickle.getName(), - scenario.getDescription() != null ? scenario.getDescription() : "", - createTestSteps(testStepsFinished), - beforeHooks.isEmpty() ? null : beforeHooks, - afterHooks.isEmpty() ? null : afterHooks, - pickle.getTags().isEmpty() ? null : createTags(pickle)); + getDateTimeFromTimeStamp(event.getTimestamp()), + query.findLocationOf(pickle).get().getLine(), + query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), + JvmElementType.scenario, + scenario.getKeyword(), + pickle.getName(), + scenario.getDescription() != null ? scenario.getDescription() : "", + createTestSteps(testStepsFinished), + beforeHooks.isEmpty() ? null : beforeHooks, + afterHooks.isEmpty() ? null : afterHooks, + pickle.getTags().isEmpty() ? null : createTags(pickle)); } private List createHookSteps( @@ -298,10 +315,10 @@ private List createHookSteps( .flatMap(testStep -> query.findHookBy(testStep) .filter(predicate) .map(hook -> new CucumberJvmJson.JvmHook( - createMatchMap(testStep, testStepFinished.getTestStepResult()), - createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createMatchMap(testStep, testStepFinished.getTestStepResult()), + createResultMap(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -314,9 +331,9 @@ private List createEmbeddings(List att List embeddings = attachments.stream() .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); if (embeddings.isEmpty()) { @@ -342,16 +359,16 @@ private List createOutput(List attachments) { private List createTestSteps(List testStepsFinished) { List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType( - testStepsFinished); + testStepsFinished); return testStepsFinishedWithHookType .stream() .filter(testStepFinished -> !testStepFinished.getKey().isPresent()) .map(Entry::getValue) .map(testStepFinished1 -> { List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.BEFORE_TEST_STEP); + testStepFinished1, HookType.BEFORE_TEST_STEP); List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.AFTER_TEST_STEP); + testStepFinished1, HookType.AFTER_TEST_STEP); return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); }) .filter(Optional::isPresent) @@ -394,19 +411,6 @@ private List findHooksFor( return Collections.emptyList(); } - private static int findIndexOf( - List, TestStepFinished>> withHookType, TestStepFinished testStepFinished - ) { - for (int i = 0; i < withHookType.size(); i++) { - TestStepFinished candidate = withHookType.get(i).getValue(); - if (testStepFinished.equals(candidate)) { - return i; - } - - } - return -1; - } - private List, TestStepFinished>> mapTestStepsFinishedToHookType( List testStepsFinished ) { @@ -414,7 +418,7 @@ private List, TestStepFinished>> mapTestStepsFinishedTo .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) .flatMap(query::findHookBy) .flatMap(Hook::getType), - testStepFinished)) + testStepFinished)) .collect(toList()); } @@ -427,19 +431,19 @@ private Optional createTestStep( .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, - include(HookType.BEFORE_TEST_STEP)); + include(HookType.BEFORE_TEST_STEP)); List jvmAfterStepHooks = createHookSteps(afterStepHooks, - include(HookType.AFTER_TEST_STEP)); + include(HookType.AFTER_TEST_STEP)); return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null), - jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, - jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null), + jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, + jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); }))); } @@ -475,30 +479,30 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); }).collect(toList())) .filter(maps -> !maps.isEmpty()); return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null)); } private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private JvmDocString createDocStringMap(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } private List createDataTableList(DataTable argument) { @@ -515,4 +519,18 @@ private String getDateTimeFromTimeStamp(Timestamp instant) { return formatter.format(Convertor.toInstant(instant)); } + private static class Data { + private final TestCaseStarted testCaseStarted; + private final Lineage lineage; + private final Pickle pickle; + private final Location location; + + private Data(TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location) { + this.testCaseStarted = testCaseStarted; + this.lineage = lineage; + this.pickle = pickle; + this.location = location; + } + } + } From 280622eaf8e4cabc879ee5f6697ef5b296a209a0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Jul 2025 00:48:38 +0200 Subject: [PATCH 17/41] Fix methods --- .../core/plugin/JsonPrettyFormatterTest.json | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json index f05bb93a84..042a0e6631 100644 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json +++ b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json @@ -16,7 +16,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" }, "keyword": "Given " }, @@ -27,7 +27,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" }, "keyword": "When " }, @@ -38,7 +38,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" }, "keyword": "Then " } @@ -70,7 +70,7 @@ "line": 9, "name": "step_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_1()" }, "keyword": "Given " }, @@ -81,7 +81,7 @@ "line": 10, "name": "step_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_2()" }, "keyword": "When " }, @@ -92,7 +92,7 @@ "line": 11, "name": "step_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_3()" }, "keyword": "Then " }, @@ -103,7 +103,7 @@ "line": 12, "name": "cliché", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#cliche()" }, "keyword": "Then " } @@ -123,7 +123,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" }, "keyword": "Given " }, @@ -134,7 +134,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" }, "keyword": "When " }, @@ -145,7 +145,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" }, "keyword": "Then " } @@ -183,7 +183,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_1()" }, "keyword": "Given " }, @@ -200,7 +200,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_2()" }, "keyword": "When " }, @@ -217,7 +217,7 @@ "offset": 0 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_3()" }, "keyword": "Then " } @@ -237,7 +237,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" }, "keyword": "Given " }, @@ -248,7 +248,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" }, "keyword": "When " }, @@ -259,7 +259,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" }, "keyword": "Then " } @@ -297,7 +297,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_1()" }, "keyword": "Given " }, @@ -314,7 +314,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_2()" }, "keyword": "When " }, @@ -331,7 +331,7 @@ "offset": 0 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_3()" }, "keyword": "Then " } @@ -351,7 +351,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" }, "keyword": "Given " }, @@ -362,7 +362,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" }, "keyword": "When " }, @@ -373,7 +373,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" }, "keyword": "Then " } @@ -405,7 +405,7 @@ "line": 25, "name": "a", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#a()" }, "keyword": "Given " }, @@ -416,7 +416,7 @@ "line": 26, "name": "b", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#b()" }, "keyword": "Then " }, @@ -427,7 +427,7 @@ "line": 27, "name": "c", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#c()" }, "keyword": "When " } From 67ecaeeede73776e4cb20923fb8aee310db40343 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Jul 2025 00:58:43 +0200 Subject: [PATCH 18/41] Spotless --- .../core/plugin/JsonReportWriter.java | 221 +++++++++--------- 1 file changed, 116 insertions(+), 105 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 4b9ed1f561..e983d02676 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -86,11 +86,11 @@ class JsonReportWriter { private static JvmLocationTag createLocationTag(Tag tag) { return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -125,19 +125,19 @@ private static String renderLocationString( String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); String argumentList = String.join(",", javaStackTraceElement.getFileName()); return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); } private static String renderLocationString(JavaMethod javaMethod) { return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); } private static int findIndexOf( @@ -156,15 +156,11 @@ private static int findIndexOf( List writeJsonReport() { return query.findAllTestCaseStarted() .stream() - .map(testCaseStarted -> query - .findPickleBy(testCaseStarted) - .flatMap(pickle -> query - .findLineageBy(pickle) - .flatMap(lineage -> query.findLocationOf(pickle) - .map(location -> new Data(testCaseStarted, lineage, pickle, location))))) + .map(this::createTestCaseStartedData) .filter(Optional::isPresent) .map(Optional::get) - .sorted(Comparator.comparing((Data data) -> data.pickle.getUri()).thenComparing(data -> data.location.getLine())) + .sorted(Comparator.comparing((TestCaseData data) -> data.pickle.getUri()) + .thenComparing(data -> data.location.getLine())) .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) .values() .stream() @@ -172,32 +168,44 @@ List writeJsonReport() { .collect(toList()); } - private JvmFeature createFeatureMap(List entries) { + private Optional createTestCaseStartedData(TestCaseStarted testCaseStarted) { + return query.findPickleBy(testCaseStarted) + .flatMap(pickle -> query.findLineageBy(pickle) + .flatMap(lineage -> query.findLocationOf(pickle) + .map(location -> { + List> testStepFinishedAndTestStep = query + .findTestStepFinishedAndTestStepBy(testCaseStarted); + return new TestCaseData(testCaseStarted, lineage, pickle, location, + testStepFinishedAndTestStep); + }))); + } + + private JvmFeature createFeatureMap(List entries) { GherkinDocument document = entries.get(0).lineage.document(); Feature feature = entries.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); return new JvmFeature( - TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), // TODO: - // Relativize, optional?, null? - convertToId(feature.getName()), - feature.getLocation().getLine(), - feature.getKeyword(), - feature.getName(), - feature.getDescription() != null ? feature.getDescription() : "", // TODO: - // Can this be null? - writeElementsReport(entries), - feature.getTags().stream() - .map(JsonReportWriter::createLocationTag) - .collect(toList())); - } - - private List writeElementsReport(List entries) { + // TODO: Relativize, optional?, null? + TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), + convertToId(feature.getName()), + feature.getLocation().getLine(), + feature.getKeyword(), + feature.getName(), + // TODO: Can this be null? + feature.getDescription() != null ? feature.getDescription() : "", + writeElementsReport(entries), + feature.getTags().stream() + .map(JsonReportWriter::createLocationTag) + .collect(toList())); + } + + private List writeElementsReport(List entries) { return entries.stream() .map(this::createTestCaseAndBackGround) .flatMap(Collection::stream) .collect(toList()); } - private List createTestCaseAndBackGround(Data data) { + private List createTestCaseAndBackGround(TestCaseData data) { // TODO: Clean up Predicate, List>> isBackGround = entry -> entry.getKey() .isPresent(); @@ -206,8 +214,7 @@ private List createTestCaseAndBackGround(Data data) { a.getValue().addAll(b.getValue()); return a; }; - Map, List> stepsByBackground = query - .findTestStepFinishedAndTestStepBy(data.testCaseStarted) + Map, List> stepsByBackground = data.testStepFinishedAndTestStep .stream() .collect(groupByBackground(data.testCaseStarted)); @@ -223,7 +230,7 @@ private List createTestCaseAndBackGround(Data data) { .filter(isTestCase) .reduce(mergeSteps) .map(Entry::getValue) - .map(testStepFinished -> createTestCase(data.testCaseStarted, testStepFinished)); + .map(testStepFinished -> createTestCase(data, testStepFinished)); return Stream.of(background, testCase) .filter(Optional::isPresent) @@ -272,39 +279,38 @@ private List getBackgroundsBy(Feature feature) { private JvmElement createBackground(Background background, List testStepsFinished) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(testStepsFinished), - null, - null, - null); - } - - private JvmElement createTestCase(TestCaseStarted event, List testStepsFinished) { - Pickle pickle = query.findPickleBy(event).get(); - Scenario scenario = query.findLineageBy(event).flatMap(Lineage::scenario).get(); + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(testStepsFinished), + null, + null, + null); + } + + private JvmElement createTestCase(TestCaseData data, List testStepsFinished) { + Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); List beforeHooks = createHookSteps(testStepsFinished, - include(HookType.BEFORE_TEST_CASE)); + include(HookType.BEFORE_TEST_CASE)); List afterHooks = createHookSteps(testStepsFinished, - include(HookType.AFTER_TEST_CASE)); + include(HookType.AFTER_TEST_CASE)); return new JvmElement( - getDateTimeFromTimeStamp(event.getTimestamp()), - query.findLocationOf(pickle).get().getLine(), - query.findLineageBy(pickle).map(idStrategy::reduce).orElse(convertToId(pickle.getName())), - JvmElementType.scenario, - scenario.getKeyword(), - pickle.getName(), - scenario.getDescription() != null ? scenario.getDescription() : "", - createTestSteps(testStepsFinished), - beforeHooks.isEmpty() ? null : beforeHooks, - afterHooks.isEmpty() ? null : afterHooks, - pickle.getTags().isEmpty() ? null : createTags(pickle)); + getDateTimeFromTimeStamp(data.testCaseStarted.getTimestamp()), + data.location.getLine(), + idStrategy.reduce(data.lineage), + JvmElementType.scenario, + scenario.getKeyword(), + data.pickle.getName(), + scenario.getDescription() != null ? scenario.getDescription() : "", + createTestSteps(testStepsFinished), + beforeHooks.isEmpty() ? null : beforeHooks, + afterHooks.isEmpty() ? null : afterHooks, + data.pickle.getTags().isEmpty() ? null : createTags(data.pickle)); } private List createHookSteps( @@ -315,10 +321,10 @@ private List createHookSteps( .flatMap(testStep -> query.findHookBy(testStep) .filter(predicate) .map(hook -> new CucumberJvmJson.JvmHook( - createMatchMap(testStep, testStepFinished.getTestStepResult()), - createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createMatchMap(testStep, testStepFinished.getTestStepResult()), + createResultMap(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -331,9 +337,9 @@ private List createEmbeddings(List att List embeddings = attachments.stream() .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); if (embeddings.isEmpty()) { @@ -359,16 +365,16 @@ private List createOutput(List attachments) { private List createTestSteps(List testStepsFinished) { List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType( - testStepsFinished); + testStepsFinished); return testStepsFinishedWithHookType .stream() .filter(testStepFinished -> !testStepFinished.getKey().isPresent()) .map(Entry::getValue) .map(testStepFinished1 -> { List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.BEFORE_TEST_STEP); + testStepFinished1, HookType.BEFORE_TEST_STEP); List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.AFTER_TEST_STEP); + testStepFinished1, HookType.AFTER_TEST_STEP); return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); }) .filter(Optional::isPresent) @@ -418,7 +424,7 @@ private List, TestStepFinished>> mapTestStepsFinishedTo .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) .flatMap(query::findHookBy) .flatMap(Hook::getType), - testStepFinished)) + testStepFinished)) .collect(toList()); } @@ -431,19 +437,19 @@ private Optional createTestStep( .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, - include(HookType.BEFORE_TEST_STEP)); + include(HookType.BEFORE_TEST_STEP)); List jvmAfterStepHooks = createHookSteps(afterStepHooks, - include(HookType.AFTER_TEST_STEP)); + include(HookType.AFTER_TEST_STEP)); return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null), - jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, - jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); + step.getKeyword(), + step.getLocation().getLine(), + createMatchMap(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createResultMap(testStepFinished.getTestStepResult()), + step.getDocString().map(this::createDocStringMap).orElse(null), + step.getDataTable().map(this::createDataTableList).orElse(null), + jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, + jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); }))); } @@ -479,30 +485,30 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); }).collect(toList())) .filter(maps -> !maps.isEmpty()); return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null)); } private JvmResult createResultMap(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private JvmDocString createDocStringMap(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } private List createDataTableList(DataTable argument) { @@ -519,17 +525,22 @@ private String getDateTimeFromTimeStamp(Timestamp instant) { return formatter.format(Convertor.toInstant(instant)); } - private static class Data { + private static class TestCaseData { private final TestCaseStarted testCaseStarted; private final Lineage lineage; private final Pickle pickle; private final Location location; + private final List> testStepFinishedAndTestStep; - private Data(TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location) { + private TestCaseData( + TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, + List> testStepFinishedAndTestStep + ) { this.testCaseStarted = testCaseStarted; this.lineage = lineage; this.pickle = pickle; this.location = location; + this.testStepFinishedAndTestStep = testStepFinishedAndTestStep; } } From d30c5195a8f4443dd782b0771aba4f158e38e505 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Jul 2025 01:12:34 +0200 Subject: [PATCH 19/41] Fix missing rule id segment in json formatter implementation --- .../cucumber/core/plugin/JsonFormatter.java | 1 - .../core/plugin/TestSourcesModel.java | 4 + .../cucumber/core/backend/StubLocation.java | 13 +- .../core/plugin/JsonFormatterOldTest.java | 1549 +++++++++++++++++ .../core/plugin/JsonFormatterTest.java | 156 +- .../JsonFormatterTestStepDefinitions.java | 83 + .../core/plugin/JsonPrettyFormatterTest.json | 58 +- 7 files changed, 1717 insertions(+), 147 deletions(-) create mode 100644 cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java create mode 100644 cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTestStepDefinitions.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java index 922929b0a6..69e5cf3a4a 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java @@ -2,7 +2,6 @@ import io.cucumber.messages.types.Envelope; import io.cucumber.plugin.ConcurrentEventListener; -import io.cucumber.plugin.EventListener; import io.cucumber.plugin.event.EventPublisher; import java.io.IOException; diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java index 4eadb552d3..fa20f8130d 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java @@ -7,6 +7,7 @@ import io.cucumber.messages.types.Feature; import io.cucumber.messages.types.FeatureChild; import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Rule; import io.cucumber.messages.types.RuleChild; import io.cucumber.messages.types.Scenario; import io.cucumber.messages.types.Source; @@ -43,6 +44,9 @@ static boolean isBackgroundStep(AstNode astNode) { static String calculateId(AstNode astNode) { Object node = astNode.node; + if (node instanceof Rule) { + return calculateId(astNode.parent) + ";" + convertToId(((Rule) node).getName()); + } if (node instanceof Scenario) { return calculateId(astNode.parent) + ";" + convertToId(((Scenario) node).getName()); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java index 06964d63e7..7263313f06 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubLocation.java @@ -19,8 +19,19 @@ public StubLocation(Method method) { } public StubLocation(SourceReference sourceReference) { - this.location = null; this.sourceReference = sourceReference; + this.location = formatLocation(sourceReference); + } + + private static String formatLocation(SourceReference sourceReference) { + if (sourceReference instanceof JavaMethodReference) { + JavaMethodReference javaMethodReference = (JavaMethodReference) sourceReference; + String className = javaMethodReference.className(); + String methodName = javaMethodReference.methodName(); + String parameterTypes = String.join(",", javaMethodReference.methodParameterTypes()); + return String.format("%s#%s(%s)", className, methodName, parameterTypes); + } + return null; } @Override diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java new file mode 100644 index 0000000000..9b36f4bac6 --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java @@ -0,0 +1,1549 @@ +package io.cucumber.core.plugin; + +import io.cucumber.core.backend.HookDefinition; +import io.cucumber.core.backend.SourceReference; +import io.cucumber.core.backend.StubHookDefinition; +import io.cucumber.core.backend.StubStepDefinition; +import io.cucumber.core.eventbus.IncrementingUuidGenerator; +import io.cucumber.core.feature.TestFeatureParser; +import io.cucumber.core.gherkin.Feature; +import io.cucumber.core.options.RuntimeOptionsBuilder; +import io.cucumber.core.runner.StepDurationTimeService; +import io.cucumber.core.runtime.Runtime; +import io.cucumber.core.runtime.Runtime.Builder; +import io.cucumber.core.runtime.StubBackendSupplier; +import io.cucumber.core.runtime.StubFeatureSupplier; +import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.datatable.DataTable; +import io.cucumber.docstring.DocString; +import org.json.JSONException; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.Scanner; +import java.util.UUID; + +import static io.cucumber.core.backend.HookDefinition.HookType.AFTER_STEP; +import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE; +import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE_STEP; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.time.Clock.fixed; +import static java.time.Duration.ofMillis; +import static java.time.Instant.EPOCH; +import static java.time.ZoneId.of; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; + +class JsonFormatterOldTest { + + final SourceReference monkeyArrives = getMethod("monkey_arrives"); + final SourceReference thereAreBananas = getMethod("there_are_bananas"); + final SourceReference thereAreOranges = getMethod("there_are_oranges"); + final SourceReference beforeHook1 = getMethod("before_hook_1"); + final SourceReference afterHook1 = getMethod("after_hook_1"); + final SourceReference beforeStepHook1 = getMethod("beforestep_hook_1"); + final SourceReference afterStepHook1 = getMethod("afterstep_hook_1"); + final SourceReference afterStepHook2 = getMethod("afterstep_hook_2"); + + final SourceReference monkeyEatsBananas = getMethod("monkey_eats_bananas"); + final SourceReference monkeyEatsMoreBananas = getMethod("monkey_eats_more_bananas"); + + private static SourceReference getMethod(String name) { + try { + return SourceReference.fromMethod(JsonFormatterTestStepDefinitions.class.getMethod(name)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Test + void featureWithOutlineTest() throws JSONException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + createRuntime(out) + .build() + .run(); + + InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); + String expected = new Scanner(resourceAsStream, "UTF-8") + .useDelimiter("\\A") + .next(); + + assertJsonEquals(expected, out); + } + + private Builder createRuntime(ByteArrayOutputStream out) { + Feature feature = TestFeatureParser.parse( + "classpath:io/cucumber/core/plugin/JsonPrettyFormatterTest.feature", + getClass().getResourceAsStream("JsonPrettyFormatterTest.feature")); + + return Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + singletonList(new StubHookDefinition(beforeHook1, BEFORE)), + asList( + new StubStepDefinition("bg_1", getMethod("bg_1")), + new StubStepDefinition("bg_2", getMethod("bg_2")), + new StubStepDefinition("bg_3", getMethod("bg_3")), + new StubStepDefinition("step_1", getMethod("step_1")), + new StubStepDefinition("step_2", getMethod("step_2")), + new StubStepDefinition("step_3", getMethod("step_3")), + new StubStepDefinition("cliché", getMethod("cliche")), + new StubStepDefinition("so_1 {int}", getMethod("so_1"), Integer.class), + new StubStepDefinition("so_2 {int} cucumbers", getMethod("so_2"), Integer.class), + new StubStepDefinition("{int} so_3", getMethod("so_3"), Integer.class), + new StubStepDefinition("a", getMethod("a")), + new StubStepDefinition("b", getMethod("b")), + new StubStepDefinition("c", getMethod("c"))), + emptyList())) + .withAdditionalPlugins(new JsonFormatterOld(out)); + } + + @Test + void featureWithOutlineTestParallel() throws JSONException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + createRuntime(out) + .withRuntimeOptions(new RuntimeOptionsBuilder().setThreads(2).build()) + .build() + .run(); + + InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); + String expected = new Scanner(resourceAsStream, "UTF-8") + .useDelimiter("\\A") + .next(); + + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_an_undefined_step() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier()) + .build() + .run(); + + 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" + + "]"; + assertJsonEquals(expected, out); + } + + private void assertJsonEquals(String expected, ByteArrayOutputStream actual) throws JSONException { + assertJsonEquals(expected, new String(actual.toByteArray(), UTF_8)); + + } + + private void assertJsonEquals(String expected, String actual) throws JSONException { + assertEquals(expected, actual, true); + } + + @Test + void should_format_scenario_with_a_passed_step() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, new IncrementingUuidGenerator())) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_failed_step() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas, + new StubException("the stack trace")))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_rule() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Rule: This is all monkey business\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas))) + .build() + .run(); + + String expected = "" + + "[\n" + + " {\n" + + " \"line\": 1,\n" + + " \"elements\": [\n" + + " {\n" + + " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + + " \"line\": 4,\n" + + " \"name\": \"Monkey eats bananas\",\n" + + " \"description\": \"\",\n" + + " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + + " \"type\": \"scenario\",\n" + + " \"keyword\": \"Scenario\",\n" + + " \"steps\": [\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"line\": 5,\n" + + " \"name\": \"there are bananas\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"keyword\": \"Given \"\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" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_rule_and_background() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Background: \n" + + " Given there are bananas\n" + + "\n" + + " Rule: This is all monkey business\n" + + "\n" + + " Background: \n" + + " Given there are bananas\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas))) + .build() + .run(); + + String expected = "" + + "[\n" + + " {\n" + + " \"line\": 1,\n" + + " \"elements\": [\n" + + " {\n" + + " \"line\": 3,\n" + + " \"name\": \"\",\n" + + " \"description\": \"\",\n" + + " \"type\": \"background\",\n" + + " \"keyword\": \"Background\",\n" + + " \"steps\": [\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"line\": 4,\n" + + " \"name\": \"there are bananas\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"keyword\": \"Given \"\n" + + " },\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"line\": 9,\n" + + " \"name\": \"there are bananas\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"keyword\": \"Given \"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + + " \"line\": 11,\n" + + " \"name\": \"Monkey eats bananas\",\n" + + " \"description\": \"\",\n" + + " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + + " \"type\": \"scenario\",\n" + + " \"keyword\": \"Scenario\",\n" + + " \"steps\": [\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"line\": 12,\n" + + " \"name\": \"there are bananas\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"keyword\": \"Given \"\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" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_outline_with_one_example() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_feature_with_background() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("the monkey eats bananas", monkeyEatsBananas), + new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_feature_and_scenario_with_tags() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_hooks() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + singletonList(new StubHookDefinition(beforeHook1, HookDefinition.HookType.BEFORE)), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.AFTER)))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"after\": [\n" + + " {\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#after_hook_1()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_add_step_hooks_to_step() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + emptyList(), + singletonList(new StubHookDefinition(beforeStepHook1, BEFORE_STEP)), + asList( + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("monkey arrives", monkeyArrives)), + asList( + new StubHookDefinition(afterStepHook1, AFTER_STEP), + new StubHookDefinition(afterStepHook2, AFTER_STEP)), + emptyList())) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + + + " }\n" + + " }\n" + + " ],\n" + + " \"line\": 4,\n" + + " \"name\": \"there are bananas\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"after\": [\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + + + " }\n" + + " },\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + + + " }\n" + + " }\n" + + " ],\n" + + " \"line\": 5,\n" + + " \"name\": \"monkey arrives\",\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_arrives()\"\n" + + + " },\n" + + " \"after\": [\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + + + " }\n" + + " },\n" + + " {\n" + + " \"result\": {\n" + + " \"duration\": 1000000,\n" + + " \"status\": \"passed\"\n" + + " },\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_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" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_handle_write_from_a_hook() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + singletonList(new StubHookDefinition(beforeHook1, BEFORE, + testCaseState -> testCaseState.log("printed from hook"))), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + emptyList())) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_handle_embed_from_a_hook() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + singletonList(new StubHookDefinition(beforeHook1, + BEFORE, + testCaseState -> testCaseState + .attach(new byte[] { 1, 2, 3 }, "mime-type;base64", null))), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + emptyList())) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_handle_embed_with_name_from_a_hook() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + singletonList(new StubHookDefinition(beforeHook1, BEFORE, + testCaseState -> testCaseState.attach(new byte[] { 1, 2, 3 }, "mime-type;base64", + "someEmbedding"))), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + emptyList())) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas, String.class))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() throws JSONException { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n" + + " \"\"\"text/plain\n" + + " doc string content\n" + + " \"\"\"\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas, DocString.class))) + .build() + .run(); + + 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\": \"text/plain\",\n" + + " \"value\": \"doc string content\",\n" + + " \"line\": 5\n" + + " },\n" + + " \"match\": {\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_format_scenario_with_a_step_with_a_data_table() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas, DataTable.class))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } + + @Test + void should_handle_several_features() throws JSONException { + 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"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature1, feature2)) + .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("there are bananas", thereAreBananas), + new StubStepDefinition("there are oranges", thereAreOranges))) + .build() + .run(); + + 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_oranges()\"\n" + + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertJsonEquals(expected, out); + } +} diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index 0ea56f4e09..f54482895b 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -53,7 +53,7 @@ class JsonFormatterTest { private static SourceReference getMethod(String name) { try { - return SourceReference.fromMethod(StepDefs.class.getMethod(name)); + return SourceReference.fromMethod(JsonFormatterTestStepDefinitions.class.getMethod(name)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -224,7 +224,7 @@ void should_format_scenario_with_a_passed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -285,7 +285,7 @@ void should_format_scenario_with_a_failed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -345,7 +345,7 @@ void should_format_scenario_with_a_rule() throws JSONException { " \"line\": 5,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -411,7 +411,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -424,7 +424,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 9,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -448,7 +448,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 12,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -513,7 +513,7 @@ void should_format_scenario_outline_with_one_example() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -579,7 +579,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -603,7 +603,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats bananas\",\n" + " \"line\": 7,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -625,7 +625,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -649,7 +649,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats more bananas\",\n" + " \"line\": 10,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_more_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -708,7 +708,7 @@ void should_format_feature_and_scenario_with_tags() throws JSONException { " \"line\": 5,\n" + " \"name\": \"the monkey eats more bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_eats_more_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + " },\n" + " \"keyword\": \"Then \"\n" + @@ -797,7 +797,8 @@ void should_format_scenario_with_hooks() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -811,7 +812,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -823,7 +824,8 @@ void should_format_scenario_with_hooks() throws JSONException { " \"after\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#after_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#after_hook_1()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -893,7 +895,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + " }\n" + " }\n" + @@ -901,7 +903,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"after\": [\n" + @@ -911,7 +913,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + " }\n" + " },\n" + @@ -921,7 +923,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + " }\n" + " }\n" + @@ -940,7 +942,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#beforestep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + " }\n" + " }\n" + @@ -948,7 +950,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 5,\n" + " \"name\": \"monkey arrives\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#monkey_arrives()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_arrives()\"\n" + " },\n" + " \"after\": [\n" + @@ -958,7 +960,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_2()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + " }\n" + " },\n" + @@ -968,7 +970,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#afterstep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + " }\n" + " }\n" + @@ -1032,7 +1034,8 @@ void should_handle_write_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"output\": [\n" + " \"printed from hook\"\n" + @@ -1049,7 +1052,7 @@ void should_handle_write_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1111,7 +1114,8 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1131,7 +1135,7 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1192,7 +1196,8 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1213,7 +1218,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1280,7 +1285,7 @@ void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1348,7 +1353,7 @@ void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() thr " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1424,7 +1429,7 @@ void should_format_scenario_with_a_step_with_a_data_table() throws JSONException " }\n" + " ],\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1490,7 +1495,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1525,7 +1530,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are oranges\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTest$StepDefs#there_are_oranges()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_oranges()\"\n" + " },\n" + " \"result\": {\n" + @@ -1542,85 +1547,4 @@ void should_handle_several_features() throws JSONException { assertJsonEquals(expected, out); } - static class StepDefs { - - public void bg_1() { - } - - public void bg_2() { - } - - public void bg_3() { - } - - public void step_1() { - } - - public void step_2() { - } - - public void step_3() { - } - - public void cliche() { - } - - public void so_1() { - } - - public void so_2() { - } - - public void so_3() { - } - - public void a() { - } - - public void b() { - } - - public void c() { - } - - public void before_hook_1() { - - } - - public void after_hook_1() { - - } - - public void beforestep_hook_1() { - - } - - public void afterstep_hook_1() { - - } - - public void afterstep_hook_2() { - - } - - public void there_are_bananas() { - - } - - public void there_are_oranges() { - - } - - public void monkey_eats_bananas() { - - } - - public void monkey_eats_more_bananas() { - - } - - public void monkey_arrives() { - - } - } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTestStepDefinitions.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTestStepDefinitions.java new file mode 100644 index 0000000000..730eccbbec --- /dev/null +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTestStepDefinitions.java @@ -0,0 +1,83 @@ +package io.cucumber.core.plugin; + +class JsonFormatterTestStepDefinitions { + + public void bg_1() { + } + + public void bg_2() { + } + + public void bg_3() { + } + + public void step_1() { + } + + public void step_2() { + } + + public void step_3() { + } + + public void cliche() { + } + + public void so_1() { + } + + public void so_2() { + } + + public void so_3() { + } + + public void a() { + } + + public void b() { + } + + public void c() { + } + + public void before_hook_1() { + + } + + public void after_hook_1() { + + } + + public void beforestep_hook_1() { + + } + + public void afterstep_hook_1() { + + } + + public void afterstep_hook_2() { + + } + + public void there_are_bananas() { + + } + + public void there_are_oranges() { + + } + + public void monkey_eats_bananas() { + + } + + public void monkey_eats_more_bananas() { + + } + + public void monkey_arrives() { + + } +} diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json index 042a0e6631..2997958900 100644 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json +++ b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json @@ -16,7 +16,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -27,7 +27,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -38,7 +38,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -52,7 +52,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -70,7 +70,7 @@ "line": 9, "name": "step_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_1()" }, "keyword": "Given " }, @@ -81,7 +81,7 @@ "line": 10, "name": "step_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_2()" }, "keyword": "When " }, @@ -92,7 +92,7 @@ "line": 11, "name": "step_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#step_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_3()" }, "keyword": "Then " }, @@ -103,7 +103,7 @@ "line": 12, "name": "cliché", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#cliche()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#cliche()" }, "keyword": "Then " } @@ -123,7 +123,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -134,7 +134,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -145,7 +145,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -159,7 +159,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -183,7 +183,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" }, "keyword": "Given " }, @@ -200,7 +200,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" }, "keyword": "When " }, @@ -217,7 +217,7 @@ "offset": 0 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" }, "keyword": "Then " } @@ -237,7 +237,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -248,7 +248,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -259,7 +259,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -273,7 +273,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -297,7 +297,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" }, "keyword": "Given " }, @@ -314,7 +314,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" }, "keyword": "When " }, @@ -331,7 +331,7 @@ "offset": 0 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#so_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" }, "keyword": "Then " } @@ -351,7 +351,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -362,7 +362,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -373,7 +373,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -387,7 +387,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -405,7 +405,7 @@ "line": 25, "name": "a", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#a()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#a()" }, "keyword": "Given " }, @@ -416,7 +416,7 @@ "line": 26, "name": "b", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#b()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#b()" }, "keyword": "Then " }, @@ -427,7 +427,7 @@ "line": 27, "name": "c", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTest$StepDefs#c()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#c()" }, "keyword": "When " } From c04fc9525f8293c7e4c0056e1fc14ece3bcb8b33 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 14 Jul 2025 01:31:06 +0200 Subject: [PATCH 20/41] Improve performance --- .../core/plugin/JsonReportWriter.java | 86 +++++++++++-------- .../core/plugin/TestSourcesModel.java | 6 +- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index e983d02676..be17776c03 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -72,6 +72,8 @@ import static io.cucumber.core.plugin.TestSourcesModel.convertToId; import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; +import static io.cucumber.messages.types.HookType.AFTER_TEST_STEP; +import static io.cucumber.messages.types.HookType.BEFORE_TEST_STEP; import static java.util.Locale.ROOT; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; @@ -110,10 +112,6 @@ private static Predicate include(HookType... hookTypes) { return hook -> hook.getType().map(keep::contains).orElse(false); } - private static Predicate exclude(HookType... hookTypes) { - return include(hookTypes).negate(); - } - private static String renderLocationString(SourceReference sourceReference, String uri) { String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); return uri + locationLine; @@ -153,6 +151,40 @@ private static int findIndexOf( return -1; } + private static List findAfterStepHooksAfter( + List, TestStepFinished>> withHookType, int stepIndex + ) { + List afterStepHooks = new ArrayList<>(); + // after step hooks, come after the step, so search forward. + for (int hookIndex = stepIndex + 1; hookIndex < withHookType.size(); hookIndex++) { + Entry, TestStepFinished> candidate = withHookType.get(hookIndex); + boolean isAfterStepHook = candidate.getKey().map(AFTER_TEST_STEP::equals).orElse(false); + // We found the end of this sequence + if (!isAfterStepHook) { + break; + } + afterStepHooks.add(candidate.getValue()); + } + return afterStepHooks; + } + + private static List findBeforeStepHooksBefore( + List, TestStepFinished>> withHookType, int stepIndex + ) { + List beforeStepHooks = new ArrayList<>(); + // before step hooks, come before the step, so search in reverse. + for (int hookIndex = stepIndex - 1; hookIndex >= 0; hookIndex--) { + Entry, TestStepFinished> candidate = withHookType.get(hookIndex); + boolean isBeforeStepHook = candidate.getKey().map(BEFORE_TEST_STEP::equals).orElse(false); + // We found the end of this sequence + if (!isBeforeStepHook) { + break; + } + beforeStepHooks.add(candidate.getValue()); + } + return beforeStepHooks; + } + List writeJsonReport() { return query.findAllTestCaseStarted() .stream() @@ -216,7 +248,7 @@ private List createTestCaseAndBackGround(TestCaseData data) { }; Map, List> stepsByBackground = data.testStepFinishedAndTestStep .stream() - .collect(groupByBackground(data.testCaseStarted)); + .collect(groupByBackground(data)); // There can be multiple backgrounds, but historically the json format // only ever had one. So we group all other backgrounds steps with the @@ -239,9 +271,9 @@ private List createTestCaseAndBackGround(TestCaseData data) { } private Collector, ?, Map, List>> groupByBackground( - TestCaseStarted testCaseStarted + TestCaseData testCaseData ) { - List backgrounds = query.findFeatureBy(testCaseStarted) + List backgrounds = testCaseData.lineage.feature() .map(this::getBackgroundsBy) .orElseGet(Collections::emptyList); @@ -306,7 +338,7 @@ private JvmElement createTestCase(TestCaseData data, List test JvmElementType.scenario, scenario.getKeyword(), data.pickle.getName(), - scenario.getDescription() != null ? scenario.getDescription() : "", + scenario.getDescription() == null ? "" : scenario.getDescription(), createTestSteps(testStepsFinished), beforeHooks.isEmpty() ? null : beforeHooks, afterHooks.isEmpty() ? null : afterHooks, @@ -372,9 +404,9 @@ private List createTestSteps(List testStepsFinished) .map(Entry::getValue) .map(testStepFinished1 -> { List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.BEFORE_TEST_STEP); + testStepFinished1, BEFORE_TEST_STEP); List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, HookType.AFTER_TEST_STEP); + testStepFinished1, AFTER_TEST_STEP); return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); }) .filter(Optional::isPresent) @@ -384,36 +416,18 @@ private List createTestSteps(List testStepsFinished) private List findHooksFor( List, TestStepFinished>> withHookType, TestStepFinished testStepFinished, - HookType hookType + HookType targetHookType ) { int stepIndex = findIndexOf(withHookType, testStepFinished); if (stepIndex < 0) { return Collections.emptyList(); } - if (hookType == HookType.BEFORE_TEST_STEP) { - List beforeStepHooks = new ArrayList<>(); - for (int hookIndex = stepIndex - 1; hookIndex >= 0; hookIndex--) { - Entry, TestStepFinished> candidate = withHookType.get(hookIndex); - boolean isBeforeStepHook = candidate.getKey().map(hookType1 -> hookType1 == hookType).orElse(false); - if (!isBeforeStepHook) { - break; - } - beforeStepHooks.add(candidate.getValue()); - } - return beforeStepHooks; - } else if (hookType == HookType.AFTER_TEST_STEP) { - List afterStepHooks = new ArrayList<>(); - for (int hookIndex = stepIndex + 1; hookIndex < withHookType.size(); hookIndex++) { - Entry, TestStepFinished> candidate = withHookType.get(hookIndex); - boolean isAfterStepHook = candidate.getKey().map(hookType1 -> hookType1 == hookType).orElse(false); - if (!isAfterStepHook) { - break; - } - afterStepHooks.add(candidate.getValue()); - } - return afterStepHooks; + if (targetHookType == BEFORE_TEST_STEP) { + return findBeforeStepHooksBefore(withHookType, stepIndex); + } + if (targetHookType == AFTER_TEST_STEP) { + return findAfterStepHooksAfter(withHookType, stepIndex); } - return Collections.emptyList(); } @@ -437,9 +451,9 @@ private Optional createTestStep( .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, - include(HookType.BEFORE_TEST_STEP)); + include(BEFORE_TEST_STEP)); List jvmAfterStepHooks = createHookSteps(afterStepHooks, - include(HookType.AFTER_TEST_STEP)); + include(AFTER_TEST_STEP)); return new JvmStep( step.getKeyword(), step.getLocation().getLine(), diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java index fa20f8130d..1cd633e0bf 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java @@ -20,8 +20,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Stream; final class TestSourcesModel { @@ -65,8 +67,10 @@ static String calculateId(AstNode astNode) { return ""; } + private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); + static String convertToId(String name) { - return name.replaceAll("[\\s'_,!]", "-").toLowerCase(); + return replacementPattern.matcher(name).replaceAll("-").toLowerCase(Locale.ROOT); } static URI relativize(URI uri) { From fb87cf15e3d1e1c17dfd8fc13c07d3a1f872b20d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 14:16:21 +0200 Subject: [PATCH 21/41] Simplify hooks --- .../core/plugin/JsonReportWriter.java | 245 +++++++++--------- 1 file changed, 116 insertions(+), 129 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index be17776c03..e375a861a3 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -52,12 +52,11 @@ import java.time.Duration; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; -import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -72,8 +71,7 @@ import static io.cucumber.core.plugin.TestSourcesModel.convertToId; import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; -import static io.cucumber.messages.types.HookType.AFTER_TEST_STEP; -import static io.cucumber.messages.types.HookType.BEFORE_TEST_STEP; +import static java.util.Collections.emptyList; import static java.util.Locale.ROOT; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; @@ -107,11 +105,6 @@ private static List createTags(Pickle pickle) { return pickle.getTags().stream().map(pickleTag -> new JvmTag(pickleTag.getName())).collect(toList()); } - private static Predicate include(HookType... hookTypes) { - List keep = Arrays.asList(hookTypes); - return hook -> hook.getType().map(keep::contains).orElse(false); - } - private static String renderLocationString(SourceReference sourceReference, String uri) { String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); return uri + locationLine; @@ -138,59 +131,10 @@ private static String renderLocationString(JavaMethod javaMethod) { String.join(",", javaMethod.getMethodParameterTypes())); } - private static int findIndexOf( - List, TestStepFinished>> withHookType, TestStepFinished testStepFinished - ) { - for (int i = 0; i < withHookType.size(); i++) { - TestStepFinished candidate = withHookType.get(i).getValue(); - if (testStepFinished.equals(candidate)) { - return i; - } - - } - return -1; - } - - private static List findAfterStepHooksAfter( - List, TestStepFinished>> withHookType, int stepIndex - ) { - List afterStepHooks = new ArrayList<>(); - // after step hooks, come after the step, so search forward. - for (int hookIndex = stepIndex + 1; hookIndex < withHookType.size(); hookIndex++) { - Entry, TestStepFinished> candidate = withHookType.get(hookIndex); - boolean isAfterStepHook = candidate.getKey().map(AFTER_TEST_STEP::equals).orElse(false); - // We found the end of this sequence - if (!isAfterStepHook) { - break; - } - afterStepHooks.add(candidate.getValue()); - } - return afterStepHooks; - } - - private static List findBeforeStepHooksBefore( - List, TestStepFinished>> withHookType, int stepIndex - ) { - List beforeStepHooks = new ArrayList<>(); - // before step hooks, come before the step, so search in reverse. - for (int hookIndex = stepIndex - 1; hookIndex >= 0; hookIndex--) { - Entry, TestStepFinished> candidate = withHookType.get(hookIndex); - boolean isBeforeStepHook = candidate.getKey().map(BEFORE_TEST_STEP::equals).orElse(false); - // We found the end of this sequence - if (!isBeforeStepHook) { - break; - } - beforeStepHooks.add(candidate.getValue()); - } - return beforeStepHooks; - } - List writeJsonReport() { return query.findAllTestCaseStarted() .stream() .map(this::createTestCaseStartedData) - .filter(Optional::isPresent) - .map(Optional::get) .sorted(Comparator.comparing((TestCaseData data) -> data.pickle.getUri()) .thenComparing(data -> data.location.getLine())) .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) @@ -200,16 +144,75 @@ List writeJsonReport() { .collect(toList()); } - private Optional createTestCaseStartedData(TestCaseStarted testCaseStarted) { - return query.findPickleBy(testCaseStarted) - .flatMap(pickle -> query.findLineageBy(pickle) - .flatMap(lineage -> query.findLocationOf(pickle) - .map(location -> { - List> testStepFinishedAndTestStep = query - .findTestStepFinishedAndTestStepBy(testCaseStarted); - return new TestCaseData(testCaseStarted, lineage, pickle, location, - testStepFinishedAndTestStep); - }))); + private TestCaseData createTestCaseStartedData(TestCaseStarted testCaseStarted) { + Pickle pickle = query.findPickleBy(testCaseStarted) + .orElseThrow( + () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); + Lineage lineage = query.findLineageBy(pickle) + .orElseThrow( + () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); + Location location = query.findLocationOf(pickle) + .orElseThrow( + () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); + + TestStepData testStepData = createTestStepData(testCaseStarted); + + return new TestCaseData( + testCaseStarted, + lineage, + pickle, + location, + testStepData + ); + + } + + private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { + List testStepsFinished = query.findTestStepsFinishedBy(testCaseStarted); + + List beforeTestCase = new ArrayList<>(); + List afterTestCase = new ArrayList<>(); + List testSteps = new ArrayList<>(); + List beforeTestStep = new ArrayList<>(); + List afterTestStep = new ArrayList<>(); + Map> beforeTestStepByStep = new HashMap<>(); + Map> afterTestStepByStep = new HashMap<>(); + + for (TestStepFinished testStepFinished : testStepsFinished) { + HookType hook = query.findTestStepBy(testStepFinished) + .flatMap(query::findHookBy) + .flatMap(Hook::getType) + .orElse(null); + + if (hook == null) { + beforeTestStepByStep.put(testStepFinished, beforeTestStep); + beforeTestStep = new ArrayList<>(); + testSteps.add(testStepFinished); + afterTestStep = new ArrayList<>(); + afterTestStepByStep.put(testStepFinished, afterTestStep); + continue; + } + + switch (hook) { + case BEFORE_TEST_RUN: + case AFTER_TEST_RUN: + break; + case BEFORE_TEST_CASE: + beforeTestCase.add(testStepFinished); + break; + case AFTER_TEST_CASE: + afterTestCase.add(testStepFinished); + break; + case BEFORE_TEST_STEP: + beforeTestStep.add(testStepFinished); + break; + case AFTER_TEST_STEP: + afterTestStep.add(testStepFinished); + break; + } + + } + return new TestStepData(testSteps, beforeTestCase, afterTestCase, beforeTestStepByStep, afterTestStepByStep); } private JvmFeature createFeatureMap(List entries) { @@ -246,7 +249,8 @@ private List createTestCaseAndBackGround(TestCaseData data) { a.getValue().addAll(b.getValue()); return a; }; - Map, List> stepsByBackground = data.testStepFinishedAndTestStep + Map, List> stepsByBackground = query + .findTestStepFinishedAndTestStepBy(data.testCaseStarted) .stream() .collect(groupByBackground(data)); @@ -256,7 +260,7 @@ private List createTestCaseAndBackGround(TestCaseData data) { Optional background = stepsByBackground.entrySet().stream() .filter(isBackGround) .reduce(mergeSteps) - .flatMap(entry -> entry.getKey().map(bg -> createBackground(bg, entry.getValue()))); + .flatMap(entry -> entry.getKey().map(bg -> createBackground(data, bg, entry.getValue()))); Optional testCase = stepsByBackground.entrySet().stream() .filter(isTestCase) @@ -309,7 +313,9 @@ private List getBackgroundsBy(Feature feature) { .collect(toList()); } - private JvmElement createBackground(Background background, List testStepsFinished) { + private JvmElement createBackground( + TestCaseData data, Background background, List testStepsFinished + ) { return new JvmElement( null, background.getLocation().getLine(), @@ -318,7 +324,7 @@ private JvmElement createBackground(Background background, List testStepsFinished) { Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - List beforeHooks = createHookSteps(testStepsFinished, - include(HookType.BEFORE_TEST_CASE)); - List afterHooks = createHookSteps(testStepsFinished, - include(HookType.AFTER_TEST_CASE)); + List beforeHooks = createHookSteps(data.testStepData.beforeTestCaseSteps); + List afterHooks = createHookSteps(data.testStepData.afterTestCaseSteps); return new JvmElement( getDateTimeFromTimeStamp(data.testCaseStarted.getTimestamp()), data.location.getLine(), @@ -339,19 +343,16 @@ private JvmElement createTestCase(TestCaseData data, List test scenario.getKeyword(), data.pickle.getName(), scenario.getDescription() == null ? "" : scenario.getDescription(), - createTestSteps(testStepsFinished), + createTestSteps(data, testStepsFinished), beforeHooks.isEmpty() ? null : beforeHooks, afterHooks.isEmpty() ? null : afterHooks, data.pickle.getTags().isEmpty() ? null : createTags(data.pickle)); } - private List createHookSteps( - List testStepsFinished, Predicate predicate - ) { + private List createHookSteps(List testStepsFinished) { return testStepsFinished.stream() .map(testStepFinished -> query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findHookBy(testStep) - .filter(predicate) .map(hook -> new CucumberJvmJson.JvmHook( createMatchMap(testStep, testStepFinished.getTestStepResult()), createResultMap(testStepFinished.getTestStepResult()), @@ -395,53 +396,20 @@ private List createOutput(List attachments) { return outputs; } - private List createTestSteps(List testStepsFinished) { - List, TestStepFinished>> testStepsFinishedWithHookType = mapTestStepsFinishedToHookType( - testStepsFinished); - return testStepsFinishedWithHookType - .stream() - .filter(testStepFinished -> !testStepFinished.getKey().isPresent()) - .map(Entry::getValue) - .map(testStepFinished1 -> { - List beforeStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, BEFORE_TEST_STEP); - List afterStepHooks = findHooksFor(testStepsFinishedWithHookType, - testStepFinished1, AFTER_TEST_STEP); - return createTestStep(testStepFinished1, beforeStepHooks, afterStepHooks); + private List createTestSteps(TestCaseData data, List testStepsFinished) { + return testStepsFinished.stream() + .map(testStepFinished -> { + List beforeStepHooks = data.testStepData.beforeStepStepsByStep + .getOrDefault(testStepFinished, emptyList()); + List afterStepHooks = data.testStepData.afterStepStepsByStep + .getOrDefault(testStepFinished, emptyList()); + return createTestStep(testStepFinished, beforeStepHooks, afterStepHooks); }) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); } - private List findHooksFor( - List, TestStepFinished>> withHookType, TestStepFinished testStepFinished, - HookType targetHookType - ) { - int stepIndex = findIndexOf(withHookType, testStepFinished); - if (stepIndex < 0) { - return Collections.emptyList(); - } - if (targetHookType == BEFORE_TEST_STEP) { - return findBeforeStepHooksBefore(withHookType, stepIndex); - } - if (targetHookType == AFTER_TEST_STEP) { - return findAfterStepHooksAfter(withHookType, stepIndex); - } - return Collections.emptyList(); - } - - private List, TestStepFinished>> mapTestStepsFinishedToHookType( - List testStepsFinished - ) { - return testStepsFinished.stream() - .map(testStepFinished -> new SimpleEntry<>(query.findTestStepBy(testStepFinished) - .flatMap(query::findHookBy) - .flatMap(Hook::getType), - testStepFinished)) - .collect(toList()); - } - private Optional createTestStep( TestStepFinished testStepFinished, List beforeStepHooks, List afterStepHooks @@ -450,10 +418,8 @@ private Optional createTestStep( .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { - List jvmBeforeStepHooks = createHookSteps(beforeStepHooks, - include(BEFORE_TEST_STEP)); - List jvmAfterStepHooks = createHookSteps(afterStepHooks, - include(AFTER_TEST_STEP)); + List jvmBeforeStepHooks = createHookSteps(beforeStepHooks); + List jvmAfterStepHooks = createHookSteps(afterStepHooks); return new JvmStep( step.getKeyword(), step.getLocation().getLine(), @@ -539,22 +505,43 @@ private String getDateTimeFromTimeStamp(Timestamp instant) { return formatter.format(Convertor.toInstant(instant)); } + private static class TestStepData { + private final List testSteps; + private final List beforeTestCaseSteps; + private final List afterTestCaseSteps; + private final Map> beforeStepStepsByStep; + private final Map> afterStepStepsByStep; + + private TestStepData( + List testSteps, List beforeTestCaseSteps, + List afterTestCaseSteps, + Map> beforeStepStepsByStep, + Map> afterStepStepsByStep + ) { + this.testSteps = testSteps; + this.beforeTestCaseSteps = beforeTestCaseSteps; + this.afterTestCaseSteps = afterTestCaseSteps; + this.beforeStepStepsByStep = beforeStepStepsByStep; + this.afterStepStepsByStep = afterStepStepsByStep; + } + } + private static class TestCaseData { private final TestCaseStarted testCaseStarted; private final Lineage lineage; private final Pickle pickle; private final Location location; - private final List> testStepFinishedAndTestStep; + private final TestStepData testStepData; private TestCaseData( TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, - List> testStepFinishedAndTestStep + TestStepData testStepData ) { this.testCaseStarted = testCaseStarted; this.lineage = lineage; this.pickle = pickle; this.location = location; - this.testStepFinishedAndTestStep = testStepFinishedAndTestStep; + this.testStepData = testStepData; } } From c8a525b70f18c818efa58ce8b5fa80a86e89e5e7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 14:47:15 +0200 Subject: [PATCH 22/41] Simplify --- .../core/plugin/JsonReportWriter.java | 306 ++++++++++-------- 1 file changed, 164 insertions(+), 142 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index e375a861a3..c9f3d021cc 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -73,24 +73,26 @@ import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; import static java.util.Collections.emptyList; import static java.util.Locale.ROOT; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; class JsonReportWriter { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneOffset.UTC); private final Query query; JsonReportWriter(Query query) { this.query = query; } - private static JvmLocationTag createLocationTag(Tag tag) { + private static JvmLocationTag createJvmLocationTag(Tag tag) { return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -101,78 +103,90 @@ private static Optional findBackgroundBy(List background .findFirst(); } - private static List createTags(Pickle pickle) { - return pickle.getTags().stream().map(pickleTag -> new JvmTag(pickleTag.getName())).collect(toList()); + private static List createJvmTags(Pickle pickle) { + List tags = pickle.getTags() + .stream() + .map(pickleTag -> new JvmTag(pickleTag.getName())) + .collect(toList()); + return tags.isEmpty() ? null : tags; } - private static String renderLocationString(SourceReference sourceReference, String uri) { + private static String formatSourceReference(SourceReference sourceReference, String uri) { String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); return uri + locationLine; } - private static String renderLocationString( + private static String formatSourceReference( SourceReference sourceReference, JavaStackTraceElement javaStackTraceElement ) { String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); String argumentList = String.join(",", javaStackTraceElement.getFileName()); return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); } - private static String renderLocationString(JavaMethod javaMethod) { + private static String formatSourceReference(JavaMethod javaMethod) { return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); + } + + private static List createJvmLocationTags(Feature feature) { + return feature.getTags().stream() + .map(JsonReportWriter::createJvmLocationTag) + .collect(toList()); + } + + private static String formatTimeStamp(Timestamp instant) { + return formatter.format(Convertor.toInstant(instant)); } List writeJsonReport() { return query.findAllTestCaseStarted() .stream() .map(this::createTestCaseStartedData) - .sorted(Comparator.comparing((TestCaseData data) -> data.pickle.getUri()) - .thenComparing(data -> data.location.getLine())) + .sorted(new JvmFeatureDataComparator()) .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) .values() .stream() - .map(this::createFeatureMap) + .map(this::createJvmFeature) .collect(toList()); } - private TestCaseData createTestCaseStartedData(TestCaseStarted testCaseStarted) { + private JvmElementData createTestCaseStartedData(TestCaseStarted testCaseStarted) { Pickle pickle = query.findPickleBy(testCaseStarted) .orElseThrow( - () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); Lineage lineage = query.findLineageBy(pickle) .orElseThrow( - () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); Location location = query.findLocationOf(pickle) .orElseThrow( - () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); - TestStepData testStepData = createTestStepData(testCaseStarted); + TestStepData testStepData = createJvmElementData(testCaseStarted); - return new TestCaseData( - testCaseStarted, - lineage, - pickle, - location, - testStepData + return new JvmElementData( + testCaseStarted, + lineage, + pickle, + location, + testStepData ); } - private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { + private TestStepData createJvmElementData(TestCaseStarted testCaseStarted) { List testStepsFinished = query.findTestStepsFinishedBy(testCaseStarted); List beforeTestCase = new ArrayList<>(); List afterTestCase = new ArrayList<>(); - List testSteps = new ArrayList<>(); List beforeTestStep = new ArrayList<>(); List afterTestStep = new ArrayList<>(); Map> beforeTestStepByStep = new HashMap<>(); @@ -187,7 +201,6 @@ private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { if (hook == null) { beforeTestStepByStep.put(testStepFinished, beforeTestStep); beforeTestStep = new ArrayList<>(); - testSteps.add(testStepFinished); afterTestStep = new ArrayList<>(); afterTestStepByStep.put(testStepFinished, afterTestStep); continue; @@ -212,35 +225,33 @@ private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { } } - return new TestStepData(testSteps, beforeTestCase, afterTestCase, beforeTestStepByStep, afterTestStepByStep); + return new TestStepData(beforeTestCase, afterTestCase, beforeTestStepByStep, afterTestStepByStep); } - private JvmFeature createFeatureMap(List entries) { - GherkinDocument document = entries.get(0).lineage.document(); - Feature feature = entries.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); + private JvmFeature createJvmFeature(List elements) { + GherkinDocument document = elements.get(0).lineage.document(); + Feature feature = elements.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); return new JvmFeature( - // TODO: Relativize, optional?, null? - TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), - convertToId(feature.getName()), - feature.getLocation().getLine(), - feature.getKeyword(), - feature.getName(), - // TODO: Can this be null? - feature.getDescription() != null ? feature.getDescription() : "", - writeElementsReport(entries), - feature.getTags().stream() - .map(JsonReportWriter::createLocationTag) - .collect(toList())); - } - - private List writeElementsReport(List entries) { + // TODO: Relativize, optional?, null? Custom? + TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), + convertToId(feature.getName()), + feature.getLocation().getLine(), + feature.getKeyword(), + feature.getName(), + // TODO: Can this be null? + feature.getDescription() != null ? feature.getDescription() : "", + createJvmElements(elements), + createJvmLocationTags(feature)); + } + + private List createJvmElements(List entries) { return entries.stream() .map(this::createTestCaseAndBackGround) .flatMap(Collection::stream) .collect(toList()); } - private List createTestCaseAndBackGround(TestCaseData data) { + private List createTestCaseAndBackGround(JvmElementData data) { // TODO: Clean up Predicate, List>> isBackGround = entry -> entry.getKey() .isPresent(); @@ -266,7 +277,7 @@ private List createTestCaseAndBackGround(TestCaseData data) { .filter(isTestCase) .reduce(mergeSteps) .map(Entry::getValue) - .map(testStepFinished -> createTestCase(data, testStepFinished)); + .map(scenarioTestStepsFinished -> createTestCase(data, scenarioTestStepsFinished)); return Stream.of(background, testCase) .filter(Optional::isPresent) @@ -275,9 +286,9 @@ private List createTestCaseAndBackGround(TestCaseData data) { } private Collector, ?, Map, List>> groupByBackground( - TestCaseData testCaseData + JvmElementData data ) { - List backgrounds = testCaseData.lineage.feature() + List backgrounds = data.lineage.feature() .map(this::getBackgroundsBy) .orElseGet(Collections::emptyList); @@ -314,53 +325,54 @@ private List getBackgroundsBy(Feature feature) { } private JvmElement createBackground( - TestCaseData data, Background background, List testStepsFinished + JvmElementData data, Background background, List backgroundTestStepsFinished ) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(data, testStepsFinished), - null, - null, - null); - } - - private JvmElement createTestCase(TestCaseData data, List testStepsFinished) { + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(data, backgroundTestStepsFinished), + null, + null, + null + ); + } + + private JvmElement createTestCase(JvmElementData data, List scenarioTestStepsFinished) { Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - List beforeHooks = createHookSteps(data.testStepData.beforeTestCaseSteps); - List afterHooks = createHookSteps(data.testStepData.afterTestCaseSteps); return new JvmElement( - getDateTimeFromTimeStamp(data.testCaseStarted.getTimestamp()), - data.location.getLine(), - idStrategy.reduce(data.lineage), - JvmElementType.scenario, - scenario.getKeyword(), - data.pickle.getName(), - scenario.getDescription() == null ? "" : scenario.getDescription(), - createTestSteps(data, testStepsFinished), - beforeHooks.isEmpty() ? null : beforeHooks, - afterHooks.isEmpty() ? null : afterHooks, - data.pickle.getTags().isEmpty() ? null : createTags(data.pickle)); + formatTimeStamp(data.testCaseStarted.getTimestamp()), + data.location.getLine(), + idStrategy.reduce(data.lineage), + JvmElementType.scenario, + scenario.getKeyword(), + data.pickle.getName(), + scenario.getDescription() == null ? "" : scenario.getDescription(), + createTestSteps(data, scenarioTestStepsFinished), + createHookSteps(data.testStepData.beforeTestCaseSteps), + createHookSteps(data.testStepData.afterTestCaseSteps), + createJvmTags(data.pickle) + ); } private List createHookSteps(List testStepsFinished) { - return testStepsFinished.stream() + List hooks = testStepsFinished.stream() .map(testStepFinished -> query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findHookBy(testStep) .map(hook -> new CucumberJvmJson.JvmHook( - createMatchMap(testStep, testStepFinished.getTestStepResult()), - createResultMap(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createJvmMatch(testStep, testStepFinished.getTestStepResult()), + createJvmResult(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); + return hooks.isEmpty() ? null : hooks; } private List createEmbeddings(List attachments) { @@ -370,9 +382,9 @@ private List createEmbeddings(List att List embeddings = attachments.stream() .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); if (embeddings.isEmpty()) { @@ -396,7 +408,7 @@ private List createOutput(List attachments) { return outputs; } - private List createTestSteps(TestCaseData data, List testStepsFinished) { + private List createTestSteps(JvmElementData data, List testStepsFinished) { return testStepsFinished.stream() .map(testStepFinished -> { List beforeStepHooks = data.testStepData.beforeStepStepsByStep @@ -418,22 +430,28 @@ private Optional createTestStep( .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) .map(step -> { - List jvmBeforeStepHooks = createHookSteps(beforeStepHooks); - List jvmAfterStepHooks = createHookSteps(afterStepHooks); return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createMatchMap(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createResultMap(testStepFinished.getTestStepResult()), - step.getDocString().map(this::createDocStringMap).orElse(null), - step.getDataTable().map(this::createDataTableList).orElse(null), - jvmBeforeStepHooks.isEmpty() ? null : jvmBeforeStepHooks, - jvmAfterStepHooks.isEmpty() ? null : jvmAfterStepHooks); + step.getKeyword(), + step.getLocation().getLine(), + createJvmMatch(testStep, testStepFinished.getTestStepResult()), + pickleStep.getText(), + createJvmResult(testStepFinished.getTestStepResult()), + createJvmDocString(step), + createJvmDataTableRows(step), + createHookSteps(beforeStepHooks), + createHookSteps(afterStepHooks)); }))); } - private JvmMatch createMatchMap(TestStep step, TestStepResult result) { + private List createJvmDataTableRows(Step step) { + return step.getDataTable().map(this::createJvmDataTableRows).orElse(null); + } + + private JvmDocString createJvmDocString(Step step) { + return step.getDocString().map(this::createJvmDocString).orElse(null); + } + + private JvmMatch createJvmMatch(TestStep step, TestStepResult result) { Optional source = query.findUnambiguousStepDefinitionBy(step) .map(StepDefinition::getSourceReference); @@ -445,13 +463,13 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(Optional::get) .findFirst().flatMap(sourceReference -> { Optional fromUri = sourceReference.getUri() - .map(uri -> renderLocationString(sourceReference, uri)); + .map(uri -> formatSourceReference(sourceReference, uri)); Optional fromJavaMethod = sourceReference.getJavaMethod() - .map(JsonReportWriter::renderLocationString); + .map(JsonReportWriter::formatSourceReference); Optional fromStackTrace = sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> renderLocationString(sourceReference, javaStackTraceElement)); + .map(javaStackTraceElement -> formatSourceReference(sourceReference, javaStackTraceElement)); return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent) .map(Optional::get) @@ -465,33 +483,33 @@ private JvmMatch createMatchMap(TestStep step, TestStepResult result) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); }).collect(toList())) .filter(maps -> !maps.isEmpty()); return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); + result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, + argumentList.orElse(null)); } - private JvmResult createResultMap(TestStepResult result) { + private JvmResult createJvmResult(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } - private JvmDocString createDocStringMap(DocString docString) { + private JvmDocString createJvmDocString(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } - private List createDataTableList(DataTable argument) { + private List createJvmDataTableRows(DataTable argument) { return argument.getRows().stream() .map(row -> new JvmDataTableRow(row.getCells().stream() .map(TableCell::getValue) @@ -499,26 +517,18 @@ private List createDataTableList(DataTable argument) { .collect(toList()); } - private String getDateTimeFromTimeStamp(Timestamp instant) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - .withZone(ZoneOffset.UTC); - return formatter.format(Convertor.toInstant(instant)); - } - private static class TestStepData { - private final List testSteps; private final List beforeTestCaseSteps; private final List afterTestCaseSteps; private final Map> beforeStepStepsByStep; private final Map> afterStepStepsByStep; private TestStepData( - List testSteps, List beforeTestCaseSteps, + List beforeTestCaseSteps, List afterTestCaseSteps, Map> beforeStepStepsByStep, Map> afterStepStepsByStep ) { - this.testSteps = testSteps; this.beforeTestCaseSteps = beforeTestCaseSteps; this.afterTestCaseSteps = afterTestCaseSteps; this.beforeStepStepsByStep = beforeStepStepsByStep; @@ -526,22 +536,34 @@ private TestStepData( } } - private static class TestCaseData { + private static class JvmElementData { private final TestCaseStarted testCaseStarted; private final Lineage lineage; private final Pickle pickle; private final Location location; private final TestStepData testStepData; - private TestCaseData( + private JvmElementData( TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, TestStepData testStepData ) { - this.testCaseStarted = testCaseStarted; - this.lineage = lineage; - this.pickle = pickle; - this.location = location; - this.testStepData = testStepData; + this.testCaseStarted = requireNonNull(testCaseStarted); + this.lineage = requireNonNull(lineage); + this.pickle = requireNonNull(pickle); + this.location = requireNonNull(location); + this.testStepData = requireNonNull(testStepData); + } + } + + private static class JvmFeatureDataComparator implements Comparator { + + @Override + public int compare(JvmElementData o1, JvmElementData o2) { + int c = o1.pickle.getUri().compareTo(o2.pickle.getUri()); + if (c != 0) { + return c; + } + return o1.location.getLine().compareTo(o2.location.getLine()); } } From 3abb8b3cd717935d74fb72ab04bb4bd8c4ed8231 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 15:01:48 +0200 Subject: [PATCH 23/41] Simplify --- .../core/plugin/JsonReportWriter.java | 255 +++++++++--------- 1 file changed, 131 insertions(+), 124 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index c9f3d021cc..714e52cc24 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -42,7 +42,6 @@ import io.cucumber.messages.types.TestStep; import io.cucumber.messages.types.TestStepFinished; import io.cucumber.messages.types.TestStepResult; -import io.cucumber.messages.types.TestStepResultStatus; import io.cucumber.messages.types.Timestamp; import io.cucumber.query.Lineage; import io.cucumber.query.LineageReducer; @@ -79,7 +78,8 @@ import static java.util.stream.Collectors.toList; class JsonReportWriter { - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneOffset.UTC); + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + .withZone(ZoneOffset.UTC); private final Query query; JsonReportWriter(Query query) { @@ -88,11 +88,11 @@ class JsonReportWriter { private static JvmLocationTag createJvmLocationTag(Tag tag) { return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); } private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { @@ -122,19 +122,19 @@ private static String formatSourceReference( String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); String argumentList = String.join(",", javaStackTraceElement.getFileName()); return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + argumentList, + locationLine); } private static String formatSourceReference(JavaMethod javaMethod) { return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes())); } private static List createJvmLocationTags(Feature feature) { @@ -147,6 +147,37 @@ private static String formatTimeStamp(Timestamp instant) { return formatter.format(Convertor.toInstant(instant)); } + private static List createJvmArguments(TestStep step) { + return step.getStepMatchArgumentsLists() + .map(argumentsLists -> argumentsLists.stream() + .map(StepMatchArgumentsList::getStepMatchArguments) + .flatMap(Collection::stream) + .map(argument -> { + Group group = argument.getGroup(); + return new JvmArgument( + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); + }).collect(toList())) + .filter(maps -> !maps.isEmpty()) + .orElse(null); + } + + private static Optional formatLocation(SourceReference sourceReference) { + Optional fromUri = sourceReference.getUri() + .map(uri -> formatSourceReference(sourceReference, uri)); + + Optional fromJavaMethod = sourceReference.getJavaMethod() + .map(JsonReportWriter::formatSourceReference); + + Optional fromStackTrace = sourceReference.getJavaStackTraceElement() + .map(javaStackTraceElement -> formatSourceReference(sourceReference, javaStackTraceElement)); + + return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } + List writeJsonReport() { return query.findAllTestCaseStarted() .stream() @@ -162,23 +193,22 @@ List writeJsonReport() { private JvmElementData createTestCaseStartedData(TestCaseStarted testCaseStarted) { Pickle pickle = query.findPickleBy(testCaseStarted) .orElseThrow( - () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); Lineage lineage = query.findLineageBy(pickle) .orElseThrow( - () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); Location location = query.findLocationOf(pickle) .orElseThrow( - () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); + () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); TestStepData testStepData = createJvmElementData(testCaseStarted); return new JvmElementData( - testCaseStarted, - lineage, - pickle, - location, - testStepData - ); + testCaseStarted, + lineage, + pickle, + location, + testStepData); } @@ -232,34 +262,34 @@ private JvmFeature createJvmFeature(List elements) { GherkinDocument document = elements.get(0).lineage.document(); Feature feature = elements.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); return new JvmFeature( - // TODO: Relativize, optional?, null? Custom? - TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), - convertToId(feature.getName()), - feature.getLocation().getLine(), - feature.getKeyword(), - feature.getName(), - // TODO: Can this be null? - feature.getDescription() != null ? feature.getDescription() : "", - createJvmElements(elements), - createJvmLocationTags(feature)); + // TODO: Relativize, optional?, null? Custom? + TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), + convertToId(feature.getName()), + feature.getLocation().getLine(), + feature.getKeyword(), + feature.getName(), + // TODO: Can this be null? + feature.getDescription() != null ? feature.getDescription() : "", + createJvmElements(elements), + createJvmLocationTags(feature)); } private List createJvmElements(List entries) { return entries.stream() - .map(this::createTestCaseAndBackGround) + .map(this::createJvmElement) .flatMap(Collection::stream) .collect(toList()); } - private List createTestCaseAndBackGround(JvmElementData data) { - // TODO: Clean up + private List createJvmElement(JvmElementData data) { Predicate, List>> isBackGround = entry -> entry.getKey() .isPresent(); Predicate, List>> isTestCase = isBackGround.negate(); - BinaryOperator, List>> mergeSteps = (a, b) -> { + BinaryOperator, List>> mergeEntries = (a, b) -> { a.getValue().addAll(b.getValue()); return a; }; + Map, List> stepsByBackground = query .findTestStepFinishedAndTestStepBy(data.testCaseStarted) .stream() @@ -270,12 +300,12 @@ private List createTestCaseAndBackGround(JvmElementData data) { // first. Optional background = stepsByBackground.entrySet().stream() .filter(isBackGround) - .reduce(mergeSteps) + .reduce(mergeEntries) .flatMap(entry -> entry.getKey().map(bg -> createBackground(data, bg, entry.getValue()))); Optional testCase = stepsByBackground.entrySet().stream() .filter(isTestCase) - .reduce(mergeSteps) + .reduce(mergeEntries) .map(Entry::getValue) .map(scenarioTestStepsFinished -> createTestCase(data, scenarioTestStepsFinished)); @@ -328,36 +358,34 @@ private JvmElement createBackground( JvmElementData data, Background background, List backgroundTestStepsFinished ) { return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(data, backgroundTestStepsFinished), - null, - null, - null - ); + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(data, backgroundTestStepsFinished), + null, + null, + null); } private JvmElement createTestCase(JvmElementData data, List scenarioTestStepsFinished) { Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); return new JvmElement( - formatTimeStamp(data.testCaseStarted.getTimestamp()), - data.location.getLine(), - idStrategy.reduce(data.lineage), - JvmElementType.scenario, - scenario.getKeyword(), - data.pickle.getName(), - scenario.getDescription() == null ? "" : scenario.getDescription(), - createTestSteps(data, scenarioTestStepsFinished), - createHookSteps(data.testStepData.beforeTestCaseSteps), - createHookSteps(data.testStepData.afterTestCaseSteps), - createJvmTags(data.pickle) - ); + formatTimeStamp(data.testCaseStarted.getTimestamp()), + data.location.getLine(), + idStrategy.reduce(data.lineage), + JvmElementType.scenario, + scenario.getKeyword(), + data.pickle.getName(), + scenario.getDescription() == null ? "" : scenario.getDescription(), + createTestSteps(data, scenarioTestStepsFinished), + createHookSteps(data.testStepData.beforeTestCaseSteps), + createHookSteps(data.testStepData.afterTestCaseSteps), + createJvmTags(data.pickle)); } private List createHookSteps(List testStepsFinished) { @@ -365,10 +393,10 @@ private List createHookSteps(List tes .map(testStepFinished -> query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findHookBy(testStep) .map(hook -> new CucumberJvmJson.JvmHook( - createJvmMatch(testStep, testStepFinished.getTestStepResult()), - createJvmResult(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) + createJvmMatch(testStep), + createJvmResult(testStepFinished.getTestStepResult()), + createEmbeddings(query.findAttachmentsBy(testStepFinished)), + createOutput(query.findAttachmentsBy(testStepFinished)))))) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); @@ -382,9 +410,9 @@ private List createEmbeddings(List att List embeddings = attachments.stream() .filter(attachment -> attachment.getContentEncoding() == BASE64) .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) + attachment.getMediaType(), + attachment.getBody(), + attachment.getFileName().orElse(null))) .collect(toList()); if (embeddings.isEmpty()) { @@ -429,18 +457,22 @@ private Optional createTestStep( return query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) - .map(step -> { - return new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createJvmMatch(testStep, testStepFinished.getTestStepResult()), - pickleStep.getText(), - createJvmResult(testStepFinished.getTestStepResult()), - createJvmDocString(step), - createJvmDataTableRows(step), - createHookSteps(beforeStepHooks), - createHookSteps(afterStepHooks)); - }))); + .map(step -> new JvmStep( + step.getKeyword(), + step.getLocation().getLine(), + createJvmMatch(testStep), + pickleStep.getText(), + createJvmResult(testStepFinished.getTestStepResult()), + createJvmDocString(step), + createJvmDataTableRows(step), + createHookSteps(beforeStepHooks), + createHookSteps(afterStepHooks))))); + } + + private JvmMatch createJvmMatch(TestStep testStep) { + return new JvmMatch( + createLocation(testStep), + createJvmArguments(testStep)); } private List createJvmDataTableRows(Step step) { @@ -451,62 +483,37 @@ private JvmDocString createJvmDocString(Step step) { return step.getDocString().map(this::createJvmDocString).orElse(null); } - private JvmMatch createJvmMatch(TestStep step, TestStepResult result) { + private String createLocation(TestStep step) { + return findSourceReference(step).flatMap(JsonReportWriter::formatLocation).orElse(null); + } + + private Optional findSourceReference(TestStep step) { Optional source = query.findUnambiguousStepDefinitionBy(step) .map(StepDefinition::getSourceReference); Optional hookSource = query.findHookBy(step) .map(Hook::getSourceReference); - Optional location = Stream.of(source, hookSource) + Optional first = Stream.of(source, hookSource) .filter(Optional::isPresent) .map(Optional::get) - .findFirst().flatMap(sourceReference -> { - Optional fromUri = sourceReference.getUri() - .map(uri -> formatSourceReference(sourceReference, uri)); - - Optional fromJavaMethod = sourceReference.getJavaMethod() - .map(JsonReportWriter::formatSourceReference); - - Optional fromStackTrace = sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> formatSourceReference(sourceReference, javaStackTraceElement)); - - return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); - }); - - Optional> argumentList = step.getStepMatchArgumentsLists() - .map(argumentsLists -> argumentsLists.stream() - .map(StepMatchArgumentsList::getStepMatchArguments) - .flatMap(Collection::stream) - .map(argument -> { - Group group = argument.getGroup(); - return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); - }).collect(toList())) - .filter(maps -> !maps.isEmpty()); - - return new JvmMatch( - result.getStatus() != TestStepResultStatus.UNDEFINED ? location.orElse(null) : null, - argumentList.orElse(null)); + .findFirst(); + return first; } private JvmResult createJvmResult(TestStepResult result) { Duration duration = Convertor.toDuration(result.getDuration()); return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private JvmDocString createJvmDocString(DocString docString) { return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); } private List createJvmDataTableRows(DataTable argument) { @@ -554,7 +561,7 @@ private JvmElementData( this.testStepData = requireNonNull(testStepData); } } - + private static class JvmFeatureDataComparator implements Comparator { @Override From 72af87d93030f054b28a6bfab682398bf29469cd Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:00:29 +0200 Subject: [PATCH 24/41] Extract formatters --- .../cucumber/core/plugin/JsonFormatter.java | 9 +- .../core/plugin/JsonReportWriter.java | 94 ++++++------------- .../core/plugin/MessagesToJsonWriter.java | 59 +++++++++++- .../core/plugin/SourceReferenceFormatter.java | 39 ++++++++ 4 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java index 69e5cf3a4a..8f10a62669 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java @@ -4,15 +4,22 @@ import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; +import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.net.URI; + +import static io.cucumber.core.plugin.MessagesToJsonWriter.builder; public final class JsonFormatter implements ConcurrentEventListener { private final MessagesToJsonWriter writer; public JsonFormatter(OutputStream out) { - this.writer = new MessagesToJsonWriter(out, Jackson.OBJECT_MAPPER::writeValue); + URI cwdUri = new File("").toURI(); + this.writer = builder(Jackson.OBJECT_MAPPER::writeValue) + .relativizeAgainst(cwdUri) + .build(out); } @Override diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 714e52cc24..9587a78852 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -24,8 +24,6 @@ import io.cucumber.messages.types.Group; import io.cucumber.messages.types.Hook; import io.cucumber.messages.types.HookType; -import io.cucumber.messages.types.JavaMethod; -import io.cucumber.messages.types.JavaStackTraceElement; import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.PickleStep; @@ -78,15 +76,18 @@ import static java.util.stream.Collectors.toList; class JsonReportWriter { - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") .withZone(ZoneOffset.UTC); private final Query query; + private final SourceReferenceFormatter sourceReferenceFormatter = new SourceReferenceFormatter(); + private final Function uriFormatter; - JsonReportWriter(Query query) { - this.query = query; + JsonReportWriter(Query query, Function uriFormatter) { + this.query = requireNonNull(query); + this.uriFormatter = requireNonNull(uriFormatter); } - private static JvmLocationTag createJvmLocationTag(Tag tag) { + private JvmLocationTag createJvmLocationTag(Tag tag) { return new JvmLocationTag( tag.getName(), "Tag", @@ -95,7 +96,7 @@ private static JvmLocationTag createJvmLocationTag(Tag tag) { tag.getLocation().getColumn().orElse(0L))); } - private static Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { + private Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { return backgrounds.stream() .filter(background -> background.getSteps().stream() .map(Step::getId) @@ -103,7 +104,7 @@ private static Optional findBackgroundBy(List background .findFirst(); } - private static List createJvmTags(Pickle pickle) { + private List createJvmTags(Pickle pickle) { List tags = pickle.getTags() .stream() .map(pickleTag -> new JvmTag(pickleTag.getName())) @@ -111,43 +112,17 @@ private static List createJvmTags(Pickle pickle) { return tags.isEmpty() ? null : tags; } - private static String formatSourceReference(SourceReference sourceReference, String uri) { - String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); - return uri + locationLine; - } - - private static String formatSourceReference( - SourceReference sourceReference, JavaStackTraceElement javaStackTraceElement - ) { - String locationLine = sourceReference.getLocation().map(location -> ":" + location.getLine()).orElse(""); - String argumentList = String.join(",", javaStackTraceElement.getFileName()); - return String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - argumentList, - locationLine); - } - - private static String formatSourceReference(JavaMethod javaMethod) { - return String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes())); - } - - private static List createJvmLocationTags(Feature feature) { + private List createJvmLocationTags(Feature feature) { return feature.getTags().stream() - .map(JsonReportWriter::createJvmLocationTag) + .map(this::createJvmLocationTag) .collect(toList()); } - private static String formatTimeStamp(Timestamp instant) { - return formatter.format(Convertor.toInstant(instant)); + private String formatTimeStamp(Timestamp instant) { + return dateTimeFormatter.format(Convertor.toInstant(instant)); } - private static List createJvmArguments(TestStep step) { + private List createJvmArguments(TestStep step) { return step.getStepMatchArgumentsLists() .map(argumentsLists -> argumentsLists.stream() .map(StepMatchArgumentsList::getStepMatchArguments) @@ -163,19 +138,8 @@ private static List createJvmArguments(TestStep step) { .orElse(null); } - private static Optional formatLocation(SourceReference sourceReference) { - Optional fromUri = sourceReference.getUri() - .map(uri -> formatSourceReference(sourceReference, uri)); - - Optional fromJavaMethod = sourceReference.getJavaMethod() - .map(JsonReportWriter::formatSourceReference); - - Optional fromStackTrace = sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> formatSourceReference(sourceReference, javaStackTraceElement)); - - return Stream.of(fromStackTrace, fromJavaMethod, fromUri).filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); + private Optional formatSourceReference(SourceReference sourceReference) { + return sourceReferenceFormatter.format(sourceReference); } List writeJsonReport() { @@ -262,8 +226,10 @@ private JvmFeature createJvmFeature(List elements) { GherkinDocument document = elements.get(0).lineage.document(); Feature feature = elements.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); return new JvmFeature( - // TODO: Relativize, optional?, null? Custom? - TestSourcesModel.relativize(URI.create(document.getUri().get())).toString(), + document.getUri() + .map(URI::create) + .map(uriFormatter) + .orElse(null), convertToId(feature.getName()), feature.getLocation().getLine(), feature.getKeyword(), @@ -460,7 +426,7 @@ private Optional createTestStep( .map(step -> new JvmStep( step.getKeyword(), step.getLocation().getLine(), - createJvmMatch(testStep), + createJvmMatch(testStep), pickleStep.getText(), createJvmResult(testStepFinished.getTestStepResult()), createJvmDocString(step), @@ -471,8 +437,8 @@ private Optional createTestStep( private JvmMatch createJvmMatch(TestStep testStep) { return new JvmMatch( - createLocation(testStep), - createJvmArguments(testStep)); + createLocation(testStep), + createJvmArguments(testStep)); } private List createJvmDataTableRows(Step step) { @@ -484,21 +450,19 @@ private JvmDocString createJvmDocString(Step step) { } private String createLocation(TestStep step) { - return findSourceReference(step).flatMap(JsonReportWriter::formatLocation).orElse(null); + return findSourceReference(step).flatMap(this::formatSourceReference).orElse(null); } private Optional findSourceReference(TestStep step) { Optional source = query.findUnambiguousStepDefinitionBy(step) .map(StepDefinition::getSourceReference); - Optional hookSource = query.findHookBy(step) - .map(Hook::getSourceReference); + if (source.isPresent()) { + return source; + } - Optional first = Stream.of(source, hookSource) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); - return first; + return query.findHookBy(step) + .map(Hook::getSourceReference); } private JvmResult createJvmResult(TestStepResult result) { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java index bf0b4b2ea7..511c559b2c 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java @@ -7,8 +7,11 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.function.Function; import static java.util.Objects.requireNonNull; @@ -18,18 +21,66 @@ * Note: Messages are first collected and only written once the stream is * closed. */ -public class MessagesToJsonWriter implements AutoCloseable { +class MessagesToJsonWriter implements AutoCloseable { private final OutputStreamWriter out; private final Query query = new Query(); private final Serializer serializer; + private final Function uriFormatter; private boolean streamClosed = false; - public MessagesToJsonWriter(OutputStream out, Serializer serializer) { + private MessagesToJsonWriter(OutputStream out, Serializer serializer, Function uriFormatter) { this.out = new OutputStreamWriter( requireNonNull(out), StandardCharsets.UTF_8); - this.serializer = serializer; + this.serializer = requireNonNull(serializer); + this.uriFormatter = requireNonNull(uriFormatter); + } + + public static Builder builder(Serializer serializer) { + return new Builder(serializer); + } + + static final class Builder { + private final Serializer serializer; + private Function uriFormatter = URI::toString; + + public Builder(Serializer serializer) { + this.serializer = requireNonNull(serializer); + } + + public Builder relativizeAgainst(URI uri) { + // TODO: Needs coverage + // TODO: Naming? + this.uriFormatter = relativize(uri) + .andThen(URI::toString); + return this; + } + + static Function relativize(URI base) { + return uri -> { + // TODO: Needs coverage + if (!"file".equals(uri.getScheme())) { + return uri; + } + if (!uri.isAbsolute()) { + return uri; + } + + try { + URI relative = base.relativize(uri); + // Scheme is lost by relativize + return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + }; + } + + public MessagesToJsonWriter build(OutputStream out) { + requireNonNull(out); + return new MessagesToJsonWriter(out, serializer, uriFormatter); + } } /** @@ -58,7 +109,7 @@ public void close() throws IOException { return; } try { - List report = new JsonReportWriter(query).writeJsonReport(); + List report = new JsonReportWriter(query, uriFormatter).writeJsonReport(); serializer.writeValue(out, report); } finally { try { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java new file mode 100644 index 0000000000..bfe89b2a5f --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java @@ -0,0 +1,39 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.Location; +import io.cucumber.messages.types.SourceReference; + +import java.util.Optional; + +final class SourceReferenceFormatter { + + SourceReferenceFormatter() { + } + + Optional format(SourceReference sourceReference) { + if (sourceReference.getJavaMethod().isPresent()) { + return sourceReference.getJavaMethod() + .map(javaMethod -> String.format( + "%s#%s(%s)", + javaMethod.getClassName(), + javaMethod.getMethodName(), + String.join(",", javaMethod.getMethodParameterTypes()))); + } + if (sourceReference.getJavaStackTraceElement().isPresent()) { + return sourceReference.getJavaStackTraceElement() + .map(javaStackTraceElement -> String.format( + "%s#%s(%s%s)", + javaStackTraceElement.getClassName(), + javaStackTraceElement.getMethodName(), + javaStackTraceElement.getFileName(), + sourceReference.getLocation().map(Location::getLine).map(line -> ":" + line).orElse(""))); + } + if (sourceReference.getUri().isPresent()) { + return sourceReference.getUri() + .map(uri -> uri + sourceReference.getLocation() + .map(location -> ":" + location.getLine()) + .orElse("")); + } + return Optional.empty(); + } +} From e96fad15a0ba363c37eb944012dd448d002d6341 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:22:20 +0200 Subject: [PATCH 25/41] Order and formatting --- .../core/plugin/JsonReportWriter.java | 318 +++++++++--------- 1 file changed, 162 insertions(+), 156 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 9587a78852..201f5b4369 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -27,6 +27,7 @@ import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.PickleStep; +import io.cucumber.messages.types.PickleTag; import io.cucumber.messages.types.Rule; import io.cucumber.messages.types.RuleChild; import io.cucumber.messages.types.Scenario; @@ -56,16 +57,17 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collector; import java.util.stream.Stream; -import static io.cucumber.core.plugin.TestSourcesModel.convertToId; import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; import static java.util.Collections.emptyList; @@ -76,6 +78,7 @@ import static java.util.stream.Collectors.toList; class JsonReportWriter { + private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") .withZone(ZoneOffset.UTC); private final Query query; @@ -87,66 +90,24 @@ class JsonReportWriter { this.uriFormatter = requireNonNull(uriFormatter); } - private JvmLocationTag createJvmLocationTag(Tag tag) { - return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); + private static List nullIfEmpty(List list) { + return list.isEmpty() ? null : list; } - private Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { - return backgrounds.stream() - .filter(background -> background.getSteps().stream() - .map(Step::getId) - .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) - .findFirst(); - } - - private List createJvmTags(Pickle pickle) { - List tags = pickle.getTags() - .stream() - .map(pickleTag -> new JvmTag(pickleTag.getName())) - .collect(toList()); - return tags.isEmpty() ? null : tags; - } - - private List createJvmLocationTags(Feature feature) { - return feature.getTags().stream() - .map(this::createJvmLocationTag) - .collect(toList()); + private String formatId(String name) { + return replacementPattern.matcher(name).replaceAll("-").toLowerCase(Locale.ROOT); } private String formatTimeStamp(Timestamp instant) { return dateTimeFormatter.format(Convertor.toInstant(instant)); } - private List createJvmArguments(TestStep step) { - return step.getStepMatchArgumentsLists() - .map(argumentsLists -> argumentsLists.stream() - .map(StepMatchArgumentsList::getStepMatchArguments) - .flatMap(Collection::stream) - .map(argument -> { - Group group = argument.getGroup(); - return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); - }).collect(toList())) - .filter(maps -> !maps.isEmpty()) - .orElse(null); - } - - private Optional formatSourceReference(SourceReference sourceReference) { - return sourceReferenceFormatter.format(sourceReference); - } - List writeJsonReport() { return query.findAllTestCaseStarted() .stream() - .map(this::createTestCaseStartedData) + .map(this::createJvmElementData) .sorted(new JvmFeatureDataComparator()) + // Preserve order with linked hashmap .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) .values() .stream() @@ -154,7 +115,7 @@ List writeJsonReport() { .collect(toList()); } - private JvmElementData createTestCaseStartedData(TestCaseStarted testCaseStarted) { + private JvmElementData createJvmElementData(TestCaseStarted testCaseStarted) { Pickle pickle = query.findPickleBy(testCaseStarted) .orElseThrow( () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); @@ -165,18 +126,15 @@ private JvmElementData createTestCaseStartedData(TestCaseStarted testCaseStarted .orElseThrow( () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); - TestStepData testStepData = createJvmElementData(testCaseStarted); - return new JvmElementData( testCaseStarted, lineage, pickle, location, - testStepData); - + createTestStepData(testCaseStarted)); } - private TestStepData createJvmElementData(TestCaseStarted testCaseStarted) { + private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { List testStepsFinished = query.findTestStepsFinishedBy(testCaseStarted); List beforeTestCase = new ArrayList<>(); @@ -230,7 +188,7 @@ private JvmFeature createJvmFeature(List elements) { .map(URI::create) .map(uriFormatter) .orElse(null), - convertToId(feature.getName()), + formatId(feature.getName()), feature.getLocation().getLine(), feature.getKeyword(), feature.getName(), @@ -259,7 +217,7 @@ private List createJvmElement(JvmElementData data) { Map, List> stepsByBackground = query .findTestStepFinishedAndTestStepBy(data.testCaseStarted) .stream() - .collect(groupByBackground(data)); + .collect(groupTestStepsByBackground(data)); // There can be multiple backgrounds, but historically the json format // only ever had one. So we group all other backgrounds steps with the @@ -267,7 +225,8 @@ private List createJvmElement(JvmElementData data) { Optional background = stepsByBackground.entrySet().stream() .filter(isBackGround) .reduce(mergeEntries) - .flatMap(entry -> entry.getKey().map(bg -> createBackground(data, bg, entry.getValue()))); + .flatMap(entry -> entry.getKey() + .map(bg -> createBackground(data, bg, entry.getValue()))); Optional testCase = stepsByBackground.entrySet().stream() .filter(isTestCase) @@ -281,62 +240,6 @@ private List createJvmElement(JvmElementData data) { .collect(toList()); } - private Collector, ?, Map, List>> groupByBackground( - JvmElementData data - ) { - List backgrounds = data.lineage.feature() - .map(this::getBackgroundsBy) - .orElseGet(Collections::emptyList); - - Function, Optional> grouping = entry -> query - .findPickleStepBy(entry.getValue()) - .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); - - Function>, List> extractKey = entries -> entries - .stream() - .map(Entry::getKey) - .collect(toList()); - - return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); - } - - private List getBackgroundsBy(Feature feature) { - return feature.getChildren() - .stream() - .map(featureChild -> { - List backgrounds = new ArrayList<>(); - featureChild.getBackground().ifPresent(backgrounds::add); - featureChild.getRule() - .map(Rule::getChildren) - .map(Collection::stream) - .orElseGet(Stream::empty) - .map(RuleChild::getBackground) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(backgrounds::add); - return backgrounds; - }) - .flatMap(Collection::stream) - .collect(toList()); - } - - private JvmElement createBackground( - JvmElementData data, Background background, List backgroundTestStepsFinished - ) { - return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(data, backgroundTestStepsFinished), - null, - null, - null); - } - private JvmElement createTestCase(JvmElementData data, List scenarioTestStepsFinished) { Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); @@ -349,13 +252,24 @@ private JvmElement createTestCase(JvmElementData data, List sc data.pickle.getName(), scenario.getDescription() == null ? "" : scenario.getDescription(), createTestSteps(data, scenarioTestStepsFinished), - createHookSteps(data.testStepData.beforeTestCaseSteps), - createHookSteps(data.testStepData.afterTestCaseSteps), - createJvmTags(data.pickle)); + nullIfEmpty(createHookSteps(data.testStepData.beforeTestCaseSteps)), + nullIfEmpty(createHookSteps(data.testStepData.afterTestCaseSteps)), + nullIfEmpty(createJvmTags(data.pickle))); + } + + private List createJvmTags(Pickle pickle) { + return pickle.getTags() + .stream() + .map(this::createJvmTag) + .collect(toList()); + } + + private JvmTag createJvmTag(PickleTag pickleTag) { + return new JvmTag(pickleTag.getName()); } private List createHookSteps(List testStepsFinished) { - List hooks = testStepsFinished.stream() + return testStepsFinished.stream() .map(testStepFinished -> query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findHookBy(testStep) .map(hook -> new CucumberJvmJson.JvmHook( @@ -366,7 +280,14 @@ private List createHookSteps(List tes .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); - return hooks.isEmpty() ? null : hooks; + } + + private JvmResult createJvmResult(TestStepResult result) { + Duration duration = Convertor.toDuration(result.getDuration()); + return new JvmResult( + duration.isZero() ? null : duration.toNanos(), + JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), + result.getException().flatMap(Exception::getStackTrace).orElse(null)); } private List createEmbeddings(List attachments) { @@ -402,24 +323,52 @@ private List createOutput(List attachments) { return outputs; } + private List createJvmLocationTags(Feature feature) { + return feature.getTags().stream() + .map(this::createJvmLocationTag) + .collect(toList()); + } + + private JvmLocationTag createJvmLocationTag(Tag tag) { + return new JvmLocationTag( + tag.getName(), + "Tag", + new JvmLocation( + tag.getLocation().getLine(), + tag.getLocation().getColumn().orElse(0L))); + } + + private JvmElement createBackground( + JvmElementData data, Background background, List backgroundTestStepsFinished + ) { + return new JvmElement( + null, + background.getLocation().getLine(), + null, + JvmElementType.background, + background.getKeyword(), + background.getName(), + background.getDescription() != null ? background.getDescription() : "", + createTestSteps(data, backgroundTestStepsFinished), + null, + null, + null); + } + private List createTestSteps(JvmElementData data, List testStepsFinished) { return testStepsFinished.stream() - .map(testStepFinished -> { - List beforeStepHooks = data.testStepData.beforeStepStepsByStep - .getOrDefault(testStepFinished, emptyList()); - List afterStepHooks = data.testStepData.afterStepStepsByStep - .getOrDefault(testStepFinished, emptyList()); - return createTestStep(testStepFinished, beforeStepHooks, afterStepHooks); - }) + .map(testStepFinished -> createTestStep(data, testStepFinished)) .filter(Optional::isPresent) .map(Optional::get) .collect(toList()); } - private Optional createTestStep( - TestStepFinished testStepFinished, List beforeStepHooks, - List afterStepHooks - ) { + private Optional createTestStep(JvmElementData data, TestStepFinished testStepFinished) { + List beforeStepHooks = data.testStepData.beforeStepStepsByStep + .getOrDefault(testStepFinished, emptyList()); + List afterStepHooks = data.testStepData.afterStepStepsByStep + .getOrDefault(testStepFinished, emptyList()); + return query.findTestStepBy(testStepFinished) .flatMap(testStep -> query.findPickleStepBy(testStep) .flatMap(pickleStep -> query.findStepBy(pickleStep) @@ -431,8 +380,8 @@ private Optional createTestStep( createJvmResult(testStepFinished.getTestStepResult()), createJvmDocString(step), createJvmDataTableRows(step), - createHookSteps(beforeStepHooks), - createHookSteps(afterStepHooks))))); + nullIfEmpty(createHookSteps(beforeStepHooks)), + nullIfEmpty(createHookSteps(afterStepHooks)))))); } private JvmMatch createJvmMatch(TestStep testStep) { @@ -441,16 +390,96 @@ private JvmMatch createJvmMatch(TestStep testStep) { createJvmArguments(testStep)); } + private List createJvmArguments(TestStep step) { + return step.getStepMatchArgumentsLists() + .map(argumentsLists -> argumentsLists.stream() + .map(StepMatchArgumentsList::getStepMatchArguments) + .flatMap(Collection::stream) + .map(argument -> { + Group group = argument.getGroup(); + return new JvmArgument( + // TODO: Nullable + group.getValue().get(), + group.getStart().get()); + }).collect(toList())) + .filter(maps -> !maps.isEmpty()) + .orElse(null); + } + private List createJvmDataTableRows(Step step) { return step.getDataTable().map(this::createJvmDataTableRows).orElse(null); } + private List createJvmDataTableRows(DataTable argument) { + return argument.getRows().stream() + .map(row -> new JvmDataTableRow(row.getCells().stream() + .map(TableCell::getValue) + .collect(toList()))) + .collect(toList()); + } + private JvmDocString createJvmDocString(Step step) { return step.getDocString().map(this::createJvmDocString).orElse(null); } + private JvmDocString createJvmDocString(DocString docString) { + return new JvmDocString( + docString.getLocation().getLine(), + docString.getContent(), + docString.getMediaType().orElse(null)); + } + private String createLocation(TestStep step) { - return findSourceReference(step).flatMap(this::formatSourceReference).orElse(null); + return findSourceReference(step) + .flatMap(sourceReferenceFormatter::format) + .orElse(null); + } + + private Collector, ?, Map, List>> groupTestStepsByBackground( + JvmElementData data + ) { + List backgrounds = data.lineage.feature() + .map(this::findBackgroundsBy) + .orElseGet(Collections::emptyList); + + Function, Optional> grouping = entry -> query + .findPickleStepBy(entry.getValue()) + .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); + + Function>, List> extractKey = entries -> entries + .stream() + .map(Entry::getKey) + .collect(toList()); + + return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); + } + + private List findBackgroundsBy(Feature feature) { + return feature.getChildren() + .stream() + .map(featureChild -> { + List backgrounds = new ArrayList<>(); + featureChild.getBackground().ifPresent(backgrounds::add); + featureChild.getRule() + .map(Rule::getChildren) + .map(Collection::stream) + .orElseGet(Stream::empty) + .map(RuleChild::getBackground) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(backgrounds::add); + return backgrounds; + }) + .flatMap(Collection::stream) + .collect(toList()); + } + + private Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { + return backgrounds.stream() + .filter(background -> background.getSteps().stream() + .map(Step::getId) + .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) + .findFirst(); } private Optional findSourceReference(TestStep step) { @@ -465,29 +494,6 @@ private Optional findSourceReference(TestStep step) { .map(Hook::getSourceReference); } - private JvmResult createJvmResult(TestStepResult result) { - Duration duration = Convertor.toDuration(result.getDuration()); - return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); - } - - private JvmDocString createJvmDocString(DocString docString) { - return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); - } - - private List createJvmDataTableRows(DataTable argument) { - return argument.getRows().stream() - .map(row -> new JvmDataTableRow(row.getCells().stream() - .map(TableCell::getValue) - .collect(toList()))) - .collect(toList()); - } - private static class TestStepData { private final List beforeTestCaseSteps; private final List afterTestCaseSteps; From 431fe2d94651bebf38db5c93d4f03f292d49ae09 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:26:26 +0200 Subject: [PATCH 26/41] Resolve nulls --- .../main/java/io/cucumber/core/plugin/JsonReportWriter.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 201f5b4369..efb756082f 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -398,9 +398,8 @@ private List createJvmArguments(TestStep step) { .map(argument -> { Group group = argument.getGroup(); return new JvmArgument( - // TODO: Nullable - group.getValue().get(), - group.getStart().get()); + group.getValue().orElse(null), + group.getStart().orElse(-1L)); }).collect(toList())) .filter(maps -> !maps.isEmpty()) .orElse(null); From f89a08447de9029e065317371d3b6765c3bee44c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:28:15 +0200 Subject: [PATCH 27/41] Resolve nulls --- .../cucumber/core/plugin/JsonReportWriter.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index efb756082f..695d94a2d1 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -34,6 +34,7 @@ import io.cucumber.messages.types.SourceReference; import io.cucumber.messages.types.Step; import io.cucumber.messages.types.StepDefinition; +import io.cucumber.messages.types.StepMatchArgument; import io.cucumber.messages.types.StepMatchArgumentsList; import io.cucumber.messages.types.TableCell; import io.cucumber.messages.types.Tag; @@ -395,16 +396,19 @@ private List createJvmArguments(TestStep step) { .map(argumentsLists -> argumentsLists.stream() .map(StepMatchArgumentsList::getStepMatchArguments) .flatMap(Collection::stream) - .map(argument -> { - Group group = argument.getGroup(); - return new JvmArgument( - group.getValue().orElse(null), - group.getStart().orElse(-1L)); - }).collect(toList())) - .filter(maps -> !maps.isEmpty()) + .map(JsonReportWriter::createJvmArgument) + .collect(toList())) + .filter(jvmArguments -> !jvmArguments.isEmpty()) .orElse(null); } + private static JvmArgument createJvmArgument(StepMatchArgument argument) { + Group group = argument.getGroup(); + return new JvmArgument( + group.getValue().orElse(null), + group.getStart().orElse(-1L)); + } + private List createJvmDataTableRows(Step step) { return step.getDataTable().map(this::createJvmDataTableRows).orElse(null); } From 5fe806e615d32a8d600ae9fb2f8cf867d51ba8fc Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:28:41 +0200 Subject: [PATCH 28/41] Resolve nulls --- .../main/java/io/cucumber/core/plugin/JsonReportWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 695d94a2d1..c7049a7fd6 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -396,13 +396,13 @@ private List createJvmArguments(TestStep step) { .map(argumentsLists -> argumentsLists.stream() .map(StepMatchArgumentsList::getStepMatchArguments) .flatMap(Collection::stream) - .map(JsonReportWriter::createJvmArgument) + .map(this::createJvmArgument) .collect(toList())) .filter(jvmArguments -> !jvmArguments.isEmpty()) .orElse(null); } - private static JvmArgument createJvmArgument(StepMatchArgument argument) { + private JvmArgument createJvmArgument(StepMatchArgument argument) { Group group = argument.getGroup(); return new JvmArgument( group.getValue().orElse(null), From 90bc96f21c3d5cf0084b44b5160e3433ae3f0f75 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 16:34:34 +0200 Subject: [PATCH 29/41] Extract inners --- .../core/plugin/JsonReportWriter.java | 51 ------------------- .../cucumber/core/plugin/JvmElementData.java | 27 ++++++++++ .../core/plugin/JvmFeatureDataComparator.java | 15 ++++++ .../io/cucumber/core/plugin/TestStepData.java | 25 +++++++++ 4 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java create mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index c7049a7fd6..182f10f383 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -54,7 +54,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -497,54 +496,4 @@ private Optional findSourceReference(TestStep step) { .map(Hook::getSourceReference); } - private static class TestStepData { - private final List beforeTestCaseSteps; - private final List afterTestCaseSteps; - private final Map> beforeStepStepsByStep; - private final Map> afterStepStepsByStep; - - private TestStepData( - List beforeTestCaseSteps, - List afterTestCaseSteps, - Map> beforeStepStepsByStep, - Map> afterStepStepsByStep - ) { - this.beforeTestCaseSteps = beforeTestCaseSteps; - this.afterTestCaseSteps = afterTestCaseSteps; - this.beforeStepStepsByStep = beforeStepStepsByStep; - this.afterStepStepsByStep = afterStepStepsByStep; - } - } - - private static class JvmElementData { - private final TestCaseStarted testCaseStarted; - private final Lineage lineage; - private final Pickle pickle; - private final Location location; - private final TestStepData testStepData; - - private JvmElementData( - TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, - TestStepData testStepData - ) { - this.testCaseStarted = requireNonNull(testCaseStarted); - this.lineage = requireNonNull(lineage); - this.pickle = requireNonNull(pickle); - this.location = requireNonNull(location); - this.testStepData = requireNonNull(testStepData); - } - } - - private static class JvmFeatureDataComparator implements Comparator { - - @Override - public int compare(JvmElementData o1, JvmElementData o2) { - int c = o1.pickle.getUri().compareTo(o2.pickle.getUri()); - if (c != 0) { - return c; - } - return o1.location.getLine().compareTo(o2.location.getLine()); - } - } - } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java new file mode 100644 index 0000000000..3b8b84bbee --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java @@ -0,0 +1,27 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.Location; +import io.cucumber.messages.types.Pickle; +import io.cucumber.messages.types.TestCaseStarted; +import io.cucumber.query.Lineage; + +import static java.util.Objects.requireNonNull; + +class JvmElementData { + final TestCaseStarted testCaseStarted; + final Lineage lineage; + final Pickle pickle; + final Location location; + final TestStepData testStepData; + + JvmElementData( + TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, + TestStepData testStepData + ) { + this.testCaseStarted = requireNonNull(testCaseStarted); + this.lineage = requireNonNull(lineage); + this.pickle = requireNonNull(pickle); + this.location = requireNonNull(location); + this.testStepData = requireNonNull(testStepData); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java new file mode 100644 index 0000000000..84a6c71069 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java @@ -0,0 +1,15 @@ +package io.cucumber.core.plugin; + +import java.util.Comparator; + +class JvmFeatureDataComparator implements Comparator { + + @Override + public int compare(JvmElementData o1, JvmElementData o2) { + int c = o1.pickle.getUri().compareTo(o2.pickle.getUri()); + if (c != 0) { + return c; + } + return o1.location.getLine().compareTo(o2.location.getLine()); + } +} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java new file mode 100644 index 0000000000..c70b0f5177 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java @@ -0,0 +1,25 @@ +package io.cucumber.core.plugin; + +import io.cucumber.messages.types.TestStepFinished; + +import java.util.List; +import java.util.Map; + +class TestStepData { + final List beforeTestCaseSteps; + final List afterTestCaseSteps; + final Map> beforeStepStepsByStep; + final Map> afterStepStepsByStep; + + TestStepData( + List beforeTestCaseSteps, + List afterTestCaseSteps, + Map> beforeStepStepsByStep, + Map> afterStepStepsByStep + ) { + this.beforeTestCaseSteps = beforeTestCaseSteps; + this.afterTestCaseSteps = afterTestCaseSteps; + this.beforeStepStepsByStep = beforeStepStepsByStep; + this.afterStepStepsByStep = afterStepStepsByStep; + } +} From b504efdceba9b4cd59a8eff3cd3e5ff9a81b472b Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 17:41:57 +0200 Subject: [PATCH 30/41] Touch ups --- .../core/plugin/JsonReportWriter.java | 41 ++++++++++++------- .../core/plugin/MessagesToJsonWriter.java | 2 +- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 182f10f383..141bb380a1 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -102,7 +102,7 @@ private String formatTimeStamp(Timestamp instant) { return dateTimeFormatter.format(Convertor.toInstant(instant)); } - List writeJsonReport() { + List createJsonReport() { return query.findAllTestCaseStarted() .stream() .map(this::createJvmElementData) @@ -181,8 +181,12 @@ private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { } private JvmFeature createJvmFeature(List elements) { - GherkinDocument document = elements.get(0).lineage.document(); - Feature feature = elements.get(0).lineage.feature().orElseThrow(() -> new IllegalStateException("No feature?")); + JvmElementData firstElement = elements.get(0); + GherkinDocument document = firstElement.lineage.document(); + Feature feature = firstElement.lineage.feature() + .orElseThrow( + () -> new IllegalStateException( + "No Feature for testCaseStarted " + firstElement.testCaseStarted.getId())); return new JvmFeature( document.getUri() .map(URI::create) @@ -206,14 +210,6 @@ private List createJvmElements(List entries) { } private List createJvmElement(JvmElementData data) { - Predicate, List>> isBackGround = entry -> entry.getKey() - .isPresent(); - Predicate, List>> isTestCase = isBackGround.negate(); - BinaryOperator, List>> mergeEntries = (a, b) -> { - a.getValue().addAll(b.getValue()); - return a; - }; - Map, List> stepsByBackground = query .findTestStepFinishedAndTestStepBy(data.testCaseStarted) .stream() @@ -223,14 +219,14 @@ private List createJvmElement(JvmElementData data) { // only ever had one. So we group all other backgrounds steps with the // first. Optional background = stepsByBackground.entrySet().stream() - .filter(isBackGround) - .reduce(mergeEntries) + .filter(isBackGround()) + .reduce(mergeEntries()) .flatMap(entry -> entry.getKey() .map(bg -> createBackground(data, bg, entry.getValue()))); Optional testCase = stepsByBackground.entrySet().stream() - .filter(isTestCase) - .reduce(mergeEntries) + .filter(isTestCase()) + .reduce(mergeEntries()) .map(Entry::getValue) .map(scenarioTestStepsFinished -> createTestCase(data, scenarioTestStepsFinished)); @@ -240,6 +236,21 @@ private List createJvmElement(JvmElementData data) { .collect(toList()); } + private static BinaryOperator, List>> mergeEntries() { + return (a, b) -> { + a.getValue().addAll(b.getValue()); + return a; + }; + } + + private static Predicate, List>> isTestCase() { + return isBackGround().negate(); + } + + private static Predicate, List>> isBackGround() { + return entry -> entry.getKey().isPresent(); + } + private JvmElement createTestCase(JvmElementData data, List scenarioTestStepsFinished) { Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java index 511c559b2c..b53f46a10e 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java @@ -109,7 +109,7 @@ public void close() throws IOException { return; } try { - List report = new JsonReportWriter(query, uriFormatter).writeJsonReport(); + List report = new JsonReportWriter(query, uriFormatter).createJsonReport(); serializer.writeValue(out, report); } finally { try { From 2e1fc68cc50ae7ac9ac7c74c5e5b52472249cd7c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 17:48:11 +0200 Subject: [PATCH 31/41] Remove old --- .../core/plugin/JsonFormatterOld.java | 442 ----- .../core/plugin/JsonFormatterOldTest.java | 1549 ----------------- 2 files changed, 1991 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java delete mode 100644 cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java deleted file mode 100644 index 8b6d943480..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatterOld.java +++ /dev/null @@ -1,442 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Background; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.Scenario; -import io.cucumber.messages.types.Step; -import io.cucumber.plugin.EventListener; -import io.cucumber.plugin.event.Argument; -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.Status; -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.TestStep; -import io.cucumber.plugin.event.TestStepFinished; -import io.cucumber.plugin.event.TestStepStarted; -import io.cucumber.plugin.event.WriteEvent; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Writer; -import java.net.URI; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static io.cucumber.core.exception.ExceptionUtils.printStackTrace; -import static io.cucumber.core.plugin.TestSourcesModel.getBackgroundForTestCase; -import static java.util.Collections.singletonList; -import static java.util.Locale.ROOT; -import static java.util.stream.Collectors.toList; - -public final class JsonFormatterOld implements EventListener { - - private static final String before = "before"; - private static final String after = "after"; - private final List> featureMaps = new ArrayList<>(); - private final Map currentBeforeStepHookList = new HashMap<>(); - private final Writer writer; - private final TestSourcesModel testSources = new TestSourcesModel(); - private URI currentFeatureFile; - private List> currentElementsList; - private Map currentElementMap; - private Map currentTestCaseMap; - private List> currentStepsList; - private Map currentStepOrHookMap; - - @SuppressWarnings("WeakerAccess") // Used by PluginFactory - public JsonFormatterOld(OutputStream out) { - this.writer = new UTF8OutputStreamWriter(out); - } - - @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(WriteEvent.class, this::handleWrite); - publisher.registerHandlerFor(EmbedEvent.class, this::handleEmbed); - publisher.registerHandlerFor(TestRunFinished.class, this::finishReport); - } - - private void handleTestSourceRead(TestSourceRead event) { - testSources.addTestSourceReadEvent(event.getUri(), event); - } - - @SuppressWarnings("unchecked") - private void handleTestCaseStarted(TestCaseStarted event) { - if (currentFeatureFile == null || !currentFeatureFile.equals(event.getTestCase().getUri())) { - currentFeatureFile = event.getTestCase().getUri(); - Map currentFeatureMap = createFeatureMap(event.getTestCase()); - featureMaps.add(currentFeatureMap); - currentElementsList = (List>) currentFeatureMap.get("elements"); - } - currentTestCaseMap = createTestCase(event); - if (testSources.hasBackground(currentFeatureFile, event.getTestCase().getLocation().getLine())) { - currentElementMap = createBackground(event.getTestCase()); - currentElementsList.add(currentElementMap); - } else { - currentElementMap = currentTestCaseMap; - } - currentElementsList.add(currentTestCaseMap); - currentStepsList = (List>) currentElementMap.get("steps"); - } - - @SuppressWarnings("unchecked") - private void handleTestStepStarted(TestStepStarted event) { - if (event.getTestStep() instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); - if (isFirstStepAfterBackground(testStep)) { - currentElementMap = currentTestCaseMap; - currentStepsList = (List>) currentElementMap.get("steps"); - } - currentStepOrHookMap = createTestStep(testStep); - // add beforeSteps list to current step - if (currentBeforeStepHookList.containsKey(before)) { - currentStepOrHookMap.put(before, currentBeforeStepHookList.get(before)); - currentBeforeStepHookList.clear(); - } - currentStepsList.add(currentStepOrHookMap); - } else if (event.getTestStep() instanceof HookTestStep) { - HookTestStep hookTestStep = (HookTestStep) event.getTestStep(); - currentStepOrHookMap = createHookStep(hookTestStep); - addHookStepToTestCaseMap(currentStepOrHookMap, hookTestStep.getHookType()); - } else { - throw new IllegalStateException(); - } - } - - private void handleTestStepFinished(TestStepFinished event) { - currentStepOrHookMap.put("match", createMatchMap(event.getTestStep(), event.getResult())); - currentStepOrHookMap.put("result", createResultMap(event.getResult())); - } - - private void handleWrite(WriteEvent event) { - addOutputToHookMap(event.getText()); - } - - private void handleEmbed(EmbedEvent event) { - addEmbeddingToHookMap(event.getData(), event.getMediaType(), event.getName()); - } - - private void finishReport(TestRunFinished event) { - Throwable exception = event.getResult().getError(); - if (exception != null) { - featureMaps.add(createDummyFeatureForFailure(event)); - } - - try { - Jackson.OBJECT_MAPPER.writeValue(writer, featureMaps); - writer.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private Map createFeatureMap(TestCase testCase) { - Map featureMap = new HashMap<>(); - featureMap.put("uri", TestSourcesModel.relativize(testCase.getUri())); - featureMap.put("elements", new ArrayList>()); - 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() : ""); - featureMap.put("line", feature.getLocation().getLine()); - featureMap.put("id", TestSourcesModel.convertToId(feature.getName())); - featureMap.put("tags", feature.getTags().stream().map( - tag -> { - Map json = new LinkedHashMap<>(); - json.put("name", tag.getName()); - json.put("type", "Tag"); - Map location = new LinkedHashMap<>(); - location.put("line", tag.getLocation().getLine()); - location.put("column", tag.getLocation().getColumn()); - json.put("location", location); - return json; - }).collect(toList())); - - } - return featureMap; - } - - private Map createTestCase(TestCaseStarted event) { - Map testCaseMap = new HashMap<>(); - - testCaseMap.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); - - TestCase testCase = event.getTestCase(); - - testCaseMap.put("name", testCase.getName()); - testCaseMap.put("line", testCase.getLine()); - testCaseMap.put("type", "scenario"); - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testCase.getLine()); - if (astNode != null) { - testCaseMap.put("id", TestSourcesModel.calculateId(astNode)); - Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode); - testCaseMap.put("keyword", scenarioDefinition.getKeyword()); - testCaseMap.put("description", - scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : ""); - } - testCaseMap.put("steps", new ArrayList>()); - 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.getLocation().getLine()); - if (astNode != null) { - Background background = getBackgroundForTestCase(astNode).get(); - Map testCaseMap = new HashMap<>(); - testCaseMap.put("name", background.getName()); - testCaseMap.put("line", background.getLocation().getLine()); - testCaseMap.put("type", "background"); - testCaseMap.put("keyword", background.getKeyword()); - testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : ""); - testCaseMap.put("steps", new ArrayList>()); - return testCaseMap; - } - return null; - } - - private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) { - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - if (astNode == null) { - return false; - } - return currentElementMap != currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode); - } - - private Map createTestStep(PickleStepTestStep testStep) { - Map stepMap = new HashMap<>(); - stepMap.put("name", testStep.getStepText()); - stepMap.put("line", testStep.getStepLine()); - TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile, testStep.getStepLine()); - 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)); - } - } - if (astNode != null) { - Step step = (Step) astNode.node; - stepMap.put("keyword", step.getKeyword()); - } - - return stepMap; - } - - private Map createHookStep(HookTestStep hookTestStep) { - return new HashMap<>(); - } - - private void addHookStepToTestCaseMap(Map currentStepOrHookMap, HookType hookType) { - String hookName; - if (hookType == HookType.AFTER || hookType == HookType.AFTER_STEP) - hookName = after; - else - hookName = before; - - Map mapToAddTo; - switch (hookType) { - case BEFORE: - mapToAddTo = currentTestCaseMap; - break; - case AFTER: - mapToAddTo = currentTestCaseMap; - break; - case BEFORE_STEP: - mapToAddTo = currentBeforeStepHookList; - break; - case AFTER_STEP: - mapToAddTo = currentStepsList.get(currentStepsList.size() - 1); - break; - default: - mapToAddTo = currentTestCaseMap; - } - - if (!mapToAddTo.containsKey(hookName)) { - mapToAddTo.put(hookName, new ArrayList>()); - } - ((List>) mapToAddTo.get(hookName)).add(currentStepOrHookMap); - } - - private Map createMatchMap(TestStep step, Result result) { - Map matchMap = new HashMap<>(); - if (step instanceof PickleStepTestStep) { - PickleStepTestStep testStep = (PickleStepTestStep) step; - if (!testStep.getDefinitionArgument().isEmpty()) { - List> argumentList = new ArrayList<>(); - for (Argument argument : testStep.getDefinitionArgument()) { - Map argumentMap = new HashMap<>(); - if (argument.getValue() != null) { - argumentMap.put("val", argument.getValue()); - argumentMap.put("offset", argument.getStart()); - } - argumentList.add(argumentMap); - } - matchMap.put("arguments", argumentList); - } - } - if (!result.getStatus().is(Status.UNDEFINED)) { - matchMap.put("location", step.getCodeLocation()); - } - 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())); - } - if (!result.getDuration().isZero()) { - resultMap.put("duration", result.getDuration().toNanos()); - } - return resultMap; - } - - private void addOutputToHookMap(String text) { - if (!currentStepOrHookMap.containsKey("output")) { - currentStepOrHookMap.put("output", new ArrayList()); - } - ((List) currentStepOrHookMap.get("output")).add(text); - } - - private void addEmbeddingToHookMap(byte[] data, String mediaType, String name) { - if (!currentStepOrHookMap.containsKey("embeddings")) { - currentStepOrHookMap.put("embeddings", new ArrayList>()); - } - Map embedMap = createEmbeddingMap(data, mediaType, name); - ((List>) currentStepOrHookMap.get("embeddings")).add(embedMap); - } - - private Map createDummyFeatureForFailure(TestRunFinished event) { - Throwable exception = event.getResult().getError(); - - Map feature = new LinkedHashMap<>(); - feature.put("line", 1); - { - Map scenario = new LinkedHashMap<>(); - feature.put("elements", singletonList(scenario)); - - scenario.put("start_timestamp", getDateTimeFromTimeStamp(event.getInstant())); - scenario.put("line", 2); - scenario.put("name", "Failure while executing Cucumber"); - scenario.put("description", ""); - scenario.put("id", "failure;failure-while-executing-cucumber"); - scenario.put("type", "scenario"); - scenario.put("keyword", "Scenario"); - - Map when = new LinkedHashMap<>(); - Map then = new LinkedHashMap<>(); - scenario.put("steps", Arrays.asList(when, then)); - { - - { - Map whenResult = new LinkedHashMap<>(); - when.put("result", whenResult); - whenResult.put("duration", 0); - whenResult.put("status", "passed"); - } - when.put("line", 3); - when.put("name", "Cucumber failed while executing"); - Map whenMatch = new LinkedHashMap<>(); - when.put("match", whenMatch); - whenMatch.put("arguments", new ArrayList<>()); - whenMatch.put("location", "io.cucumber.core.Failure.failure_while_executing_cucumber()"); - when.put("keyword", "When "); - - { - Map thenResult = new LinkedHashMap<>(); - then.put("result", thenResult); - thenResult.put("duration", 0); - thenResult.put("error_message", printStackTrace(exception)); - thenResult.put("status", "failed"); - } - then.put("line", 4); - then.put("name", "Cucumber will report this error:"); - Map thenMatch = new LinkedHashMap<>(); - then.put("match", thenMatch); - thenMatch.put("arguments", new ArrayList<>()); - thenMatch.put("location", "io.cucumber.core.Failure.cucumber_reports_this_error()"); - then.put("keyword", "Then "); - } - - feature.put("name", "Test run failed"); - feature.put("description", "There were errors during the execution"); - feature.put("id", "failure"); - feature.put("keyword", "Feature"); - feature.put("uri", "classpath:io/cucumber/core/failure.feature"); - feature.put("tags", new ArrayList<>()); - } - - return feature; - } - - private String getDateTimeFromTimeStamp(Instant instant) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - .withZone(ZoneOffset.UTC); - return formatter.format(instant); - } - - private Map createDocStringMap(DocStringArgument docString) { - Map docStringMap = new HashMap<>(); - docStringMap.put("value", docString.getContent()); - docStringMap.put("line", docString.getLine()); - docStringMap.put("content_type", docString.getMediaType()); - return docStringMap; - } - - private List>> createDataTableList(DataTableArgument argument) { - List>> rowList = new ArrayList<>(); - for (List row : argument.cells()) { - Map> rowMap = new HashMap<>(); - rowMap.put("cells", new ArrayList<>(row)); - rowList.add(rowMap); - } - return rowList; - } - - private Map createEmbeddingMap(byte[] data, String mediaType, String name) { - Map embedMap = new HashMap<>(); - embedMap.put("mime_type", mediaType); // Should be media-type but not - // worth migrating for - embedMap.put("data", Base64.getEncoder().encodeToString(data)); - if (name != null) { - embedMap.put("name", name); - } - return embedMap; - } - -} diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java deleted file mode 100644 index 9b36f4bac6..0000000000 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterOldTest.java +++ /dev/null @@ -1,1549 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.backend.HookDefinition; -import io.cucumber.core.backend.SourceReference; -import io.cucumber.core.backend.StubHookDefinition; -import io.cucumber.core.backend.StubStepDefinition; -import io.cucumber.core.eventbus.IncrementingUuidGenerator; -import io.cucumber.core.feature.TestFeatureParser; -import io.cucumber.core.gherkin.Feature; -import io.cucumber.core.options.RuntimeOptionsBuilder; -import io.cucumber.core.runner.StepDurationTimeService; -import io.cucumber.core.runtime.Runtime; -import io.cucumber.core.runtime.Runtime.Builder; -import io.cucumber.core.runtime.StubBackendSupplier; -import io.cucumber.core.runtime.StubFeatureSupplier; -import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.datatable.DataTable; -import io.cucumber.docstring.DocString; -import org.json.JSONException; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.Scanner; -import java.util.UUID; - -import static io.cucumber.core.backend.HookDefinition.HookType.AFTER_STEP; -import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE; -import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE_STEP; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.Clock.fixed; -import static java.time.Duration.ofMillis; -import static java.time.Instant.EPOCH; -import static java.time.ZoneId.of; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; - -class JsonFormatterOldTest { - - final SourceReference monkeyArrives = getMethod("monkey_arrives"); - final SourceReference thereAreBananas = getMethod("there_are_bananas"); - final SourceReference thereAreOranges = getMethod("there_are_oranges"); - final SourceReference beforeHook1 = getMethod("before_hook_1"); - final SourceReference afterHook1 = getMethod("after_hook_1"); - final SourceReference beforeStepHook1 = getMethod("beforestep_hook_1"); - final SourceReference afterStepHook1 = getMethod("afterstep_hook_1"); - final SourceReference afterStepHook2 = getMethod("afterstep_hook_2"); - - final SourceReference monkeyEatsBananas = getMethod("monkey_eats_bananas"); - final SourceReference monkeyEatsMoreBananas = getMethod("monkey_eats_more_bananas"); - - private static SourceReference getMethod(String name) { - try { - return SourceReference.fromMethod(JsonFormatterTestStepDefinitions.class.getMethod(name)); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @Test - void featureWithOutlineTest() throws JSONException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - createRuntime(out) - .build() - .run(); - - InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - - assertJsonEquals(expected, out); - } - - private Builder createRuntime(ByteArrayOutputStream out) { - Feature feature = TestFeatureParser.parse( - "classpath:io/cucumber/core/plugin/JsonPrettyFormatterTest.feature", - getClass().getResourceAsStream("JsonPrettyFormatterTest.feature")); - - return Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE)), - asList( - new StubStepDefinition("bg_1", getMethod("bg_1")), - new StubStepDefinition("bg_2", getMethod("bg_2")), - new StubStepDefinition("bg_3", getMethod("bg_3")), - new StubStepDefinition("step_1", getMethod("step_1")), - new StubStepDefinition("step_2", getMethod("step_2")), - new StubStepDefinition("step_3", getMethod("step_3")), - new StubStepDefinition("cliché", getMethod("cliche")), - new StubStepDefinition("so_1 {int}", getMethod("so_1"), Integer.class), - new StubStepDefinition("so_2 {int} cucumbers", getMethod("so_2"), Integer.class), - new StubStepDefinition("{int} so_3", getMethod("so_3"), Integer.class), - new StubStepDefinition("a", getMethod("a")), - new StubStepDefinition("b", getMethod("b")), - new StubStepDefinition("c", getMethod("c"))), - emptyList())) - .withAdditionalPlugins(new JsonFormatterOld(out)); - } - - @Test - void featureWithOutlineTestParallel() throws JSONException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - createRuntime(out) - .withRuntimeOptions(new RuntimeOptionsBuilder().setThreads(2).build()) - .build() - .run(); - - InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_an_undefined_step() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier()) - .build() - .run(); - - 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" + - "]"; - assertJsonEquals(expected, out); - } - - private void assertJsonEquals(String expected, ByteArrayOutputStream actual) throws JSONException { - assertJsonEquals(expected, new String(actual.toByteArray(), UTF_8)); - - } - - private void assertJsonEquals(String expected, String actual) throws JSONException { - assertEquals(expected, actual, true); - } - - @Test - void should_format_scenario_with_a_passed_step() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, new IncrementingUuidGenerator())) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_failed_step() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, - new StubException("the stack trace")))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_rule() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Rule: This is all monkey business\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"line\": 4,\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 5,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_rule_and_background() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Background: \n" + - " Given there are bananas\n" + - "\n" + - " Rule: This is all monkey business\n" + - "\n" + - " Background: \n" + - " Given there are bananas\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"line\": 3,\n" + - " \"name\": \"\",\n" + - " \"description\": \"\",\n" + - " \"type\": \"background\",\n" + - " \"keyword\": \"Background\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 4,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 9,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"line\": 11,\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 12,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_outline_with_one_example() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_feature_with_background() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("the monkey eats bananas", monkeyEatsBananas), - new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_feature_and_scenario_with_tags() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_hooks() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, HookDefinition.HookType.BEFORE)), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.AFTER)))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"after\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#after_hook_1()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_add_step_hooks_to_step() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - emptyList(), - singletonList(new StubHookDefinition(beforeStepHook1, BEFORE_STEP)), - asList( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("monkey arrives", monkeyArrives)), - asList( - new StubHookDefinition(afterStepHook1, AFTER_STEP), - new StubHookDefinition(afterStepHook2, AFTER_STEP)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" - + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 4,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" - + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" - + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 5,\n" + - " \"name\": \"monkey arrives\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_arrives()\"\n" - + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" - + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_write_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE, - testCaseState -> testCaseState.log("printed from hook"))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_embed_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, - BEFORE, - testCaseState -> testCaseState - .attach(new byte[] { 1, 2, 3 }, "mime-type;base64", null))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_embed_with_name_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE, - testCaseState -> testCaseState.attach(new byte[] { 1, 2, 3 }, "mime-type;base64", - "someEmbedding"))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, String.class))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " \"\"\"text/plain\n" + - " doc string content\n" + - " \"\"\"\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, DocString.class))) - .build() - .run(); - - 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\": \"text/plain\",\n" + - " \"value\": \"doc string content\",\n" + - " \"line\": 5\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_data_table() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, DataTable.class))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_several_features() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature1, feature2)) - .withAdditionalPlugins(timeService, new JsonFormatterOld(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("there are oranges", thereAreOranges))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_oranges()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } -} From d41694fd917c32fca9118a533cfaf4eded6f5d41 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 17:50:39 +0200 Subject: [PATCH 32/41] Clean up --- .../src/main/java/io/cucumber/core/plugin/TestSourcesModel.java | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java index b34b0f8959..b3e87d4d7c 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java @@ -20,7 +20,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; From 80d4205ea192121f8cad1bdf724ffebdd39e807f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 15 Jul 2025 18:01:52 +0200 Subject: [PATCH 33/41] Clean up --- .../cucumber/core/plugin/IdNamingVisitor.java | 20 +- .../core/plugin/JsonReportWriter.java | 8 +- .../core/plugin/TestSourcesModel.java | 253 ------------------ 3 files changed, 14 insertions(+), 267 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java index db7930d9b1..2a0cd26497 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java @@ -10,31 +10,33 @@ import java.util.ArrayList; import java.util.List; - -import static io.cucumber.core.plugin.TestSourcesModel.convertToId; +import java.util.Locale; +import java.util.regex.Pattern; class IdNamingVisitor implements LineageReducer.Collector { + private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); + private final List parts = new ArrayList<>(); @Override public void add(Feature feature) { - parts.add(convertToId(feature.getName())); + parts.add(formatId(feature.getName())); } @Override public void add(Rule rule) { - parts.add(convertToId(rule.getName())); + parts.add(formatId(rule.getName())); } @Override public void add(Scenario scenario) { - parts.add(convertToId(scenario.getName())); + parts.add(formatId(scenario.getName())); } @Override public void add(Examples examples, int index) { - parts.add(convertToId(examples.getName())); + parts.add(formatId(examples.getName())); } @Override @@ -45,11 +47,15 @@ public void add(TableRow example, int index) { @Override public void add(Pickle pickle) { - convertToId(pickle.getName()); + formatId(pickle.getName()); } @Override public String finish() { return String.join(";", parts); } + + static String formatId(String name) { + return replacementPattern.matcher(name).replaceAll("-").toLowerCase(Locale.ROOT); + } } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 141bb380a1..8446851f83 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -57,17 +57,16 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Predicate; -import java.util.regex.Pattern; import java.util.stream.Collector; import java.util.stream.Stream; +import static io.cucumber.core.plugin.IdNamingVisitor.formatId; import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; import static java.util.Collections.emptyList; @@ -78,7 +77,6 @@ import static java.util.stream.Collectors.toList; class JsonReportWriter { - private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") .withZone(ZoneOffset.UTC); private final Query query; @@ -94,10 +92,6 @@ private static List nullIfEmpty(List list) { return list.isEmpty() ? null : list; } - private String formatId(String name) { - return replacementPattern.matcher(name).replaceAll("-").toLowerCase(Locale.ROOT); - } - private String formatTimeStamp(Timestamp instant) { return dateTimeFormatter.format(Convertor.toInstant(instant)); } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java deleted file mode 100644 index b3e87d4d7c..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java +++ /dev/null @@ -1,253 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.gherkin.GherkinParser; -import io.cucumber.messages.types.Background; -import io.cucumber.messages.types.Envelope; -import io.cucumber.messages.types.Examples; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.FeatureChild; -import io.cucumber.messages.types.GherkinDocument; -import io.cucumber.messages.types.Rule; -import io.cucumber.messages.types.RuleChild; -import io.cucumber.messages.types.Scenario; -import io.cucumber.messages.types.Source; -import io.cucumber.messages.types.SourceMediaType; -import io.cucumber.messages.types.Step; -import io.cucumber.messages.types.TableRow; -import io.cucumber.plugin.event.TestSourceRead; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -final class TestSourcesModel { - - private final Map pathToReadEventMap = new HashMap<>(); - private final Map pathToAstMap = new HashMap<>(); - private final Map> pathToNodeMap = new HashMap<>(); - - static Scenario getScenarioDefinition(AstNode astNode) { - AstNode candidate = astNode; - while (candidate != null && !(candidate.node instanceof Scenario)) { - candidate = candidate.parent; - } - return candidate == null ? null : (Scenario) candidate.node; - } - - static boolean isBackgroundStep(AstNode astNode) { - return astNode.parent.node instanceof Background; - } - - static String calculateId(AstNode astNode) { - Object node = astNode.node; - if (node instanceof Rule) { - return calculateId(astNode.parent) + ";" + convertToId(((Rule) node).getName()); - } - if (node instanceof Scenario) { - return calculateId(astNode.parent) + ";" + convertToId(((Scenario) node).getName()); - } - if (node instanceof ExamplesRowWrapperNode) { - return calculateId(astNode.parent) + ";" + (((ExamplesRowWrapperNode) node).bodyRowIndex + 2); - } - if (node instanceof TableRow) { - return calculateId(astNode.parent) + ";" + 1; - } - if (node instanceof Examples) { - return calculateId(astNode.parent) + ";" + convertToId(((Examples) node).getName()); - } - if (node instanceof Feature) { - return convertToId(((Feature) node).getName()); - } - return ""; - } - - private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); - - static String convertToId(String name) { - return replacementPattern.matcher(name).replaceAll("-").toLowerCase(); - } - - 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); - } - } - - void addTestSourceReadEvent(URI path, TestSourceRead event) { - pathToReadEventMap.put(path, event); - } - - Feature getFeature(URI path) { - if (!pathToAstMap.containsKey(path)) { - parseGherkinSource(path); - } - if (pathToAstMap.containsKey(path)) { - return pathToAstMap.get(path).getFeature().orElse(null); - } - return null; - } - - private void parseGherkinSource(URI path) { - if (!pathToReadEventMap.containsKey(path)) { - return; - } - String source = pathToReadEventMap.get(path).getSource(); - - GherkinParser parser = GherkinParser.builder() - .build(); - - Stream envelopes = parser.parse( - Envelope.of(new Source(path.toString(), source, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN))); - - // TODO: What about empty gherkin docs? - GherkinDocument gherkinDocument = envelopes - .map(Envelope::getGherkinDocument) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst() - .orElse(null); - - pathToAstMap.put(path, gherkinDocument); - Map nodeMap = new HashMap<>(); - // TODO: What about gherkin docs with no features? - Feature feature = gherkinDocument.getFeature().get(); - AstNode currentParent = new AstNode(feature, null); - for (FeatureChild child : feature.getChildren()) { - processFeatureDefinition(nodeMap, child, currentParent); - } - pathToNodeMap.put(path, nodeMap); - - } - - private void processFeatureDefinition(Map nodeMap, FeatureChild child, AstNode currentParent) { - child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); - child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); - child.getRule().ifPresent(rule -> { - AstNode childNode = new AstNode(rule, currentParent); - nodeMap.put(rule.getLocation().getLine(), childNode); - rule.getChildren().forEach(ruleChild -> processRuleDefinition(nodeMap, ruleChild, childNode)); - }); - } - - private void processBackgroundDefinition( - Map nodeMap, Background background, AstNode currentParent - ) { - AstNode childNode = new AstNode(background, currentParent); - nodeMap.put(background.getLocation().getLine(), childNode); - for (Step step : background.getSteps()) { - nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode)); - } - } - - private void processScenarioDefinition(Map nodeMap, Scenario child, AstNode currentParent) { - AstNode childNode = new AstNode(child, currentParent); - nodeMap.put(child.getLocation().getLine(), childNode); - for (io.cucumber.messages.types.Step step : child.getSteps()) { - nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode)); - } - if (!child.getExamples().isEmpty()) { - processScenarioOutlineExamples(nodeMap, child, childNode); - } - } - - private void processRuleDefinition(Map nodeMap, RuleChild child, AstNode currentParent) { - child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); - child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); - } - - private void processScenarioOutlineExamples( - Map nodeMap, Scenario scenarioOutline, AstNode parent - ) { - for (Examples examples : scenarioOutline.getExamples()) { - AstNode examplesNode = new AstNode(examples, parent); - // TODO: Can tables without headers even exist? - TableRow headerRow = examples.getTableHeader().get(); - AstNode headerNode = new AstNode(headerRow, examplesNode); - nodeMap.put(headerRow.getLocation().getLine(), headerNode); - for (int i = 0; i < examples.getTableBody().size(); ++i) { - TableRow examplesRow = examples.getTableBody().get(i); - Object rowNode = new ExamplesRowWrapperNode(examplesRow, i); - AstNode expandedScenarioNode = new AstNode(rowNode, examplesNode); - nodeMap.put(examplesRow.getLocation().getLine(), expandedScenarioNode); - } - } - } - - AstNode getAstNode(URI path, int line) { - if (!pathToNodeMap.containsKey(path)) { - parseGherkinSource(path); - } - if (pathToNodeMap.containsKey(path)) { - return pathToNodeMap.get(path).get((long) line); - } - return null; - } - - boolean hasBackground(URI path, int line) { - if (!pathToNodeMap.containsKey(path)) { - parseGherkinSource(path); - } - if (pathToNodeMap.containsKey(path)) { - AstNode astNode = pathToNodeMap.get(path).get((long) line); - return getBackgroundForTestCase(astNode).isPresent(); - } - return false; - } - - static Optional getBackgroundForTestCase(AstNode astNode) { - Feature feature = getFeatureForTestCase(astNode); - return feature.getChildren() - .stream() - .map(FeatureChild::getBackground) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); - } - - private static Feature getFeatureForTestCase(AstNode astNode) { - while (astNode.parent != null) { - astNode = astNode.parent; - } - return (Feature) astNode.node; - } - - static class ExamplesRowWrapperNode { - - final int bodyRowIndex; - - ExamplesRowWrapperNode(Object examplesRow, int bodyRowIndex) { - this.bodyRowIndex = bodyRowIndex; - } - - } - - static class AstNode { - - final Object node; - final AstNode parent; - - AstNode(Object node, AstNode parent) { - this.node = node; - this.parent = parent; - } - - } - -} From 4b3f08140c3331c67d2f8b804b0449a9abcdc448 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 21 Jul 2025 17:06:18 +0200 Subject: [PATCH 34/41] Use comparators from messages --- cucumber-bom/pom.xml | 2 +- .../io/cucumber/core/plugin/JsonReportWriter.java | 2 +- .../io/cucumber/core/plugin/JvmElementData.java | 8 ++++++++ .../core/plugin/JvmFeatureDataComparator.java | 15 --------------- 4 files changed, 10 insertions(+), 17 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index 168d68414c..e8d14cbc10 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -18,7 +18,7 @@ 33.0.0 21.13.0 0.8.0 - 28.0.0 + 28.1.0 0.3.0 13.5.0 6.1.2 diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java index 8446851f83..71c10717e8 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -100,7 +100,7 @@ List createJsonReport() { return query.findAllTestCaseStarted() .stream() .map(this::createJvmElementData) - .sorted(new JvmFeatureDataComparator()) + .sorted(JvmElementData.comparator) // Preserve order with linked hashmap .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) .values() diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java index 3b8b84bbee..11fdbe9a54 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java @@ -1,13 +1,21 @@ package io.cucumber.core.plugin; +import io.cucumber.messages.LocationComparator; import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.TestCaseStarted; import io.cucumber.query.Lineage; +import java.util.Comparator; + import static java.util.Objects.requireNonNull; class JvmElementData { + static final Comparator comparator = Comparator + .comparing((JvmElementData data) -> data.pickle.getUri()) + // TODO: Use location from pickle + .thenComparing(data -> data.location, new LocationComparator()); + final TestCaseStarted testCaseStarted; final Lineage lineage; final Pickle pickle; diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java deleted file mode 100644 index 84a6c71069..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmFeatureDataComparator.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.cucumber.core.plugin; - -import java.util.Comparator; - -class JvmFeatureDataComparator implements Comparator { - - @Override - public int compare(JvmElementData o1, JvmElementData o2) { - int c = o1.pickle.getUri().compareTo(o2.pickle.getUri()); - if (c != 0) { - return c; - } - return o1.location.getLine().compareTo(o2.location.getLine()); - } -} From eeb93a5f4e2841213e1999aadc776ec892752c7c Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 21 Jul 2025 17:40:02 +0200 Subject: [PATCH 35/41] Extract to library --- cucumber-bom/pom.xml | 6 + cucumber-core/pom.xml | 4 + .../cucumber/core/plugin/CucumberJvmJson.java | 422 --------------- .../cucumber/core/plugin/IdNamingVisitor.java | 61 --- .../cucumber/core/plugin/JsonFormatter.java | 3 +- .../core/plugin/JsonReportWriter.java | 504 ------------------ .../cucumber/core/plugin/JvmElementData.java | 35 -- .../core/plugin/MessagesToJsonWriter.java | 129 ----- .../core/plugin/SourceReferenceFormatter.java | 39 -- .../io/cucumber/core/plugin/TestStepData.java | 25 - 10 files changed, 12 insertions(+), 1216 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index e8d14cbc10..fb35565b9c 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -17,6 +17,7 @@ 18.0.1 33.0.0 21.13.0 + 0.0.1-SNAPSHOT 0.8.0 28.1.0 0.3.0 @@ -48,6 +49,11 @@ html-formatter ${html-formatter.version} + + io.cucumber + json-formatter + ${json-formatter.version} + io.cucumber junit-xml-formatter diff --git a/cucumber-core/pom.xml b/cucumber-core/pom.xml index ca4e9dcb5c..0a66647554 100644 --- a/cucumber-core/pom.xml +++ b/cucumber-core/pom.xml @@ -96,6 +96,10 @@ io.cucumber html-formatter + + io.cucumber + json-formatter + io.cucumber junit-xml-formatter diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java deleted file mode 100644 index 4fef8e2f81..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java +++ /dev/null @@ -1,422 +0,0 @@ -package io.cucumber.core.plugin; - -import java.util.List; - -import static java.util.Objects.requireNonNull; - -/** - * Object representation of cucumber-jvm.json - * schema. - */ -class CucumberJvmJson { - enum JvmElementType { - background, scenario - } - enum JvmStatus { - passed, - failed, - skipped, - undefined, - pending - } - - static class JvmFeature { - private final Long line; - private final String uri; - private final String id; - private final String keyword; - private final String name; - private final String description; - private final List elements; - private final List tags; - - JvmFeature( - String uri, String id, Long line, String keyword, String name, String description, - List elements, List tags - ) { - this.uri = requireNonNull(uri); - this.id = requireNonNull(id); - this.line = requireNonNull(line); - this.keyword = requireNonNull(keyword); - this.name = requireNonNull(name); - this.description = requireNonNull(description); - this.elements = requireNonNull(elements); - this.tags = tags; - } - - public String getUri() { - return uri; - } - - public String getId() { - return id; - } - - public Long getLine() { - return line; - } - - public String getKeyword() { - return keyword; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public List getElements() { - return elements; - } - - public List getTags() { - return tags; - } - } - - static class JvmElement { - private final String start_timestamp; - private final Long line; - private final String id; - private final JvmElementType type; - private final String keyword; - private final String name; - private final String description; - private final List steps; - private final List before; - private final List after; - private final List tags; - - JvmElement( - String start_timestamp, Long line, String id, JvmElementType type, String keyword, String name, - String description, List steps, List before, List after, List tags - ) { - this.start_timestamp = start_timestamp; - this.line = requireNonNull(line); - this.id = id; - this.type = requireNonNull(type); - this.keyword = requireNonNull(keyword); - this.name = requireNonNull(name); - this.description = requireNonNull(description); - this.steps = requireNonNull(steps); - this.before = before; - this.after = after; - this.tags = tags; - } - - public String getStart_timestamp() { - return start_timestamp; - } - - public Long getLine() { - return line; - } - - public String getId() { - return id; - } - - public JvmElementType getType() { - return type; - } - - public String getKeyword() { - return keyword; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public List getSteps() { - return steps; - } - - public List getBefore() { - return before; - } - - public List getAfter() { - return after; - } - - public List getTags() { - return tags; - } - } - - static class JvmStep { - private final String keyword; - private final Long line; - private final JvmMatch match; - private final String name; - private final JvmResult result; - private final JvmDocString doc_string; - private final List rows; - private final List before; - private final List after; - - JvmStep( - String keyword, Long line, JvmMatch match, String name, JvmResult result, JvmDocString doc_string, - List rows, List before, List after - ) { - this.keyword = requireNonNull(keyword); - this.line = requireNonNull(line); - this.match = match; - this.name = requireNonNull(name); - this.result = requireNonNull(result); - this.doc_string = doc_string; - this.rows = rows; - this.before = before; - this.after = after; - } - - public String getKeyword() { - return keyword; - } - - public Long getLine() { - return line; - } - - public JvmMatch getMatch() { - return match; - } - - public String getName() { - return name; - } - - public JvmResult getResult() { - return result; - } - - public JvmDocString getDoc_string() { - return doc_string; - } - - public List getRows() { - return rows; - } - - public List getBefore() { - return before; - } - - public List getAfter() { - return after; - } - } - - static class JvmMatch { - private final String location; - private final List arguments; - - JvmMatch(String location, List arguments) { - this.location = location; - this.arguments = arguments; - } - - public String getLocation() { - return location; - } - - public List getArguments() { - return arguments; - } - } - - static class JvmArgument { - private final String val; - private final Number offset; - - JvmArgument(String val, Number offset) { - this.val = requireNonNull(val); - this.offset = requireNonNull(offset); - } - - public String getVal() { - return val; - } - - public Number getOffset() { - return offset; - } - } - - static class JvmResult { - private final Long duration; - private final JvmStatus status; - private final String error_message; - - JvmResult(Long duration, JvmStatus status, String error_message) { - this.duration = duration; - this.status = requireNonNull(status); - this.error_message = error_message; - } - - public Long getDuration() { - return duration; - } - - public JvmStatus getStatus() { - return status; - } - - public String getError_message() { - return error_message; - } - } - - static class JvmDocString { - private final Long line; - private final String value; - private final String content_type; - - JvmDocString(Long line, String value, String content_type) { - this.line = requireNonNull(line); - this.value = requireNonNull(value); - this.content_type = content_type; - } - - public Long getLine() { - return line; - } - - public String getValue() { - return value; - } - - public String getContent_type() { - return content_type; - } - } - - static class JvmDataTableRow { - private final List cells; - - JvmDataTableRow(List cells) { - this.cells = requireNonNull(cells); - } - - public List getCells() { - return cells; - } - } - - static class JvmHook { - private final JvmMatch match; - private final JvmResult result; - private final List embeddings; - private final List output; - - JvmHook(JvmMatch match, JvmResult result, List embeddings, List output) { - this.match = requireNonNull(match); - this.result = requireNonNull(result); - this.embeddings = embeddings; - this.output = output; - } - - public JvmMatch getMatch() { - return match; - } - - public JvmResult getResult() { - return result; - } - - public List getEmbeddings() { - return embeddings; - } - - public List getOutput() { - return output; - } - } - - static class JvmEmbedding { - private final String mime_type; - private final String data; - private final String name; - - JvmEmbedding(String mime_type, String data, String name) { - this.mime_type = requireNonNull(mime_type); - this.data = requireNonNull(data); - this.name = name; - } - - public String getData() { - return data; - } - - public String getMime_type() { - return mime_type; - } - - public String getName() { - return name; - } - } - - static class JvmTag { - private final String name; - - JvmTag(String name) { - this.name = requireNonNull(name); - } - - public String getName() { - return name; - } - } - - static class JvmLocationTag { - private final String name; - private final String type; - private final JvmLocation location; - - JvmLocationTag(String name, String type, JvmLocation location) { - this.name = requireNonNull(name); - this.type = requireNonNull(type); - this.location = requireNonNull(location); - } - - public String getName() { - return name; - } - - public String getType() { - return type; - } - - public JvmLocation getLocation() { - return location; - } - } - - static class JvmLocation { - private final Long line; - private final Long column; - - JvmLocation(Long line, Long column) { - this.line = requireNonNull(line); - this.column = requireNonNull(column); - } - - public Long getLine() { - return line; - } - - public Long getColumn() { - return column; - } - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java deleted file mode 100644 index 2a0cd26497..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Examples; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.Pickle; -import io.cucumber.messages.types.Rule; -import io.cucumber.messages.types.Scenario; -import io.cucumber.messages.types.TableRow; -import io.cucumber.query.LineageReducer; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; - -class IdNamingVisitor implements LineageReducer.Collector { - - private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]"); - - private final List parts = new ArrayList<>(); - - @Override - public void add(Feature feature) { - parts.add(formatId(feature.getName())); - } - - @Override - public void add(Rule rule) { - parts.add(formatId(rule.getName())); - } - - @Override - public void add(Scenario scenario) { - parts.add(formatId(scenario.getName())); - } - - @Override - public void add(Examples examples, int index) { - parts.add(formatId(examples.getName())); - } - - @Override - public void add(TableRow example, int index) { - // json report uses base-1 indexing, and skips the first row - parts.add(String.valueOf(index + 2)); - } - - @Override - public void add(Pickle pickle) { - formatId(pickle.getName()); - } - - @Override - public String finish() { - return String.join(";", parts); - } - - static String formatId(String name) { - return replacementPattern.matcher(name).replaceAll("-").toLowerCase(Locale.ROOT); - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java index 8f10a62669..4334cb920f 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java @@ -1,5 +1,6 @@ package io.cucumber.core.plugin; +import io.cucumber.jsonformatter.MessagesToJsonWriter; import io.cucumber.messages.types.Envelope; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; @@ -9,7 +10,7 @@ import java.io.OutputStream; import java.net.URI; -import static io.cucumber.core.plugin.MessagesToJsonWriter.builder; +import static io.cucumber.jsonformatter.MessagesToJsonWriter.builder; public final class JsonFormatter implements ConcurrentEventListener { diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java deleted file mode 100644 index 71c10717e8..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java +++ /dev/null @@ -1,504 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.plugin.CucumberJvmJson.JvmArgument; -import io.cucumber.core.plugin.CucumberJvmJson.JvmDataTableRow; -import io.cucumber.core.plugin.CucumberJvmJson.JvmDocString; -import io.cucumber.core.plugin.CucumberJvmJson.JvmElement; -import io.cucumber.core.plugin.CucumberJvmJson.JvmElementType; -import io.cucumber.core.plugin.CucumberJvmJson.JvmFeature; -import io.cucumber.core.plugin.CucumberJvmJson.JvmLocation; -import io.cucumber.core.plugin.CucumberJvmJson.JvmLocationTag; -import io.cucumber.core.plugin.CucumberJvmJson.JvmMatch; -import io.cucumber.core.plugin.CucumberJvmJson.JvmResult; -import io.cucumber.core.plugin.CucumberJvmJson.JvmStatus; -import io.cucumber.core.plugin.CucumberJvmJson.JvmStep; -import io.cucumber.core.plugin.CucumberJvmJson.JvmTag; -import io.cucumber.messages.Convertor; -import io.cucumber.messages.types.Attachment; -import io.cucumber.messages.types.Background; -import io.cucumber.messages.types.DataTable; -import io.cucumber.messages.types.DocString; -import io.cucumber.messages.types.Exception; -import io.cucumber.messages.types.Feature; -import io.cucumber.messages.types.GherkinDocument; -import io.cucumber.messages.types.Group; -import io.cucumber.messages.types.Hook; -import io.cucumber.messages.types.HookType; -import io.cucumber.messages.types.Location; -import io.cucumber.messages.types.Pickle; -import io.cucumber.messages.types.PickleStep; -import io.cucumber.messages.types.PickleTag; -import io.cucumber.messages.types.Rule; -import io.cucumber.messages.types.RuleChild; -import io.cucumber.messages.types.Scenario; -import io.cucumber.messages.types.SourceReference; -import io.cucumber.messages.types.Step; -import io.cucumber.messages.types.StepDefinition; -import io.cucumber.messages.types.StepMatchArgument; -import io.cucumber.messages.types.StepMatchArgumentsList; -import io.cucumber.messages.types.TableCell; -import io.cucumber.messages.types.Tag; -import io.cucumber.messages.types.TestCaseStarted; -import io.cucumber.messages.types.TestStep; -import io.cucumber.messages.types.TestStepFinished; -import io.cucumber.messages.types.TestStepResult; -import io.cucumber.messages.types.Timestamp; -import io.cucumber.query.Lineage; -import io.cucumber.query.LineageReducer; -import io.cucumber.query.Query; - -import java.net.URI; -import java.time.Duration; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collector; -import java.util.stream.Stream; - -import static io.cucumber.core.plugin.IdNamingVisitor.formatId; -import static io.cucumber.messages.types.AttachmentContentEncoding.BASE64; -import static io.cucumber.messages.types.AttachmentContentEncoding.IDENTITY; -import static java.util.Collections.emptyList; -import static java.util.Locale.ROOT; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; - -class JsonReportWriter { - private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - .withZone(ZoneOffset.UTC); - private final Query query; - private final SourceReferenceFormatter sourceReferenceFormatter = new SourceReferenceFormatter(); - private final Function uriFormatter; - - JsonReportWriter(Query query, Function uriFormatter) { - this.query = requireNonNull(query); - this.uriFormatter = requireNonNull(uriFormatter); - } - - private static List nullIfEmpty(List list) { - return list.isEmpty() ? null : list; - } - - private String formatTimeStamp(Timestamp instant) { - return dateTimeFormatter.format(Convertor.toInstant(instant)); - } - - List createJsonReport() { - return query.findAllTestCaseStarted() - .stream() - .map(this::createJvmElementData) - .sorted(JvmElementData.comparator) - // Preserve order with linked hashmap - .collect(groupingBy(data -> data.pickle.getUri(), LinkedHashMap::new, toList())) - .values() - .stream() - .map(this::createJvmFeature) - .collect(toList()); - } - - private JvmElementData createJvmElementData(TestCaseStarted testCaseStarted) { - Pickle pickle = query.findPickleBy(testCaseStarted) - .orElseThrow( - () -> new IllegalStateException("No Pickle for testCaseStarted " + testCaseStarted.getId())); - Lineage lineage = query.findLineageBy(pickle) - .orElseThrow( - () -> new IllegalStateException("No Lineage for testCaseStarted " + testCaseStarted.getId())); - Location location = query.findLocationOf(pickle) - .orElseThrow( - () -> new IllegalStateException("No Location for testCaseStarted " + testCaseStarted.getId())); - - return new JvmElementData( - testCaseStarted, - lineage, - pickle, - location, - createTestStepData(testCaseStarted)); - } - - private TestStepData createTestStepData(TestCaseStarted testCaseStarted) { - List testStepsFinished = query.findTestStepsFinishedBy(testCaseStarted); - - List beforeTestCase = new ArrayList<>(); - List afterTestCase = new ArrayList<>(); - List beforeTestStep = new ArrayList<>(); - List afterTestStep = new ArrayList<>(); - Map> beforeTestStepByStep = new HashMap<>(); - Map> afterTestStepByStep = new HashMap<>(); - - for (TestStepFinished testStepFinished : testStepsFinished) { - HookType hook = query.findTestStepBy(testStepFinished) - .flatMap(query::findHookBy) - .flatMap(Hook::getType) - .orElse(null); - - if (hook == null) { - beforeTestStepByStep.put(testStepFinished, beforeTestStep); - beforeTestStep = new ArrayList<>(); - afterTestStep = new ArrayList<>(); - afterTestStepByStep.put(testStepFinished, afterTestStep); - continue; - } - - switch (hook) { - case BEFORE_TEST_RUN: - case AFTER_TEST_RUN: - break; - case BEFORE_TEST_CASE: - beforeTestCase.add(testStepFinished); - break; - case AFTER_TEST_CASE: - afterTestCase.add(testStepFinished); - break; - case BEFORE_TEST_STEP: - beforeTestStep.add(testStepFinished); - break; - case AFTER_TEST_STEP: - afterTestStep.add(testStepFinished); - break; - } - - } - return new TestStepData(beforeTestCase, afterTestCase, beforeTestStepByStep, afterTestStepByStep); - } - - private JvmFeature createJvmFeature(List elements) { - JvmElementData firstElement = elements.get(0); - GherkinDocument document = firstElement.lineage.document(); - Feature feature = firstElement.lineage.feature() - .orElseThrow( - () -> new IllegalStateException( - "No Feature for testCaseStarted " + firstElement.testCaseStarted.getId())); - return new JvmFeature( - document.getUri() - .map(URI::create) - .map(uriFormatter) - .orElse(null), - formatId(feature.getName()), - feature.getLocation().getLine(), - feature.getKeyword(), - feature.getName(), - // TODO: Can this be null? - feature.getDescription() != null ? feature.getDescription() : "", - createJvmElements(elements), - createJvmLocationTags(feature)); - } - - private List createJvmElements(List entries) { - return entries.stream() - .map(this::createJvmElement) - .flatMap(Collection::stream) - .collect(toList()); - } - - private List createJvmElement(JvmElementData data) { - Map, List> stepsByBackground = query - .findTestStepFinishedAndTestStepBy(data.testCaseStarted) - .stream() - .collect(groupTestStepsByBackground(data)); - - // There can be multiple backgrounds, but historically the json format - // only ever had one. So we group all other backgrounds steps with the - // first. - Optional background = stepsByBackground.entrySet().stream() - .filter(isBackGround()) - .reduce(mergeEntries()) - .flatMap(entry -> entry.getKey() - .map(bg -> createBackground(data, bg, entry.getValue()))); - - Optional testCase = stepsByBackground.entrySet().stream() - .filter(isTestCase()) - .reduce(mergeEntries()) - .map(Entry::getValue) - .map(scenarioTestStepsFinished -> createTestCase(data, scenarioTestStepsFinished)); - - return Stream.of(background, testCase) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toList()); - } - - private static BinaryOperator, List>> mergeEntries() { - return (a, b) -> { - a.getValue().addAll(b.getValue()); - return a; - }; - } - - private static Predicate, List>> isTestCase() { - return isBackGround().negate(); - } - - private static Predicate, List>> isBackGround() { - return entry -> entry.getKey().isPresent(); - } - - private JvmElement createTestCase(JvmElementData data, List scenarioTestStepsFinished) { - Scenario scenario = data.lineage.scenario().orElseThrow(() -> new IllegalStateException("No scenario?")); - LineageReducer idStrategy = LineageReducer.descending(IdNamingVisitor::new); - return new JvmElement( - formatTimeStamp(data.testCaseStarted.getTimestamp()), - data.location.getLine(), - idStrategy.reduce(data.lineage), - JvmElementType.scenario, - scenario.getKeyword(), - data.pickle.getName(), - scenario.getDescription() == null ? "" : scenario.getDescription(), - createTestSteps(data, scenarioTestStepsFinished), - nullIfEmpty(createHookSteps(data.testStepData.beforeTestCaseSteps)), - nullIfEmpty(createHookSteps(data.testStepData.afterTestCaseSteps)), - nullIfEmpty(createJvmTags(data.pickle))); - } - - private List createJvmTags(Pickle pickle) { - return pickle.getTags() - .stream() - .map(this::createJvmTag) - .collect(toList()); - } - - private JvmTag createJvmTag(PickleTag pickleTag) { - return new JvmTag(pickleTag.getName()); - } - - private List createHookSteps(List testStepsFinished) { - return testStepsFinished.stream() - .map(testStepFinished -> query.findTestStepBy(testStepFinished) - .flatMap(testStep -> query.findHookBy(testStep) - .map(hook -> new CucumberJvmJson.JvmHook( - createJvmMatch(testStep), - createJvmResult(testStepFinished.getTestStepResult()), - createEmbeddings(query.findAttachmentsBy(testStepFinished)), - createOutput(query.findAttachmentsBy(testStepFinished)))))) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toList()); - } - - private JvmResult createJvmResult(TestStepResult result) { - Duration duration = Convertor.toDuration(result.getDuration()); - return new JvmResult( - duration.isZero() ? null : duration.toNanos(), - JvmStatus.valueOf(result.getStatus().name().toLowerCase(ROOT)), - result.getException().flatMap(Exception::getStackTrace).orElse(null)); - } - - private List createEmbeddings(List attachments) { - if (attachments.isEmpty()) { - return null; - } - List embeddings = attachments.stream() - .filter(attachment -> attachment.getContentEncoding() == BASE64) - .map(attachment -> new CucumberJvmJson.JvmEmbedding( - attachment.getMediaType(), - attachment.getBody(), - attachment.getFileName().orElse(null))) - .collect(toList()); - - if (embeddings.isEmpty()) { - return null; - } - return embeddings; - } - - private List createOutput(List attachments) { - if (attachments.isEmpty()) { - return null; - } - List outputs = attachments.stream() - .filter(attachment -> attachment.getContentEncoding() == IDENTITY) - .map(Attachment::getBody) - .collect(toList()); - - if (outputs.isEmpty()) { - return null; - } - return outputs; - } - - private List createJvmLocationTags(Feature feature) { - return feature.getTags().stream() - .map(this::createJvmLocationTag) - .collect(toList()); - } - - private JvmLocationTag createJvmLocationTag(Tag tag) { - return new JvmLocationTag( - tag.getName(), - "Tag", - new JvmLocation( - tag.getLocation().getLine(), - tag.getLocation().getColumn().orElse(0L))); - } - - private JvmElement createBackground( - JvmElementData data, Background background, List backgroundTestStepsFinished - ) { - return new JvmElement( - null, - background.getLocation().getLine(), - null, - JvmElementType.background, - background.getKeyword(), - background.getName(), - background.getDescription() != null ? background.getDescription() : "", - createTestSteps(data, backgroundTestStepsFinished), - null, - null, - null); - } - - private List createTestSteps(JvmElementData data, List testStepsFinished) { - return testStepsFinished.stream() - .map(testStepFinished -> createTestStep(data, testStepFinished)) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(toList()); - } - - private Optional createTestStep(JvmElementData data, TestStepFinished testStepFinished) { - List beforeStepHooks = data.testStepData.beforeStepStepsByStep - .getOrDefault(testStepFinished, emptyList()); - List afterStepHooks = data.testStepData.afterStepStepsByStep - .getOrDefault(testStepFinished, emptyList()); - - return query.findTestStepBy(testStepFinished) - .flatMap(testStep -> query.findPickleStepBy(testStep) - .flatMap(pickleStep -> query.findStepBy(pickleStep) - .map(step -> new JvmStep( - step.getKeyword(), - step.getLocation().getLine(), - createJvmMatch(testStep), - pickleStep.getText(), - createJvmResult(testStepFinished.getTestStepResult()), - createJvmDocString(step), - createJvmDataTableRows(step), - nullIfEmpty(createHookSteps(beforeStepHooks)), - nullIfEmpty(createHookSteps(afterStepHooks)))))); - } - - private JvmMatch createJvmMatch(TestStep testStep) { - return new JvmMatch( - createLocation(testStep), - createJvmArguments(testStep)); - } - - private List createJvmArguments(TestStep step) { - return step.getStepMatchArgumentsLists() - .map(argumentsLists -> argumentsLists.stream() - .map(StepMatchArgumentsList::getStepMatchArguments) - .flatMap(Collection::stream) - .map(this::createJvmArgument) - .collect(toList())) - .filter(jvmArguments -> !jvmArguments.isEmpty()) - .orElse(null); - } - - private JvmArgument createJvmArgument(StepMatchArgument argument) { - Group group = argument.getGroup(); - return new JvmArgument( - group.getValue().orElse(null), - group.getStart().orElse(-1L)); - } - - private List createJvmDataTableRows(Step step) { - return step.getDataTable().map(this::createJvmDataTableRows).orElse(null); - } - - private List createJvmDataTableRows(DataTable argument) { - return argument.getRows().stream() - .map(row -> new JvmDataTableRow(row.getCells().stream() - .map(TableCell::getValue) - .collect(toList()))) - .collect(toList()); - } - - private JvmDocString createJvmDocString(Step step) { - return step.getDocString().map(this::createJvmDocString).orElse(null); - } - - private JvmDocString createJvmDocString(DocString docString) { - return new JvmDocString( - docString.getLocation().getLine(), - docString.getContent(), - docString.getMediaType().orElse(null)); - } - - private String createLocation(TestStep step) { - return findSourceReference(step) - .flatMap(sourceReferenceFormatter::format) - .orElse(null); - } - - private Collector, ?, Map, List>> groupTestStepsByBackground( - JvmElementData data - ) { - List backgrounds = data.lineage.feature() - .map(this::findBackgroundsBy) - .orElseGet(Collections::emptyList); - - Function, Optional> grouping = entry -> query - .findPickleStepBy(entry.getValue()) - .flatMap(pickleStep -> findBackgroundBy(backgrounds, pickleStep)); - - Function>, List> extractKey = entries -> entries - .stream() - .map(Entry::getKey) - .collect(toList()); - - return groupingBy(grouping, LinkedHashMap::new, collectingAndThen(toList(), extractKey)); - } - - private List findBackgroundsBy(Feature feature) { - return feature.getChildren() - .stream() - .map(featureChild -> { - List backgrounds = new ArrayList<>(); - featureChild.getBackground().ifPresent(backgrounds::add); - featureChild.getRule() - .map(Rule::getChildren) - .map(Collection::stream) - .orElseGet(Stream::empty) - .map(RuleChild::getBackground) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(backgrounds::add); - return backgrounds; - }) - .flatMap(Collection::stream) - .collect(toList()); - } - - private Optional findBackgroundBy(List backgrounds, PickleStep pickleStep) { - return backgrounds.stream() - .filter(background -> background.getSteps().stream() - .map(Step::getId) - .anyMatch(step -> pickleStep.getAstNodeIds().contains(step))) - .findFirst(); - } - - private Optional findSourceReference(TestStep step) { - Optional source = query.findUnambiguousStepDefinitionBy(step) - .map(StepDefinition::getSourceReference); - - if (source.isPresent()) { - return source; - } - - return query.findHookBy(step) - .map(Hook::getSourceReference); - } - -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java deleted file mode 100644 index 11fdbe9a54..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JvmElementData.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.LocationComparator; -import io.cucumber.messages.types.Location; -import io.cucumber.messages.types.Pickle; -import io.cucumber.messages.types.TestCaseStarted; -import io.cucumber.query.Lineage; - -import java.util.Comparator; - -import static java.util.Objects.requireNonNull; - -class JvmElementData { - static final Comparator comparator = Comparator - .comparing((JvmElementData data) -> data.pickle.getUri()) - // TODO: Use location from pickle - .thenComparing(data -> data.location, new LocationComparator()); - - final TestCaseStarted testCaseStarted; - final Lineage lineage; - final Pickle pickle; - final Location location; - final TestStepData testStepData; - - JvmElementData( - TestCaseStarted testCaseStarted, Lineage lineage, Pickle pickle, Location location, - TestStepData testStepData - ) { - this.testCaseStarted = requireNonNull(testCaseStarted); - this.lineage = requireNonNull(lineage); - this.pickle = requireNonNull(pickle); - this.location = requireNonNull(location); - this.testStepData = requireNonNull(testStepData); - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java deleted file mode 100644 index b53f46a10e..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Envelope; -import io.cucumber.query.Query; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.function.Function; - -import static java.util.Objects.requireNonNull; - -/** - * Writes the message output of a test run as single json report. - *

- * Note: Messages are first collected and only written once the stream is - * closed. - */ -class MessagesToJsonWriter implements AutoCloseable { - - private final OutputStreamWriter out; - private final Query query = new Query(); - private final Serializer serializer; - private final Function uriFormatter; - private boolean streamClosed = false; - - private MessagesToJsonWriter(OutputStream out, Serializer serializer, Function uriFormatter) { - this.out = new OutputStreamWriter( - requireNonNull(out), - StandardCharsets.UTF_8); - this.serializer = requireNonNull(serializer); - this.uriFormatter = requireNonNull(uriFormatter); - } - - public static Builder builder(Serializer serializer) { - return new Builder(serializer); - } - - static final class Builder { - private final Serializer serializer; - private Function uriFormatter = URI::toString; - - public Builder(Serializer serializer) { - this.serializer = requireNonNull(serializer); - } - - public Builder relativizeAgainst(URI uri) { - // TODO: Needs coverage - // TODO: Naming? - this.uriFormatter = relativize(uri) - .andThen(URI::toString); - return this; - } - - static Function relativize(URI base) { - return uri -> { - // TODO: Needs coverage - if (!"file".equals(uri.getScheme())) { - return uri; - } - if (!uri.isAbsolute()) { - return uri; - } - - try { - URI relative = base.relativize(uri); - // Scheme is lost by relativize - return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e.getMessage(), e); - } - }; - } - - public MessagesToJsonWriter build(OutputStream out) { - requireNonNull(out); - return new MessagesToJsonWriter(out, serializer, uriFormatter); - } - } - - /** - * Writes a cucumber message to the xml output. - * - * @param envelope the message - * @throws IOException if an IO error occurs - */ - public void write(Envelope envelope) throws IOException { - if (streamClosed) { - throw new IOException("Stream closed"); - } - query.update(envelope); - } - - /** - * Closes the stream, flushing it first. Once closed further write() - * invocations will cause an IOException to be thrown. Closing a closed - * stream has no effect. - * - * @throws IOException if an IO error occurs - */ - @Override - public void close() throws IOException { - if (streamClosed) { - return; - } - try { - List report = new JsonReportWriter(query, uriFormatter).createJsonReport(); - serializer.writeValue(out, report); - } finally { - try { - out.close(); - } finally { - streamClosed = true; - } - } - } - - @FunctionalInterface - public interface Serializer { - - void writeValue(Writer writer, Object value) throws IOException; - - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java deleted file mode 100644 index bfe89b2a5f..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/SourceReferenceFormatter.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Location; -import io.cucumber.messages.types.SourceReference; - -import java.util.Optional; - -final class SourceReferenceFormatter { - - SourceReferenceFormatter() { - } - - Optional format(SourceReference sourceReference) { - if (sourceReference.getJavaMethod().isPresent()) { - return sourceReference.getJavaMethod() - .map(javaMethod -> String.format( - "%s#%s(%s)", - javaMethod.getClassName(), - javaMethod.getMethodName(), - String.join(",", javaMethod.getMethodParameterTypes()))); - } - if (sourceReference.getJavaStackTraceElement().isPresent()) { - return sourceReference.getJavaStackTraceElement() - .map(javaStackTraceElement -> String.format( - "%s#%s(%s%s)", - javaStackTraceElement.getClassName(), - javaStackTraceElement.getMethodName(), - javaStackTraceElement.getFileName(), - sourceReference.getLocation().map(Location::getLine).map(line -> ":" + line).orElse(""))); - } - if (sourceReference.getUri().isPresent()) { - return sourceReference.getUri() - .map(uri -> uri + sourceReference.getLocation() - .map(location -> ":" + location.getLine()) - .orElse("")); - } - return Optional.empty(); - } -} diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java deleted file mode 100644 index c70b0f5177..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestStepData.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.TestStepFinished; - -import java.util.List; -import java.util.Map; - -class TestStepData { - final List beforeTestCaseSteps; - final List afterTestCaseSteps; - final Map> beforeStepStepsByStep; - final Map> afterStepStepsByStep; - - TestStepData( - List beforeTestCaseSteps, - List afterTestCaseSteps, - Map> beforeStepStepsByStep, - Map> afterStepStepsByStep - ) { - this.beforeTestCaseSteps = beforeTestCaseSteps; - this.afterTestCaseSteps = afterTestCaseSteps; - this.beforeStepStepsByStep = beforeStepStepsByStep; - this.afterStepStepsByStep = afterStepStepsByStep; - } -} From 16278fb149d8c9f6f04f54f61ef1f719edb9b737 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 21 Jul 2025 21:42:15 +0200 Subject: [PATCH 36/41] Source locations are formatted with a . --- .../core/plugin/JsonFormatterTest.java | 68 +++++++++---------- .../core/plugin/JsonPrettyFormatterTest.json | 58 ++++++++-------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index f54482895b..a2760a68ec 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -224,7 +224,7 @@ void should_format_scenario_with_a_passed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -285,7 +285,7 @@ void should_format_scenario_with_a_failed_step() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -345,7 +345,7 @@ void should_format_scenario_with_a_rule() throws JSONException { " \"line\": 5,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -411,7 +411,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -424,7 +424,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 9,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -448,7 +448,7 @@ void should_format_scenario_with_a_rule_and_background() throws JSONException { " \"line\": 12,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"keyword\": \"Given \"\n" + @@ -513,7 +513,7 @@ void should_format_scenario_outline_with_one_example() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -579,7 +579,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -603,7 +603,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats bananas\",\n" + " \"line\": 7,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_eats_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -625,7 +625,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -649,7 +649,7 @@ void should_format_feature_with_background() throws JSONException { " \"name\": \"the monkey eats more bananas\",\n" + " \"line\": 10,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_eats_more_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -708,7 +708,7 @@ void should_format_feature_and_scenario_with_tags() throws JSONException { " \"line\": 5,\n" + " \"name\": \"the monkey eats more bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_eats_more_bananas()\"\n" + " },\n" + " \"keyword\": \"Then \"\n" + @@ -797,7 +797,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()\"\n" + " },\n" + " \"result\": {\n" + @@ -812,7 +812,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -824,7 +824,7 @@ void should_format_scenario_with_hooks() throws JSONException { " \"after\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#after_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.after_hook_1()\"\n" + " },\n" + " \"result\": {\n" + @@ -895,7 +895,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.beforestep_hook_1()\"\n" + " }\n" + " }\n" + @@ -903,7 +903,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"after\": [\n" + @@ -913,7 +913,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_2()\"\n" + " }\n" + " },\n" + @@ -923,7 +923,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_1()\"\n" + " }\n" + " }\n" + @@ -942,7 +942,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.beforestep_hook_1()\"\n" + " }\n" + " }\n" + @@ -950,7 +950,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"line\": 5,\n" + " \"name\": \"monkey arrives\",\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_arrives()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_arrives()\"\n" + " },\n" + " \"after\": [\n" + @@ -960,7 +960,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_2()\"\n" + " }\n" + " },\n" + @@ -970,7 +970,7 @@ void should_add_step_hooks_to_step() throws JSONException { " \"status\": \"passed\"\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_1()\"\n" + " }\n" + " }\n" + @@ -1034,7 +1034,7 @@ void should_handle_write_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()\"\n" + " },\n" + " \"output\": [\n" + @@ -1052,7 +1052,7 @@ void should_handle_write_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1114,7 +1114,7 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()\"\n" + " },\n" + " \"embeddings\": [\n" + @@ -1135,7 +1135,7 @@ void should_handle_embed_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1196,7 +1196,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()\"\n" + " },\n" + " \"embeddings\": [\n" + @@ -1218,7 +1218,7 @@ void should_handle_embed_with_name_from_a_hook() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1285,7 +1285,7 @@ void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1353,7 +1353,7 @@ void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() thr " \"line\": 5\n" + " },\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1429,7 +1429,7 @@ void should_format_scenario_with_a_step_with_a_data_table() throws JSONException " }\n" + " ],\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1495,7 +1495,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are bananas\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" + " },\n" + " \"result\": {\n" + @@ -1530,7 +1530,7 @@ void should_handle_several_features() throws JSONException { " \"name\": \"there are oranges\",\n" + " \"line\": 4,\n" + " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_oranges()\"\n" + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_oranges()\"\n" + " },\n" + " \"result\": {\n" + diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json index 2997958900..5f06c1e84d 100644 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json +++ b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json @@ -16,7 +16,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" }, "keyword": "Given " }, @@ -27,7 +27,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" }, "keyword": "When " }, @@ -38,7 +38,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" }, "keyword": "Then " } @@ -52,7 +52,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" } } ], @@ -70,7 +70,7 @@ "line": 9, "name": "step_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_1()" }, "keyword": "Given " }, @@ -81,7 +81,7 @@ "line": 10, "name": "step_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_2()" }, "keyword": "When " }, @@ -92,7 +92,7 @@ "line": 11, "name": "step_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_3()" }, "keyword": "Then " }, @@ -103,7 +103,7 @@ "line": 12, "name": "cliché", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#cliche()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.cliche()" }, "keyword": "Then " } @@ -123,7 +123,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" }, "keyword": "Given " }, @@ -134,7 +134,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" }, "keyword": "When " }, @@ -145,7 +145,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" }, "keyword": "Then " } @@ -159,7 +159,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" } } ], @@ -183,7 +183,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_1()" }, "keyword": "Given " }, @@ -200,7 +200,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_2()" }, "keyword": "When " }, @@ -217,7 +217,7 @@ "offset": 0 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_3()" }, "keyword": "Then " } @@ -237,7 +237,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" }, "keyword": "Given " }, @@ -248,7 +248,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" }, "keyword": "When " }, @@ -259,7 +259,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" }, "keyword": "Then " } @@ -273,7 +273,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" } } ], @@ -297,7 +297,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_1()" }, "keyword": "Given " }, @@ -314,7 +314,7 @@ "offset": 5 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_2()" }, "keyword": "When " }, @@ -331,7 +331,7 @@ "offset": 0 } ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_3()" }, "keyword": "Then " } @@ -351,7 +351,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" }, "keyword": "Given " }, @@ -362,7 +362,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" }, "keyword": "When " }, @@ -373,7 +373,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" }, "keyword": "Then " } @@ -387,7 +387,7 @@ "status": "passed" }, "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" } } ], @@ -405,7 +405,7 @@ "line": 25, "name": "a", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#a()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.a()" }, "keyword": "Given " }, @@ -416,7 +416,7 @@ "line": 26, "name": "b", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#b()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.b()" }, "keyword": "Then " }, @@ -427,7 +427,7 @@ "line": 27, "name": "c", "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#c()" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.c()" }, "keyword": "When " } From f7ba8d11cbb8ad0167f3e796997e772a35037c6e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Jul 2025 21:12:29 +0200 Subject: [PATCH 37/41] Fix --- cucumber-core/pom.xml | 8 ++++---- .../java/io/cucumber/core/backend/StubHookDefinition.java | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cucumber-core/pom.xml b/cucumber-core/pom.xml index 235058865d..b5dde426e4 100644 --- a/cucumber-core/pom.xml +++ b/cucumber-core/pom.xml @@ -80,6 +80,10 @@ io.cucumber cucumber-expressions + + io.cucumber + cucumber-json-formatter + io.cucumber datatable @@ -96,10 +100,6 @@ io.cucumber html-formatter - - io.cucumber - json-formatter - io.cucumber junit-xml-formatter diff --git a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java index af8cab27ba..0cf532a79b 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java +++ b/cucumber-core/src/test/java/io/cucumber/core/backend/StubHookDefinition.java @@ -24,6 +24,10 @@ public StubHookDefinition(SourceReference location, HookType hookType, Consumer< this(new StubLocation(location), null, action, hookType); } + public StubHookDefinition() { + this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), null, null, null); + } + public StubHookDefinition(Consumer action) { this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), null, action, null); } From 32ee4f67ffe9bae0ea1461d7f789b8c897115a06 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Jul 2025 21:16:44 +0200 Subject: [PATCH 38/41] Remove now redundant tests --- .../core/plugin/JsonFormatterTest.java | 1455 +---------------- .../core/plugin/JsonParallelRuntimeTest.java | 89 - .../core/plugin/FormatterInParallel.feature | 28 - .../plugin/JsonPrettyFormatterTest.feature | 28 - .../core/plugin/JsonPrettyFormatterTest.json | 444 ----- .../core/plugin/surefire-test-report-3.0.xsd | 140 -- 6 files changed, 1 insertion(+), 2183 deletions(-) delete mode 100644 cucumber-core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json delete mode 100644 cucumber-core/src/test/resources/io/cucumber/core/plugin/surefire-test-report-3.0.xsd diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java index a2760a68ec..5dec0d1d4a 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonFormatterTest.java @@ -1,55 +1,27 @@ package io.cucumber.core.plugin; -import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.SourceReference; -import io.cucumber.core.backend.StubHookDefinition; import io.cucumber.core.backend.StubStepDefinition; import io.cucumber.core.eventbus.IncrementingUuidGenerator; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; -import io.cucumber.core.options.RuntimeOptionsBuilder; import io.cucumber.core.runner.StepDurationTimeService; import io.cucumber.core.runtime.Runtime; -import io.cucumber.core.runtime.Runtime.Builder; import io.cucumber.core.runtime.StubBackendSupplier; import io.cucumber.core.runtime.StubFeatureSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.datatable.DataTable; -import io.cucumber.docstring.DocString; import org.json.JSONException; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.Scanner; -import java.util.UUID; -import static io.cucumber.core.backend.HookDefinition.HookType.AFTER_STEP; -import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE; -import static io.cucumber.core.backend.HookDefinition.HookType.BEFORE_STEP; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.Clock.fixed; import static java.time.Duration.ofMillis; -import static java.time.Instant.EPOCH; -import static java.time.ZoneId.of; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; class JsonFormatterTest { - final SourceReference monkeyArrives = getMethod("monkey_arrives"); final SourceReference thereAreBananas = getMethod("there_are_bananas"); - final SourceReference thereAreOranges = getMethod("there_are_oranges"); - final SourceReference beforeHook1 = getMethod("before_hook_1"); - final SourceReference afterHook1 = getMethod("after_hook_1"); - final SourceReference beforeStepHook1 = getMethod("beforestep_hook_1"); - final SourceReference afterStepHook1 = getMethod("afterstep_hook_1"); - final SourceReference afterStepHook2 = getMethod("afterstep_hook_2"); - - final SourceReference monkeyEatsBananas = getMethod("monkey_eats_bananas"); - final SourceReference monkeyEatsMoreBananas = getMethod("monkey_eats_more_bananas"); private static SourceReference getMethod(String name) { try { @@ -59,126 +31,8 @@ private static SourceReference getMethod(String name) { } } - @Test - void featureWithOutlineTest() throws JSONException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - createRuntime(out) - .build() - .run(); - - InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - - assertJsonEquals(expected, out); - } - - private Builder createRuntime(ByteArrayOutputStream out) { - Feature feature = TestFeatureParser.parse( - "classpath:io/cucumber/core/plugin/JsonPrettyFormatterTest.feature", - getClass().getResourceAsStream("JsonPrettyFormatterTest.feature")); - - return Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE)), - asList( - new StubStepDefinition("bg_1", getMethod("bg_1")), - new StubStepDefinition("bg_2", getMethod("bg_2")), - new StubStepDefinition("bg_3", getMethod("bg_3")), - new StubStepDefinition("step_1", getMethod("step_1")), - new StubStepDefinition("step_2", getMethod("step_2")), - new StubStepDefinition("step_3", getMethod("step_3")), - new StubStepDefinition("cliché", getMethod("cliche")), - new StubStepDefinition("so_1 {int}", getMethod("so_1"), Integer.class), - new StubStepDefinition("so_2 {int} cucumbers", getMethod("so_2"), Integer.class), - new StubStepDefinition("{int} so_3", getMethod("so_3"), Integer.class), - new StubStepDefinition("a", getMethod("a")), - new StubStepDefinition("b", getMethod("b")), - new StubStepDefinition("c", getMethod("c"))), - emptyList())) - .withAdditionalPlugins(new JsonFormatter(out)); - } - - @Test - void featureWithOutlineTestParallel() throws JSONException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - createRuntime(out) - .withRuntimeOptions(new RuntimeOptionsBuilder().setThreads(2).build()) - .build() - .run(); - - InputStream resourceAsStream = getClass().getResourceAsStream("JsonPrettyFormatterTest.json"); - String expected = new Scanner(resourceAsStream, "UTF-8") - .useDelimiter("\\A") - .next(); - - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_an_undefined_step() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier()) - .build() - .run(); - - 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" + - "]"; - assertJsonEquals(expected, out); - } - private void assertJsonEquals(String expected, ByteArrayOutputStream actual) throws JSONException { - assertJsonEquals(expected, new String(actual.toByteArray(), UTF_8)); - - } - - private void assertJsonEquals(String expected, String actual) throws JSONException { - assertEquals(expected, actual, true); + assertEquals(expected, new String(actual.toByteArray(), UTF_8), true); } @Test @@ -240,1311 +94,4 @@ void should_format_scenario_with_a_passed_step() throws JSONException { "]"; assertJsonEquals(expected, out); } - - @Test - void should_format_scenario_with_a_failed_step() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, - new StubException("the stack trace")))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_rule() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Rule: This is all monkey business\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"line\": 4,\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 5,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_rule_and_background() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Background: \n" + - " Given there are bananas\n" + - "\n" + - " Rule: This is all monkey business\n" + - "\n" + - " Background: \n" + - " Given there are bananas\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - String expected = "" + - "[\n" + - " {\n" + - " \"line\": 1,\n" + - " \"elements\": [\n" + - " {\n" + - " \"line\": 3,\n" + - " \"name\": \"\",\n" + - " \"description\": \"\",\n" + - " \"type\": \"background\",\n" + - " \"keyword\": \"Background\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 4,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 9,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\n" + - " }\n" + - " ]\n" + - " },\n" + - " {\n" + - " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + - " \"line\": 11,\n" + - " \"name\": \"Monkey eats bananas\",\n" + - " \"description\": \"\",\n" + - " \"id\": \"banana-party;this-is-all-monkey-business;monkey-eats-bananas\",\n" + - " \"type\": \"scenario\",\n" + - " \"keyword\": \"Scenario\",\n" + - " \"steps\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"line\": 12,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"keyword\": \"Given \"\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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_outline_with_one_example() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_feature_with_background() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("the monkey eats bananas", monkeyEatsBananas), - new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_eats_more_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_feature_and_scenario_with_tags() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("the monkey eats more bananas", monkeyEatsMoreBananas))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_hooks() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, HookDefinition.HookType.BEFORE)), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.AFTER)))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"after\": [\n" + - " {\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.after_hook_1()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_add_step_hooks_to_step() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - emptyList(), - singletonList(new StubHookDefinition(beforeStepHook1, BEFORE_STEP)), - asList( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("monkey arrives", monkeyArrives)), - asList( - new StubHookDefinition(afterStepHook1, AFTER_STEP), - new StubHookDefinition(afterStepHook2, AFTER_STEP)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.beforestep_hook_1()\"\n" - + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 4,\n" + - " \"name\": \"there are bananas\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_2()\"\n" - + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.beforestep_hook_1()\"\n" - + - " }\n" + - " }\n" + - " ],\n" + - " \"line\": 5,\n" + - " \"name\": \"monkey arrives\",\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.monkey_arrives()\"\n" - + - " },\n" + - " \"after\": [\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_2()\"\n" - + - " }\n" + - " },\n" + - " {\n" + - " \"result\": {\n" + - " \"duration\": 1000000,\n" + - " \"status\": \"passed\"\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.afterstep_hook_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" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_write_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE, - testCaseState -> testCaseState.log("printed from hook"))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_embed_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, - BEFORE, - testCaseState -> testCaseState - .attach(new byte[] { 1, 2, 3 }, "mime-type;base64", null))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_embed_with_name_from_a_hook() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - singletonList(new StubHookDefinition(beforeHook1, BEFORE, - testCaseState -> testCaseState.attach(new byte[] { 1, 2, 3 }, "mime-type;base64", - "someEmbedding"))), - singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), - emptyList())) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, String.class))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_doc_string_and_content_type() throws JSONException { - Feature feature = TestFeatureParser.parse("path/test.feature", "" + - "Feature: Banana party\n" + - "\n" + - " Scenario: Monkey eats bananas\n" + - " Given there are bananas\n" + - " \"\"\"text/plain\n" + - " doc string content\n" + - " \"\"\"\n"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, DocString.class))) - .build() - .run(); - - 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\": \"text/plain\",\n" + - " \"value\": \"doc string content\",\n" + - " \"line\": 5\n" + - " },\n" + - " \"match\": {\n" + - " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_format_scenario_with_a_step_with_a_data_table() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas, DataTable.class))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_bananas()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - - @Test - void should_handle_several_features() throws JSONException { - 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"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - StepDurationTimeService timeService = new StepDurationTimeService(ofMillis(1)); - Runtime.builder() - .withFeatureSupplier(new StubFeatureSupplier(feature1, feature2)) - .withAdditionalPlugins(timeService, new JsonFormatter(out)) - .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) - .withBackendSupplier(new StubBackendSupplier( - new StubStepDefinition("there are bananas", thereAreBananas), - new StubStepDefinition("there are oranges", thereAreOranges))) - .build() - .run(); - - 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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.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\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.there_are_oranges()\"\n" - + - " },\n" + - " \"result\": {\n" + - " \"status\": \"passed\",\n" + - " \"duration\": 1000000\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " ],\n" + - " \"tags\": []\n" + - " }\n" + - "]"; - assertJsonEquals(expected, out); - } - } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java deleted file mode 100644 index c6fcd9057d..0000000000 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/JsonParallelRuntimeTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.feature.FeatureWithLines; -import io.cucumber.core.options.RuntimeOptionsBuilder; -import io.cucumber.core.runner.ClockStub; -import io.cucumber.core.runtime.Runtime; -import io.cucumber.core.runtime.TimeServiceEventBus; -import org.json.JSONException; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.util.UUID; - -import static java.time.Duration.ZERO; -import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; - -class JsonParallelRuntimeTest { - - @Test - void testSingleFeature() throws JSONException { - ByteArrayOutputStream parallel = new ByteArrayOutputStream(); - - Runtime.builder() - .withRuntimeOptions( - new RuntimeOptionsBuilder() - .setThreads(3) - .addFeature(FeatureWithLines.parse( - "src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature")) - .build()) - .withAdditionalPlugins(new JsonFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - ByteArrayOutputStream serial = new ByteArrayOutputStream(); - - Runtime.builder() - .withRuntimeOptions( - new RuntimeOptionsBuilder() - .setThreads(1) - .addFeature(FeatureWithLines.parse( - "src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature")) - .build()) - .withAdditionalPlugins(new JsonFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - assertEquals(serial.toString(), parallel.toString(), false); - } - - @Test - void testMultipleFeatures() throws JSONException { - ByteArrayOutputStream parallel = new ByteArrayOutputStream(); - - Runtime.builder() - .withRuntimeOptions( - new RuntimeOptionsBuilder() - .setThreads(3) - .addFeature(FeatureWithLines.parse( - "src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature")) - .addFeature(FeatureWithLines - .parse("src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature")) - .build()) - .withAdditionalPlugins(new JsonFormatter(parallel)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - ByteArrayOutputStream serial = new ByteArrayOutputStream(); - - Runtime.builder() - .withRuntimeOptions( - new RuntimeOptionsBuilder() - .setThreads(1) - .addFeature(FeatureWithLines.parse( - "src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature")) - .addFeature(FeatureWithLines - .parse("src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature")) - .build()) - .withAdditionalPlugins(new JsonFormatter(serial)) - .withEventBus(new TimeServiceEventBus(new ClockStub(ZERO), UUID::randomUUID)) - .build() - .run(); - - assertEquals(serial.toString(), parallel.toString(), false); - } - -} diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature b/cucumber-core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature deleted file mode 100644 index 35d38f6fa0..0000000000 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/FormatterInParallel.feature +++ /dev/null @@ -1,28 +0,0 @@ -Feature: Feature executed in parallel - - Background: - Given bg_1_parallel - When bg_2_parallel - Then bg_3_parallel - - Scenario: Scenario_1 - Given step_1_parallel - When step_2_parallel - Then step_3_parallel - Then cliché_parallel - - Scenario Outline: ScenarioOutline_1_parallel - Given so_1 _parallel - When so_2 cucumbers_parallel - Then so_3_parallel - - Examples: - | a | b | c | - | 12 | 5 | 7 | - | 20 | 5 | 15 | - - Scenario: Scenario_2 - Given a_parallel - Then b_parallel - When c_parallel - diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.feature deleted file mode 100644 index 77cd1c49b8..0000000000 --- a/cucumber-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/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json b/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json deleted file mode 100644 index 5f06c1e84d..0000000000 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/JsonPrettyFormatterTest.json +++ /dev/null @@ -1,444 +0,0 @@ -[ - { - "line": 1, - "elements": [ - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 4, - "name": "bg_1", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 5, - "name": "bg_2", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 6, - "name": "bg_3", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" - }, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:00.000Z", - "before": [ - { - "result": { - "status": "passed" - }, - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" - } - } - ], - "line": 8, - "name": "Scenario_1", - "description": "", - "id": "feature-3;scenario-1", - "type": "scenario", - "keyword": "Scenario", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 9, - "name": "step_1", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 10, - "name": "step_2", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 11, - "name": "step_3", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.step_3()" - }, - "keyword": "Then " - }, - { - "result": { - "status": "passed" - }, - "line": 12, - "name": "cliché", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.cliche()" - }, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 4, - "name": "bg_1", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 5, - "name": "bg_2", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 6, - "name": "bg_3", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" - }, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:00.000Z", - "before": [ - { - "result": { - "status": "passed" - }, - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" - } - } - ], - "line": 21, - "name": "ScenarioOutline_1", - "description": "", - "id": "feature-3;scenariooutline-1;;2", - "type": "scenario", - "keyword": "Scenario Outline", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 15, - "name": "so_1 12", - "match": { - "arguments": [ - { - "val": "12", - "offset": 5 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 16, - "name": "so_2 7 cucumbers", - "match": { - "arguments": [ - { - "val": "7", - "offset": 5 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 17, - "name": "5 so_3", - "match": { - "arguments": [ - { - "val": "5", - "offset": 0 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_3()" - }, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 4, - "name": "bg_1", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 5, - "name": "bg_2", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 6, - "name": "bg_3", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" - }, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:00.000Z", - "before": [ - { - "result": { - "status": "passed" - }, - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" - } - } - ], - "line": 22, - "name": "ScenarioOutline_1", - "description": "", - "id": "feature-3;scenariooutline-1;;3", - "type": "scenario", - "keyword": "Scenario Outline", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 15, - "name": "so_1 20", - "match": { - "arguments": [ - { - "val": "20", - "offset": 5 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 16, - "name": "so_2 15 cucumbers", - "match": { - "arguments": [ - { - "val": "15", - "offset": 5 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 17, - "name": "5 so_3", - "match": { - "arguments": [ - { - "val": "5", - "offset": 0 - } - ], - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.so_3()" - }, - "keyword": "Then " - } - ] - }, - { - "line": 3, - "name": "", - "description": "", - "type": "background", - "keyword": "Background", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 4, - "name": "bg_1", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_1()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 5, - "name": "bg_2", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_2()" - }, - "keyword": "When " - }, - { - "result": { - "status": "passed" - }, - "line": 6, - "name": "bg_3", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.bg_3()" - }, - "keyword": "Then " - } - ] - }, - { - "start_timestamp": "1970-01-01T00:00:00.000Z", - "before": [ - { - "result": { - "status": "passed" - }, - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.before_hook_1()" - } - } - ], - "line": 24, - "name": "Scenario_2", - "description": "", - "id": "feature-3;scenario-2", - "type": "scenario", - "keyword": "Scenario", - "steps": [ - { - "result": { - "status": "passed" - }, - "line": 25, - "name": "a", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.a()" - }, - "keyword": "Given " - }, - { - "result": { - "status": "passed" - }, - "line": 26, - "name": "b", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.b()" - }, - "keyword": "Then " - }, - { - "result": { - "status": "passed" - }, - "line": 27, - "name": "c", - "match": { - "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions.c()" - }, - "keyword": "When " - } - ] - } - ], - "name": "Feature_3", - "description": "", - "id": "feature-3", - "keyword": "Feature", - "uri": "classpath:io/cucumber/core/plugin/JsonPrettyFormatterTest.feature", - "tags": [] - } -] diff --git a/cucumber-core/src/test/resources/io/cucumber/core/plugin/surefire-test-report-3.0.xsd b/cucumber-core/src/test/resources/io/cucumber/core/plugin/surefire-test-report-3.0.xsd deleted file mode 100644 index 69d9c93d11..0000000000 --- a/cucumber-core/src/test/resources/io/cucumber/core/plugin/surefire-test-report-3.0.xsd +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 98f42d566c90d459e2ca6ce7a4952643a0e56288 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Jul 2025 21:22:57 +0200 Subject: [PATCH 39/41] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48359878b7..0db32025e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed +- [Core] Use a [message based Cucumber JSON Formatter](https://github.com/cucumber/cucumber-json-formatter) ([##2888](https://github.com/cucumber/cucumber-jvm/pull/#2888) M.P. Korstanje) - [Core] Show all steps in progress formatter ([#3029](https://github.com/cucumber/cucumber-jvm/pull/3029) M.P. Korstanje) ## [7.26.0] - 2025-07-14 From 3b4b215cf25488b7d5cf281b91f536f84b2cd17a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 24 Jul 2025 21:35:18 +0200 Subject: [PATCH 40/41] Suppress Revapi findings --- .revapi/api-changes.json | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.revapi/api-changes.json b/.revapi/api-changes.json index 0a1b3c3f57..a93cc621be 100644 --- a/.revapi/api-changes.json +++ b/.revapi/api-changes.json @@ -71,22 +71,12 @@ ], "internal": [ { - "extension": "revapi.differences", - "id": "internal-api-issues", "ignore": true, - "configuration": { - "differences": [ - { - "ignore": true, - "code": "java.method.visibilityIncreased", - "old": "method io.cucumber.core.eventbus.UuidGenerator io.cucumber.core.runtime.UuidGeneratorServiceLoader::loadUuidGenerator()", - "new": "method io.cucumber.core.eventbus.UuidGenerator io.cucumber.core.runtime.UuidGeneratorServiceLoader::loadUuidGenerator()", - "oldVisibility": "package", - "newVisibility": "public", - "justification": "Expose internal API to other internal components" - } - ] - } + "code": "java.class.nowImplementsInterface", + "old": "class io.cucumber.core.plugin.JsonFormatter", + "new": "class io.cucumber.core.plugin.JsonFormatter", + "interface": "io.cucumber.plugin.ConcurrentEventListener", + "justification": "The JsonFormatter is consumed by Cucumber" } ], "testng": [ From 4cc7e8b9cb059f28c33b7d4579551659501a5490 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 29 Jul 2025 00:57:31 +0200 Subject: [PATCH 41/41] Remove unused class --- .../core/plugin/TestSourceReadResource.java | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourceReadResource.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourceReadResource.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourceReadResource.java deleted file mode 100644 index d9fd4104ce..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourceReadResource.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.core.resource.Resource; -import io.cucumber.plugin.event.TestSourceRead; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.net.URI; - -import static java.nio.charset.StandardCharsets.UTF_8; - -final class TestSourceReadResource implements Resource { - - private final TestSourceRead event; - - TestSourceReadResource(TestSourceRead event) { - this.event = event; - } - - @Override - public URI getUri() { - return event.getUri(); - } - - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(event.getSource().getBytes(UTF_8)); - } - -}