From e03ed36f55db6a216a36f17c21f604c2c01b44ad Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 13 Nov 2025 19:02:43 +0100 Subject: [PATCH 1/3] Add OS version to Meta message Reports build from messages include meta information about the operating system, runtime and cpu architecture. This was missing the OS version. --- .../java/io/cucumber/core/runtime/CucumberExecutionContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/runtime/CucumberExecutionContext.java b/cucumber-core/src/main/java/io/cucumber/core/runtime/CucumberExecutionContext.java index dd413010d7..739e5d5113 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/runtime/CucumberExecutionContext.java +++ b/cucumber-core/src/main/java/io/cucumber/core/runtime/CucumberExecutionContext.java @@ -66,7 +66,7 @@ private Meta createMeta() { ProtocolVersion.getVersion(), new Product("cucumber-jvm", VERSION), new Product(System.getProperty("java.vm.name"), System.getProperty("java.vm.version")), - new Product(System.getProperty("os.name"), null), + new Product(System.getProperty("os.name"), System.getProperty("os.version")), new Product(System.getProperty("os.arch"), null), detectCiEnvironment(System.getenv()).map(ci -> new Ci( ci.getName(), From 2b02a5c8defe25309466e1c668198fc14147b088 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Wed, 19 Nov 2025 20:02:58 +0100 Subject: [PATCH 2/3] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d48881d3..78a28a1603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [7.31.0] - 2025-10-27 ### Added - [Core] Add a `UsageJsonFormatter`, use with `--plugin usage-json` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje) +- [Core] Add OS version to `Meta` message ([#3108](https://github.com/cucumber/cucumber-jvm/pull/3108)) ### Changed - [Core] Update dependency io.cucumber:ci-environment to v12.0.0 From e915a69deb7bbaf381b1d8cfa76eaf1db5decd9d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 21 Nov 2025 00:02:35 +0100 Subject: [PATCH 3/3] Refactor exceptions to compatibility test --- CHANGELOG.md | 2 + .../compatibility/AComparableMessage.java | 244 +++++++----------- .../compatibility/CompatibilityTest.java | 170 +++++++++++- 3 files changed, 258 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a28a1603..d3f041a7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [7.31.0] - 2025-10-27 ### Added - [Core] Add a `UsageJsonFormatter`, use with `--plugin usage-json` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje) + +### Fixed - [Core] Add OS version to `Meta` message ([#3108](https://github.com/cucumber/cucumber-jvm/pull/3108)) ### Changed diff --git a/compatibility/src/test/java/io/cucumber/compatibility/AComparableMessage.java b/compatibility/src/test/java/io/cucumber/compatibility/AComparableMessage.java index 5fffec8b72..a658a9bd24 100644 --- a/compatibility/src/test/java/io/cucumber/compatibility/AComparableMessage.java +++ b/compatibility/src/test/java/io/cucumber/compatibility/AComparableMessage.java @@ -1,194 +1,124 @@ package io.cucumber.compatibility; +import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.BooleanNode; -import com.fasterxml.jackson.databind.node.NumericNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import org.hamcrest.CoreMatchers; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; -import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.Spliterator; -import java.util.stream.Collectors; - -import static java.util.Spliterators.spliteratorUnknownSize; -import static java.util.stream.StreamSupport.stream; -import static org.hamcrest.CoreMatchers.anyOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.isA; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.collection.IsEmptyIterable.emptyIterable; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; -import static org.hamcrest.collection.IsIterableContainingInRelativeOrder.containsInRelativeOrder; -import static org.hamcrest.collection.IsMapContaining.hasEntry; -import static org.hamcrest.collection.IsMapContaining.hasKey; - -public class AComparableMessage extends - TypeSafeDiagnosingMatcher { - - private final List> expectedFields; - private final int depth; - - public AComparableMessage(String messageType, JsonNode expectedMessage) { - this(messageType, expectedMessage, 0); - } +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; + +public class AComparableMessage extends TypeSafeDiagnosingMatcher { - AComparableMessage(String messageType, JsonNode expectedMessage, int depth) { - this.depth = depth + 1; - this.expectedFields = extractExpectedFields(messageType, expectedMessage, this.depth); + private final JsonNode expectedMessage; + private final String messageType; + private final Map> replacements; + private final Map expectedFields; + private final Map> expectedMatchers; + + public AComparableMessage(String messageType, JsonNode expectedMessage, Map> replacements) { + this.expectedMessage = expectedMessage; + this.messageType = requireNonNull(messageType); + this.replacements = requireNonNull(replacements); + this.expectedFields = extractFieldsAndPointers(requireNonNull(expectedMessage)); + this.expectedMatchers = createMatchers(expectedFields); } - private static List> extractExpectedFields(String messageType, JsonNode expectedMessage, int depth) { - List> expected = new ArrayList<>(); - asMapOfJsonNameToField(expectedMessage).forEach((fieldName, expectedValue) -> { - switch (fieldName) { - // exception: error messages are platform specific - case "exception": - case "message": - expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass()))); - expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass()))); - break; - - // exception: the CCK uses relative paths as uris - case "uri": - expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass()))); - break; - - // exception: the CCK expects source references with URIs but - // Java can only provide method and stack trace references. - case "sourceReference": - expected.add(hasKey(is(fieldName))); - break; - - // exception: ids are not predictable - case "id": - // exception: not yet implemented - if ("testRunStarted".equals(messageType)) { - expected.add(not(hasKey(fieldName))); - break; - } - case "pickleId": - case "astNodeId": - case "hookId": - case "pickleStepId": - case "testCaseId": - case "testStepId": - case "testCaseStartedId": - expected.add(hasEntry(is(fieldName), isA(TextNode.class))); - break; - // exception: not yet implemented - case "testRunStartedId": - expected.add(not(hasKey(fieldName))); - break; - // exception: protocolVersion can vary - case "protocolVersion": - expected.add(hasEntry(is(fieldName), isA(TextNode.class))); - break; - case "astNodeIds": - case "stepDefinitionIds": - if (expectedValue instanceof ArrayNode) { - ArrayNode expectedValues = (ArrayNode) expectedValue; - if (expectedValues.isEmpty()) { - expected.add(hasEntry(is(fieldName), emptyIterable())); - } else { - expected.add(hasEntry(is(fieldName), containsInRelativeOrder(isA(TextNode.class)))); - } - break; - } - // exception: timestamps and durations are not predictable - case "timestamp": - case "duration": - expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass()))); - break; - - // exception: Mata fields depend on the platform - case "implementation": - case "runtime": - case "os": - case "cpu": - expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass()))); - break; - case "ci": - // exception: Absent when running locally, present in ci - expected.add( - anyOf(not(hasKey(is(fieldName))), hasEntry(is(fieldName), - isA(expectedValue.getClass())))); - break; - default: - expected.add(hasEntry(is(fieldName), aComparableValue(messageType, - expectedValue, - depth))); - } + private Map> createMatchers(Map expectedFields) { + Map> expectedMatchers = new LinkedHashMap<>(); + expectedFields.forEach((jsonPointer, node) -> { + Matcher defaultValue = CoreMatchers.equalTo(node); + expectedMatchers.put(jsonPointer, findReplacement(jsonPointer, defaultValue)); }); - return expected; + return expectedMatchers; } - @SuppressWarnings("unchecked") - private static Matcher aComparableValue(String messageType, Object value, int depth) { - if (value instanceof ObjectNode) { - JsonNode message = (JsonNode) value; - return new AComparableMessage(messageType, message, depth); - } - - if (value instanceof ArrayNode) { - ArrayNode values = (ArrayNode) value; - Spliterator spliterator = spliteratorUnknownSize(values.iterator(), 0); - List> allComparableValues = stream(spliterator, false) - .map(o -> aComparableValue(messageType, o, depth)) - .map(o -> (Matcher) o) - .collect(Collectors.toList()); - if (allComparableValues.isEmpty()) { - return emptyIterable(); + private Matcher findReplacement(JsonPointer jsonPointer, Matcher defaultValue) { + for (Map.Entry> entry : replacements.entrySet()) { + if (entry.getKey().matcher(jsonPointer.toString()).matches()) { + return entry.getValue(); } - return contains(allComparableValues); } + return defaultValue; + } + + private Map extractFieldsAndPointers(JsonNode node) { + JsonPointer path = JsonPointer.empty(); + return extractFieldsAndPointers(path, node); + } - if (value instanceof TextNode - || value instanceof NumericNode - || value instanceof BooleanNode) { - return CoreMatchers.is(value); + private Map extractFieldsAndPointers(JsonPointer path, JsonNode node) { + if (node instanceof ObjectNode) { + return extractFieldsAndPointers(path, (ObjectNode) node); } - throw new IllegalArgumentException("Unsupported type " + value.getClass() + - ": " + value); + if (node instanceof ArrayNode) { + return extractFieldsAndPointers(path, (ArrayNode) node); + } + return Collections.singletonMap(path, node); } - @Override - public void describeTo(Description description) { - StringBuilder padding = new StringBuilder(); - for (int i = 0; i < depth + 1; i++) { - padding.append("\t"); + private Map extractFieldsAndPointers(JsonPointer path, ObjectNode node) { + Map expectedFields = new LinkedHashMap<>(); + node.fieldNames().forEachRemaining(fieldName -> { + JsonNode field = node.get(fieldName); + JsonPointer fieldPath = path.appendProperty(fieldName); + expectedFields.putAll(extractFieldsAndPointers(fieldPath, field)); + }); + return expectedFields; + } + + private Map extractFieldsAndPointers(JsonPointer path, ArrayNode node) { + Map expectedFields = new LinkedHashMap<>(); + for (int i = 0, size = node.size(); i < size; i++) { + JsonNode element = node.get(i); + JsonPointer elementPath = path.appendIndex(i); + expectedFields.putAll(extractFieldsAndPointers(elementPath, element)); } - description.appendList("\n" + padding, ",\n" + padding, - "\n", expectedFields); + return expectedFields; } @Override - protected boolean matchesSafely(JsonNode actual, Description mismatchDescription) { - Map actualFields = asMapOfJsonNameToField(actual); - for (Matcher expectedField : expectedFields) { - if (!expectedField.matches(actualFields)) { - expectedField.describeMismatch(actualFields, mismatchDescription); + protected boolean matchesSafely(JsonNode item, Description mismatchDescription) { + for (Map.Entry> entry : expectedMatchers.entrySet()) { + JsonPointer pointer = entry.getKey(); + Matcher expected = entry.getValue(); + JsonNode actual = item.at(pointer); + + if (!expected.matches(actual)) { + mismatchDescription + .appendText(pointer.toString()).appendText(" ") + .appendText(actual.toString()).appendText(" "); + // Copy and paste needed to suppress this finding. + // System.out.printf("%s.put(Pattern.compile(\"%s\"), + // isA(%s.class));%n", messageType, key, + // actual.getClass().getSimpleName()); return false; } } return true; } - private static Map asMapOfJsonNameToField(JsonNode envelope) { - Map map = new LinkedHashMap<>(); - envelope.fieldNames() - .forEachRemaining(jsonField -> { - JsonNode value = envelope.get(jsonField); - map.put(jsonField, value); - }); - return map; + @Override + public void describeTo(Description description) { + description.appendValue(expectedMessage); } + @Override + public String toString() { + return "AComparableMessage{" + + "expectedMessage=" + expectedMessage + + ", messageType='" + messageType + '\'' + + ", replacements=" + replacements + + ", expectedFields=" + expectedFields + + ", expectedMatchers=" + expectedMatchers + + '}'; + } } diff --git a/compatibility/src/test/java/io/cucumber/compatibility/CompatibilityTest.java b/compatibility/src/test/java/io/cucumber/compatibility/CompatibilityTest.java index cc0a26f3f0..a85bbe99d5 100644 --- a/compatibility/src/test/java/io/cucumber/compatibility/CompatibilityTest.java +++ b/compatibility/src/test/java/io/cucumber/compatibility/CompatibilityTest.java @@ -4,6 +4,10 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.NumericNode; +import com.fasterxml.jackson.databind.node.TextNode; import io.cucumber.core.options.RuntimeOptionsBuilder; import io.cucumber.core.plugin.MessageFormatter; import io.cucumber.core.runtime.Runtime; @@ -21,17 +25,180 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.nio.file.Files.newOutputStream; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInRelativeOrder.containsInRelativeOrder; import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.core.Is.isA; public class CompatibilityTest { + private static final Map>> exceptions = createExceptions(); + + private static Map>> createExceptions() { + Map>> exceptions = new LinkedHashMap<>(); + + Map> attachment = new LinkedHashMap<>(); + attachment.put(Pattern.compile("/testCaseStartedId"), isA(TextNode.class)); + attachment.put(Pattern.compile("/testStepId"), isA(TextNode.class)); + exceptions.put("attachment", attachment); + + Map> meta = new LinkedHashMap<>(); + // exception: protocolVersion can vary + meta.put(Pattern.compile("/protocolVersion"), isA(TextNode.class)); + // exception: Mata fields depend on the platform + meta.put(Pattern.compile("/implementation/name"), isA(TextNode.class)); + meta.put(Pattern.compile("/implementation/version"), isA(TextNode.class)); + meta.put(Pattern.compile("/cpu/name"), isA(TextNode.class)); + meta.put(Pattern.compile("/os/name"), isA(TextNode.class)); + meta.put(Pattern.compile("/os/version"), isA(TextNode.class)); + meta.put(Pattern.compile("/runtime/name"), isA(TextNode.class)); + meta.put(Pattern.compile("/runtime/version"), isA(TextNode.class)); + // exceptioN: Ci information depends on where the tests are ran + Matcher value = anyOf(isA(MissingNode.class), isA(TextNode.class)); + meta.put(Pattern.compile("/ci/name"), value); + meta.put(Pattern.compile("/ci/url"), value); + meta.put(Pattern.compile("/ci/buildNumber"), value); + meta.put(Pattern.compile("/ci/git/revision"), value); + meta.put(Pattern.compile("/ci/git/remote"), value); + meta.put(Pattern.compile("/ci/git/branch"), value); + exceptions.put("meta", meta); + + Map> source = new LinkedHashMap<>(); + source.put(Pattern.compile("/uri"), isA(TextNode.class)); + exceptions.put("source", source); + + Map> gherkinDocument = new LinkedHashMap<>(); + gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/id"), isA(TextNode.class)); + gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/steps/.*/id"), isA(TextNode.class)); + gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/examples/.*/id"), isA(TextNode.class)); + gherkinDocument.put(Pattern.compile("/feature/children/.*/rule/id"), isA(TextNode.class)); + gherkinDocument.put(Pattern.compile("/feature/children/.*/rule/tags/.*/id"), isA(TextNode.class)); + gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/tags/.*/id"), isA(TextNode.class)); + + gherkinDocument.put(Pattern.compile("/uri"), isA(TextNode.class)); + + exceptions.put("gherkinDocument", gherkinDocument); + + Map> pickle = new LinkedHashMap<>(); + // exception: ids are not predictable + pickle.put(Pattern.compile("/id"), isA(TextNode.class)); + pickle.put(Pattern.compile("/uri"), isA(TextNode.class)); + pickle.put(Pattern.compile("/astNodeIds/.*"), isA(TextNode.class)); + pickle.put(Pattern.compile("/steps/.*/id"), isA(TextNode.class)); + pickle.put(Pattern.compile("/steps/.*/astNodeIds/.*"), isA(TextNode.class)); + + pickle.put(Pattern.compile("/tags/.*/astNodeId"), isA(TextNode.class)); + pickle.put(Pattern.compile("/name"), isA(TextNode.class)); + exceptions.put("pickle", pickle); + + Map> stepDefinition = new LinkedHashMap<>(); + // exception: ids are not predictable + stepDefinition.put(Pattern.compile("/id"), isA(TextNode.class)); + // exception: the CCK uses relative paths as uris + stepDefinition.put(Pattern.compile("/sourceReference/uri"), isA(MissingNode.class)); + stepDefinition.put(Pattern.compile("/sourceReference/location/line"), isA(MissingNode.class)); + exceptions.put("stepDefinition", stepDefinition); + + Map> testRunStarted = new LinkedHashMap<>(); + // exception: not yet implemented + testRunStarted.put(Pattern.compile("/id"), isA(MissingNode.class)); + // exception: timestamps and durations are not predictable + testRunStarted.put(Pattern.compile("/timestamp/seconds"), isA(NumericNode.class)); + testRunStarted.put(Pattern.compile("/timestamp/nanos"), isA(NumericNode.class)); + exceptions.put("testRunStarted", testRunStarted); + + Map> testCase = new LinkedHashMap<>(); + // exception: ids are not predictable + testCase.put(Pattern.compile("/id"), isA(TextNode.class)); + testCase.put(Pattern.compile("/pickleId"), isA(TextNode.class)); + testCase.put(Pattern.compile("/testSteps/.*/id"), isA(TextNode.class)); + testCase.put(Pattern.compile("/testSteps/.*/pickleStepId"), isA(TextNode.class)); + testCase.put(Pattern.compile("/testSteps/.*/stepDefinitionIds/.*"), isA(TextNode.class)); + testCase.put(Pattern.compile("/testSteps/.*/hookId"), isA(TextNode.class)); + // exception: not yet implemented + testCase.put(Pattern.compile("/testRunStartedId"), isA(MissingNode.class)); + exceptions.put("testCase", testCase); + + Map> testCaseStarted = new LinkedHashMap<>(); + // exception: ids are not predictable + testCaseStarted.put(Pattern.compile("/id"), isA(TextNode.class)); + testCaseStarted.put(Pattern.compile("/testCaseId"), isA(TextNode.class)); + // exception: timestamps and durations are not predictable + testCaseStarted.put(Pattern.compile("/timestamp/seconds"), isA(IntNode.class)); + testCaseStarted.put(Pattern.compile("/timestamp/nanos"), isA(IntNode.class)); + exceptions.put("testCaseStarted", testCaseStarted); + + Map> testStepStarted = new LinkedHashMap<>(); + testStepStarted.put(Pattern.compile("/testCaseStartedId"), isA(TextNode.class)); + testStepStarted.put(Pattern.compile("/testStepId"), isA(TextNode.class)); + // exception: timestamps and durations are not predictable + testStepStarted.put(Pattern.compile("/timestamp/seconds"), isA(IntNode.class)); + testStepStarted.put(Pattern.compile("/timestamp/nanos"), isA(IntNode.class)); + exceptions.put("testStepStarted", testStepStarted); + + Map> testStepFinished = new LinkedHashMap<>(); + // exception: ids are not predictable + testStepFinished.put(Pattern.compile("/testCaseStartedId"), isA(TextNode.class)); + // exception: ids are not predictable + testStepFinished.put(Pattern.compile("/testStepId"), isA(TextNode.class)); + // exception: timestamps and durations are not predictable + testStepFinished.put(Pattern.compile("/testStepResult/duration/seconds"), isA(IntNode.class)); + testStepFinished.put(Pattern.compile("/testStepResult/duration/nanos"), isA(IntNode.class)); + // exception: error messages are platform specific + testStepFinished.put(Pattern.compile("/testStepResult/message"), isA(TextNode.class)); + // exception: exceptions are platform specific + testStepFinished.put(Pattern.compile("/testStepResult/exception/type"), isA(TextNode.class)); + testStepFinished.put(Pattern.compile("/testStepResult/exception/message"), isA(TextNode.class)); + testStepFinished.put(Pattern.compile("/testStepResult/exception/stackTrace"), isA(TextNode.class)); + // exception: timestamps and durations are not predictable + testStepFinished.put(Pattern.compile("/timestamp/seconds"), isA(IntNode.class)); + testStepFinished.put(Pattern.compile("/timestamp/nanos"), isA(IntNode.class)); + exceptions.put("testStepFinished", testStepFinished); + + Map> testCaseFinished = new LinkedHashMap<>(); + // exception: ids are not predictable + testCaseFinished.put(Pattern.compile("/testCaseStartedId"), isA(TextNode.class)); + // exception: timestamps and durations are not predictable + testCaseFinished.put(Pattern.compile("/timestamp/seconds"), isA(IntNode.class)); + testCaseFinished.put(Pattern.compile("/timestamp/nanos"), isA(IntNode.class)); + exceptions.put("testCaseFinished", testCaseFinished); + + Map> testRunFinished = new LinkedHashMap<>(); + // exception: not yet implemented + testRunFinished.put(Pattern.compile("/testRunStartedId"), isA(MissingNode.class)); + // exception: timestamps and durations are not predictable + testRunFinished.put(Pattern.compile("/timestamp/seconds"), isA(IntNode.class)); + testRunFinished.put(Pattern.compile("/timestamp/nanos"), isA(IntNode.class)); + exceptions.put("testRunFinished", testRunFinished); + + Map> hook = new LinkedHashMap<>(); + // exception: ids are not predictable + hook.put(Pattern.compile("/id"), isA(TextNode.class)); + // exception: the CCK expects source references with URIs but + // Java can only provide method and stack trace references. + hook.put(Pattern.compile("/sourceReference/uri"), isA(MissingNode.class)); + hook.put(Pattern.compile("/sourceReference/location/line"), isA(MissingNode.class)); + exceptions.put("hook", hook); + + Map> parameterType = new LinkedHashMap<>(); + // exception: ids are not predictable + parameterType.put(Pattern.compile("/id"), isA(TextNode.class)); + // exception: the CCK uses relative paths as uris + parameterType.put(Pattern.compile("/sourceReference/uri"), isA(MissingNode.class)); + parameterType.put(Pattern.compile("/sourceReference/location/line"), isA(MissingNode.class)); + exceptions.put("parameterType", parameterType); + + return exceptions; + } + @ParameterizedTest @MethodSource("io.cucumber.compatibility.TestCase#testCases") void produces_expected_output_for(TestCase testCase) throws IOException { @@ -161,7 +328,8 @@ private void sortStepDefinitionsAndHooks(Map> envelopes) private static List> aComparableMessage(String messageType, List messages) { return messages.stream() - .map(jsonNode -> new AComparableMessage(messageType, jsonNode)) + .map(jsonNode -> new AComparableMessage(messageType, jsonNode, + exceptions.getOrDefault(messageType, emptyMap()))) .collect(Collectors.toList()); }