Skip to content

Commit a6192a4

Browse files
authored
Add OS version to Meta message (#3108)
Reports build from messages include meta information about the operating system, runtime and cpu architecture. This was missing the OS version.
1 parent d6d500d commit a6192a4

File tree

4 files changed

+260
-159
lines changed

4 files changed

+260
-159
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Added
1616
- [Core] Add a `UsageJsonFormatter`, use with `--plugin usage-json` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje)
1717

18+
### Fixed
19+
- [Core] Add OS version to `Meta` message ([#3108](https://github.com/cucumber/cucumber-jvm/pull/3108))
20+
1821
### Changed
1922
- [Core] Update dependency io.cucumber:ci-environment to v12.0.0
2023
- [Core] Update dependency io.cucumber:cucumber-json-formatter to v0.3.0
Lines changed: 87 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,194 +1,124 @@
11
package io.cucumber.compatibility;
22

3+
import com.fasterxml.jackson.core.JsonPointer;
34
import com.fasterxml.jackson.databind.JsonNode;
45
import com.fasterxml.jackson.databind.node.ArrayNode;
5-
import com.fasterxml.jackson.databind.node.BooleanNode;
6-
import com.fasterxml.jackson.databind.node.NumericNode;
76
import com.fasterxml.jackson.databind.node.ObjectNode;
8-
import com.fasterxml.jackson.databind.node.TextNode;
97
import org.hamcrest.CoreMatchers;
108
import org.hamcrest.Description;
119
import org.hamcrest.Matcher;
1210
import org.hamcrest.TypeSafeDiagnosingMatcher;
1311

14-
import java.util.ArrayList;
12+
import java.util.Collections;
1513
import java.util.LinkedHashMap;
16-
import java.util.List;
1714
import java.util.Map;
18-
import java.util.Spliterator;
19-
import java.util.stream.Collectors;
20-
21-
import static java.util.Spliterators.spliteratorUnknownSize;
22-
import static java.util.stream.StreamSupport.stream;
23-
import static org.hamcrest.CoreMatchers.anyOf;
24-
import static org.hamcrest.CoreMatchers.is;
25-
import static org.hamcrest.CoreMatchers.isA;
26-
import static org.hamcrest.CoreMatchers.not;
27-
import static org.hamcrest.collection.IsEmptyIterable.emptyIterable;
28-
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
29-
import static org.hamcrest.collection.IsIterableContainingInRelativeOrder.containsInRelativeOrder;
30-
import static org.hamcrest.collection.IsMapContaining.hasEntry;
31-
import static org.hamcrest.collection.IsMapContaining.hasKey;
32-
33-
public class AComparableMessage extends
34-
TypeSafeDiagnosingMatcher<JsonNode> {
35-
36-
private final List<Matcher<?>> expectedFields;
37-
private final int depth;
38-
39-
public AComparableMessage(String messageType, JsonNode expectedMessage) {
40-
this(messageType, expectedMessage, 0);
41-
}
15+
import java.util.regex.Pattern;
16+
17+
import static java.util.Objects.requireNonNull;
18+
19+
public class AComparableMessage extends TypeSafeDiagnosingMatcher<JsonNode> {
4220

43-
AComparableMessage(String messageType, JsonNode expectedMessage, int depth) {
44-
this.depth = depth + 1;
45-
this.expectedFields = extractExpectedFields(messageType, expectedMessage, this.depth);
21+
private final JsonNode expectedMessage;
22+
private final String messageType;
23+
private final Map<Pattern, Matcher<?>> replacements;
24+
private final Map<JsonPointer, JsonNode> expectedFields;
25+
private final Map<JsonPointer, Matcher<?>> expectedMatchers;
26+
27+
public AComparableMessage(String messageType, JsonNode expectedMessage, Map<Pattern, Matcher<?>> replacements) {
28+
this.expectedMessage = expectedMessage;
29+
this.messageType = requireNonNull(messageType);
30+
this.replacements = requireNonNull(replacements);
31+
this.expectedFields = extractFieldsAndPointers(requireNonNull(expectedMessage));
32+
this.expectedMatchers = createMatchers(expectedFields);
4633
}
4734

48-
private static List<Matcher<?>> extractExpectedFields(String messageType, JsonNode expectedMessage, int depth) {
49-
List<Matcher<?>> expected = new ArrayList<>();
50-
asMapOfJsonNameToField(expectedMessage).forEach((fieldName, expectedValue) -> {
51-
switch (fieldName) {
52-
// exception: error messages are platform specific
53-
case "exception":
54-
case "message":
55-
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
56-
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
57-
break;
58-
59-
// exception: the CCK uses relative paths as uris
60-
case "uri":
61-
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
62-
break;
63-
64-
// exception: the CCK expects source references with URIs but
65-
// Java can only provide method and stack trace references.
66-
case "sourceReference":
67-
expected.add(hasKey(is(fieldName)));
68-
break;
69-
70-
// exception: ids are not predictable
71-
case "id":
72-
// exception: not yet implemented
73-
if ("testRunStarted".equals(messageType)) {
74-
expected.add(not(hasKey(fieldName)));
75-
break;
76-
}
77-
case "pickleId":
78-
case "astNodeId":
79-
case "hookId":
80-
case "pickleStepId":
81-
case "testCaseId":
82-
case "testStepId":
83-
case "testCaseStartedId":
84-
expected.add(hasEntry(is(fieldName), isA(TextNode.class)));
85-
break;
86-
// exception: not yet implemented
87-
case "testRunStartedId":
88-
expected.add(not(hasKey(fieldName)));
89-
break;
90-
// exception: protocolVersion can vary
91-
case "protocolVersion":
92-
expected.add(hasEntry(is(fieldName), isA(TextNode.class)));
93-
break;
94-
case "astNodeIds":
95-
case "stepDefinitionIds":
96-
if (expectedValue instanceof ArrayNode) {
97-
ArrayNode expectedValues = (ArrayNode) expectedValue;
98-
if (expectedValues.isEmpty()) {
99-
expected.add(hasEntry(is(fieldName), emptyIterable()));
100-
} else {
101-
expected.add(hasEntry(is(fieldName), containsInRelativeOrder(isA(TextNode.class))));
102-
}
103-
break;
104-
}
105-
// exception: timestamps and durations are not predictable
106-
case "timestamp":
107-
case "duration":
108-
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
109-
break;
110-
111-
// exception: Mata fields depend on the platform
112-
case "implementation":
113-
case "runtime":
114-
case "os":
115-
case "cpu":
116-
expected.add(hasEntry(is(fieldName), isA(expectedValue.getClass())));
117-
break;
118-
case "ci":
119-
// exception: Absent when running locally, present in ci
120-
expected.add(
121-
anyOf(not(hasKey(is(fieldName))), hasEntry(is(fieldName),
122-
isA(expectedValue.getClass()))));
123-
break;
124-
default:
125-
expected.add(hasEntry(is(fieldName), aComparableValue(messageType,
126-
expectedValue,
127-
depth)));
128-
}
35+
private Map<JsonPointer, Matcher<?>> createMatchers(Map<JsonPointer, JsonNode> expectedFields) {
36+
Map<JsonPointer, Matcher<?>> expectedMatchers = new LinkedHashMap<>();
37+
expectedFields.forEach((jsonPointer, node) -> {
38+
Matcher<JsonNode> defaultValue = CoreMatchers.equalTo(node);
39+
expectedMatchers.put(jsonPointer, findReplacement(jsonPointer, defaultValue));
12940
});
130-
return expected;
41+
return expectedMatchers;
13142
}
13243

133-
@SuppressWarnings("unchecked")
134-
private static Matcher<?> aComparableValue(String messageType, Object value, int depth) {
135-
if (value instanceof ObjectNode) {
136-
JsonNode message = (JsonNode) value;
137-
return new AComparableMessage(messageType, message, depth);
138-
}
139-
140-
if (value instanceof ArrayNode) {
141-
ArrayNode values = (ArrayNode) value;
142-
Spliterator<JsonNode> spliterator = spliteratorUnknownSize(values.iterator(), 0);
143-
List<Matcher<? super Object>> allComparableValues = stream(spliterator, false)
144-
.map(o -> aComparableValue(messageType, o, depth))
145-
.map(o -> (Matcher<? super Object>) o)
146-
.collect(Collectors.toList());
147-
if (allComparableValues.isEmpty()) {
148-
return emptyIterable();
44+
private Matcher<?> findReplacement(JsonPointer jsonPointer, Matcher<JsonNode> defaultValue) {
45+
for (Map.Entry<Pattern, Matcher<?>> entry : replacements.entrySet()) {
46+
if (entry.getKey().matcher(jsonPointer.toString()).matches()) {
47+
return entry.getValue();
14948
}
150-
return contains(allComparableValues);
15149
}
50+
return defaultValue;
51+
}
52+
53+
private Map<JsonPointer, JsonNode> extractFieldsAndPointers(JsonNode node) {
54+
JsonPointer path = JsonPointer.empty();
55+
return extractFieldsAndPointers(path, node);
56+
}
15257

153-
if (value instanceof TextNode
154-
|| value instanceof NumericNode
155-
|| value instanceof BooleanNode) {
156-
return CoreMatchers.is(value);
58+
private Map<JsonPointer, JsonNode> extractFieldsAndPointers(JsonPointer path, JsonNode node) {
59+
if (node instanceof ObjectNode) {
60+
return extractFieldsAndPointers(path, (ObjectNode) node);
15761
}
158-
throw new IllegalArgumentException("Unsupported type " + value.getClass() +
159-
": " + value);
62+
if (node instanceof ArrayNode) {
63+
return extractFieldsAndPointers(path, (ArrayNode) node);
64+
}
65+
return Collections.singletonMap(path, node);
16066
}
16167

162-
@Override
163-
public void describeTo(Description description) {
164-
StringBuilder padding = new StringBuilder();
165-
for (int i = 0; i < depth + 1; i++) {
166-
padding.append("\t");
68+
private Map<JsonPointer, JsonNode> extractFieldsAndPointers(JsonPointer path, ObjectNode node) {
69+
Map<JsonPointer, JsonNode> expectedFields = new LinkedHashMap<>();
70+
node.fieldNames().forEachRemaining(fieldName -> {
71+
JsonNode field = node.get(fieldName);
72+
JsonPointer fieldPath = path.appendProperty(fieldName);
73+
expectedFields.putAll(extractFieldsAndPointers(fieldPath, field));
74+
});
75+
return expectedFields;
76+
}
77+
78+
private Map<JsonPointer, JsonNode> extractFieldsAndPointers(JsonPointer path, ArrayNode node) {
79+
Map<JsonPointer, JsonNode> expectedFields = new LinkedHashMap<>();
80+
for (int i = 0, size = node.size(); i < size; i++) {
81+
JsonNode element = node.get(i);
82+
JsonPointer elementPath = path.appendIndex(i);
83+
expectedFields.putAll(extractFieldsAndPointers(elementPath, element));
16784
}
168-
description.appendList("\n" + padding, ",\n" + padding,
169-
"\n", expectedFields);
85+
return expectedFields;
17086
}
17187

17288
@Override
173-
protected boolean matchesSafely(JsonNode actual, Description mismatchDescription) {
174-
Map<String, Object> actualFields = asMapOfJsonNameToField(actual);
175-
for (Matcher<?> expectedField : expectedFields) {
176-
if (!expectedField.matches(actualFields)) {
177-
expectedField.describeMismatch(actualFields, mismatchDescription);
89+
protected boolean matchesSafely(JsonNode item, Description mismatchDescription) {
90+
for (Map.Entry<JsonPointer, Matcher<?>> entry : expectedMatchers.entrySet()) {
91+
JsonPointer pointer = entry.getKey();
92+
Matcher<?> expected = entry.getValue();
93+
JsonNode actual = item.at(pointer);
94+
95+
if (!expected.matches(actual)) {
96+
mismatchDescription
97+
.appendText(pointer.toString()).appendText(" ")
98+
.appendText(actual.toString()).appendText(" ");
99+
// Copy and paste needed to suppress this finding.
100+
// System.out.printf("%s.put(Pattern.compile(\"%s\"),
101+
// isA(%s.class));%n", messageType, key,
102+
// actual.getClass().getSimpleName());
178103
return false;
179104
}
180105
}
181106
return true;
182107
}
183108

184-
private static Map<String, Object> asMapOfJsonNameToField(JsonNode envelope) {
185-
Map<String, Object> map = new LinkedHashMap<>();
186-
envelope.fieldNames()
187-
.forEachRemaining(jsonField -> {
188-
JsonNode value = envelope.get(jsonField);
189-
map.put(jsonField, value);
190-
});
191-
return map;
109+
@Override
110+
public void describeTo(Description description) {
111+
description.appendValue(expectedMessage);
192112
}
193113

114+
@Override
115+
public String toString() {
116+
return "AComparableMessage{" +
117+
"expectedMessage=" + expectedMessage +
118+
", messageType='" + messageType + '\'' +
119+
", replacements=" + replacements +
120+
", expectedFields=" + expectedFields +
121+
", expectedMatchers=" + expectedMatchers +
122+
'}';
123+
}
194124
}

0 commit comments

Comments
 (0)