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..4fef8e2f81 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CucumberJvmJson.java @@ -0,0 +1,422 @@ +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 new file mode 100644 index 0000000000..2a0cd26497 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/IdNamingVisitor.java @@ -0,0 +1,61 @@ +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 f36526e4ea..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 @@ -1,442 +1,47 @@ 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.messages.types.Envelope; +import io.cucumber.plugin.ConcurrentEventListener; 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.File; 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; +import static io.cucumber.core.plugin.MessagesToJsonWriter.builder; -public final class JsonFormatter implements EventListener { +public final class JsonFormatter implements ConcurrentEventListener { - 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); + URI cwdUri = new File("").toURI(); + this.writer = builder(Jackson.OBJECT_MAPPER::writeValue) + .relativizeAgainst(cwdUri) + .build(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); + publisher.registerHandlerFor(Envelope.class, this::write); } - 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)); - } - + private void write(Envelope event) { try { - Jackson.OBJECT_MAPPER.writeValue(writer, featureMaps); - writer.close(); + writer.write(event); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalStateException(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); + // 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); } - 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/JsonReportWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java new file mode 100644 index 0000000000..8446851f83 --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonReportWriter.java @@ -0,0 +1,504 @@ +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(new JvmFeatureDataComparator()) + // 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 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/MessagesToJsonWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java new file mode 100644 index 0000000000..b53f46a10e --- /dev/null +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/MessagesToJsonWriter.java @@ -0,0 +1,129 @@ +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 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(); + } +} 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; - } - - } - -} 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; + } +} 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 532e444942..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 @@ -6,49 +6,38 @@ public class StubHookDefinition implements HookDefinition { private static final String STUBBED_LOCATION_WITH_DETAILS = "{stubbed location with details}"; - private final String location; + private final Located location; private final RuntimeException exception; private final Consumer action; - private final SourceReference sourceReference; private final HookType hookType; public StubHookDefinition( - String location, RuntimeException exception, Consumer action, - SourceReference sourceReference, HookType hookType + Located location, RuntimeException exception, Consumer action, HookType hookType ) { this.location = location; this.exception = exception; this.action = action; - this.sourceReference = sourceReference; this.hookType = hookType; } - public StubHookDefinition(String location, Consumer action) { - this(location, null, action, null, null); - } - - public StubHookDefinition() { - this(STUBBED_LOCATION_WITH_DETAILS, null, null, null, null); + public StubHookDefinition(SourceReference location, HookType hookType, Consumer action) { + this(new StubLocation(location), null, action, hookType); } public StubHookDefinition(Consumer action) { - this(STUBBED_LOCATION_WITH_DETAILS, null, action, null, null); + this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), null, action, null); } public StubHookDefinition(RuntimeException exception) { - this(STUBBED_LOCATION_WITH_DETAILS, exception, null, null, null); - } - - public StubHookDefinition(String location) { - this(location, null, null, null, null); + this(new StubLocation(STUBBED_LOCATION_WITH_DETAILS), exception, null, null); } public StubHookDefinition(SourceReference sourceReference, HookType hookType) { - this(null, null, null, sourceReference, hookType); + this(new StubLocation(sourceReference), null, null, hookType); } public StubHookDefinition(SourceReference sourceReference, HookType hookType, RuntimeException exception) { - this(null, exception, null, sourceReference, hookType); + this(new StubLocation(sourceReference), exception, null, hookType); } @Override @@ -78,12 +67,12 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { @Override public String getLocation() { - return location; + return location.getLocation(); } @Override public Optional getSourceReference() { - return Optional.ofNullable(sourceReference); + return location.getSourceReference(); } @Override 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..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 @@ -1,11 +1,37 @@ package io.cucumber.core.backend; +import java.lang.reflect.Method; +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(Method method) { + this.location = null; + this.sourceReference = SourceReference.fromMethod(method); + } + + public StubLocation(SourceReference sourceReference) { + 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 @@ -13,6 +39,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/backend/StubStepDefinition.java b/cucumber-core/src/test/java/io/cucumber/core/backend/StubStepDefinition.java index d47e8d06a1..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 @@ -16,36 +16,31 @@ public class StubStepDefinition implements StepDefinition { private final String expression; private final Throwable exception; private final Located location; - private SourceReference sourceReference; public StubStepDefinition(String pattern, String location, Type... types) { - this(pattern, location, null, types); + this(pattern, new StubLocation(location), null, types); } - public StubStepDefinition(String pattern, Type... types) { - this(pattern, STUBBED_LOCATION_WITH_DETAILS, null, types); + public StubStepDefinition(String pattern, SourceReference location, Type... types) { + this(pattern, new StubLocation(location), null, types); } - public StubStepDefinition(String pattern, Throwable exception, Type... types) { - this(pattern, STUBBED_LOCATION_WITH_DETAILS, exception, types); + public StubStepDefinition(String pattern, Type... types) { + this(pattern, new StubLocation(STUBBED_LOCATION_WITH_DETAILS), null, types); } - public StubStepDefinition(String pattern, String 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; + public StubStepDefinition(String pattern, Throwable exception, Type... types) { + this(pattern, new StubLocation(STUBBED_LOCATION_WITH_DETAILS), exception, types); } - public StubStepDefinition(String pattern, SourceReference sourceReference, Type... types) { - this(pattern, sourceReference, null, types); + public StubStepDefinition(String pattern, SourceReference location, Throwable exception, Type... types) { + this(pattern, new StubLocation(location), exception, types); } - public StubStepDefinition(String pattern, SourceReference sourceReference, Throwable exception, Type... 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; - this.location = new StubLocation(""); - this.sourceReference = sourceReference; + this.location = location; this.exception = exception; } @@ -86,7 +81,7 @@ public String getPattern() { @Override public Optional getSourceReference() { - return Optional.ofNullable(sourceReference); + return location.getSourceReference(); } private static final class StubParameterInfo implements ParameterInfo { 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 0a13c2cf50..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 @@ -1,7 +1,10 @@ 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; @@ -21,6 +24,9 @@ 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; @@ -33,6 +39,26 @@ 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 { + return SourceReference.fromMethod(JsonFormatterTestStepDefinitions.class.getMethod(name)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + @Test void featureWithOutlineTest() throws JSONException { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -57,21 +83,21 @@ 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"), - 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)); } @@ -168,9 +194,9 @@ 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()"))) + new StubStepDefinition("there are bananas", thereAreBananas))) .build() .run(); @@ -198,7 +224,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -229,7 +256,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("the stack trace")))) .build() .run(); @@ -258,7 +285,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"failed\",\n" + @@ -291,7 +319,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(); @@ -317,7 +345,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -358,7 +387,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(); @@ -382,7 +411,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"keyword\": \"Given \"\n" + " },\n" + @@ -394,7 +424,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -417,7 +448,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"keyword\": \"Given \"\n" + " }\n" + @@ -453,7 +485,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(); @@ -481,7 +513,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -518,9 +551,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(); @@ -546,7 +579,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -569,7 +603,8 @@ 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.JsonFormatterTestStepDefinitions#monkey_eats_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -590,7 +625,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -613,7 +649,8 @@ 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.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -645,7 +682,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(); @@ -671,7 +708,8 @@ 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.JsonFormatterTestStepDefinitions#monkey_eats_more_bananas()\"\n" + + " },\n" + " \"keyword\": \"Then \"\n" + " }\n" + @@ -732,9 +770,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, HookDefinition.HookType.BEFORE)), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), + singletonList(new StubHookDefinition(afterHook1, HookDefinition.HookType.AFTER)))) .build() .run(); @@ -759,7 +797,8 @@ void should_format_scenario_with_hooks() throws JSONException { " \"before\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.before_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -773,7 +812,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -784,7 +824,8 @@ void should_format_scenario_with_hooks() throws JSONException { " \"after\": [\n" + " {\n" + " \"match\": {\n" + - " \"location\": \"Hooks.after_hook_1()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#after_hook_1()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -817,13 +858,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", "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()")), + new StubHookDefinition(afterStepHook1, AFTER_STEP), + new StubHookDefinition(afterStepHook2, AFTER_STEP)), emptyList())) .build() .run(); @@ -854,14 +895,16 @@ 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.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + " \"line\": 4,\n" + " \"name\": \"there are bananas\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"after\": [\n" + " {\n" + @@ -870,7 +913,8 @@ 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.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + + " }\n" + " },\n" + " {\n" + @@ -879,7 +923,8 @@ 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.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -897,14 +942,16 @@ 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.JsonFormatterTestStepDefinitions#beforestep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + " \"line\": 5,\n" + " \"name\": \"monkey arrives\",\n" + " \"match\": {\n" + - " \"location\": \"StepDefs.monkey_arrives()\"\n" + + " \"location\": \"io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#monkey_arrives()\"\n" + + " },\n" + " \"after\": [\n" + " {\n" + @@ -913,7 +960,8 @@ 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.JsonFormatterTestStepDefinitions#afterstep_hook_2()\"\n" + + " }\n" + " },\n" + " {\n" + @@ -922,7 +970,8 @@ 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.JsonFormatterTestStepDefinitions#afterstep_hook_1()\"\n" + + " }\n" + " }\n" + " ],\n" + @@ -957,9 +1006,9 @@ 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, BEFORE, 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(); @@ -985,7 +1034,8 @@ 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.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"output\": [\n" + " \"printed from hook\"\n" + @@ -1002,7 +1052,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1033,10 +1084,11 @@ 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", "StepDefs.there_are_bananas()")), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) .build() .run(); @@ -1062,7 +1114,8 @@ 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.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1082,7 +1135,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1113,10 +1167,10 @@ 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", "StepDefs.there_are_bananas()")), + singletonList(new StubStepDefinition("there are bananas", thereAreBananas)), emptyList())) .build() .run(); @@ -1142,7 +1196,8 @@ 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.JsonFormatterTestStepDefinitions#before_hook_1()\"\n" + + " },\n" + " \"embeddings\": [\n" + " {\n" + @@ -1163,7 +1218,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1197,7 +1253,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(); @@ -1229,7 +1285,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1263,7 +1320,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(); @@ -1296,7 +1353,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1329,7 +1387,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(); @@ -1371,7 +1429,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1407,8 +1466,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(); @@ -1436,7 +1495,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_bananas()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1470,7 +1530,8 @@ 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.JsonFormatterTestStepDefinitions#there_are_oranges()\"\n" + + " },\n" + " \"result\": {\n" + " \"status\": \"passed\",\n" + @@ -1485,4 +1546,5 @@ void should_handle_several_features() throws JSONException { "]"; assertJsonEquals(expected, out); } + } 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 b86942ff95..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": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -27,7 +27,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -38,7 +38,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -52,7 +52,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -70,7 +70,7 @@ "line": 9, "name": "step_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_1()" }, "keyword": "Given " }, @@ -81,7 +81,7 @@ "line": 10, "name": "step_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_2()" }, "keyword": "When " }, @@ -92,7 +92,7 @@ "line": 11, "name": "step_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#step_3()" }, "keyword": "Then " }, @@ -103,7 +103,7 @@ "line": 12, "name": "cliché", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#cliche()" }, "keyword": "Then " } @@ -123,7 +123,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -134,7 +134,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -145,7 +145,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -159,7 +159,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -183,7 +183,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" }, "keyword": "Given " }, @@ -200,7 +200,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" }, "keyword": "When " }, @@ -217,7 +217,7 @@ "offset": 0 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" }, "keyword": "Then " } @@ -237,7 +237,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -248,7 +248,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -259,7 +259,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -273,7 +273,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -297,7 +297,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_1()" }, "keyword": "Given " }, @@ -314,7 +314,7 @@ "offset": 5 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_2()" }, "keyword": "When " }, @@ -331,7 +331,7 @@ "offset": 0 } ], - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#so_3()" }, "keyword": "Then " } @@ -351,7 +351,7 @@ "line": 4, "name": "bg_1", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_1()" }, "keyword": "Given " }, @@ -362,7 +362,7 @@ "line": 5, "name": "bg_2", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_2()" }, "keyword": "When " }, @@ -373,7 +373,7 @@ "line": 6, "name": "bg_3", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#bg_3()" }, "keyword": "Then " } @@ -387,7 +387,7 @@ "status": "passed" }, "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#before_hook_1()" } } ], @@ -405,7 +405,7 @@ "line": 25, "name": "a", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#a()" }, "keyword": "Given " }, @@ -416,7 +416,7 @@ "line": 26, "name": "b", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#b()" }, "keyword": "Then " }, @@ -427,7 +427,7 @@ "line": 27, "name": "c", "match": { - "location": "{stubbed location with details}" + "location": "io.cucumber.core.plugin.JsonFormatterTestStepDefinitions#c()" }, "keyword": "When " }