Skip to content

Commit c6f43f3

Browse files
authored
Upgrade @cucumber/compatibility-kit (#3072)
1 parent e9d50e6 commit c6f43f3

File tree

72 files changed

+353
-1405
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+353
-1405
lines changed

.github/workflows/test-testdata.yml

Lines changed: 0 additions & 39 deletions
This file was deleted.

compatibility/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
</dependencyManagement>
4646

4747
<dependencies>
48+
<dependency>
49+
<groupId>io.cucumber</groupId>
50+
<artifactId>compatibility-kit</artifactId>
51+
<version>25.0.0</version>
52+
</dependency>
4853
<dependency>
4954
<groupId>io.cucumber</groupId>
5055
<artifactId>cucumber-java</artifactId>

compatibility/src/test/java/io/cucumber/compatibility/AComparableMessage.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ protected boolean matchesSafely(JsonNode item, Description mismatchDescription)
9797
.appendText(pointer.toString()).appendText(" ")
9898
.appendText(actual.toString()).appendText(" ");
9999
// 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());
100+
System.out.printf("%s.put(Pattern.compile(\"%s\"), isA(%s.class));%n", messageType, pointer,
101+
actual.getClass().getSimpleName());
103102
return false;
104103
}
105104
}

compatibility/src/test/java/io/cucumber/compatibility/CompatibilityTest.java

Lines changed: 122 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,31 @@
44
import com.fasterxml.jackson.databind.DeserializationFeature;
55
import com.fasterxml.jackson.databind.JsonNode;
66
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.node.ArrayNode;
78
import com.fasterxml.jackson.databind.node.IntNode;
89
import com.fasterxml.jackson.databind.node.MissingNode;
910
import com.fasterxml.jackson.databind.node.NumericNode;
1011
import com.fasterxml.jackson.databind.node.TextNode;
1112
import io.cucumber.core.options.RuntimeOptionsBuilder;
13+
import io.cucumber.core.order.PickleOrder;
14+
import io.cucumber.core.order.StandardPickleOrders;
1215
import io.cucumber.core.plugin.MessageFormatter;
1316
import io.cucumber.core.runtime.Runtime;
1417
import org.hamcrest.Matcher;
1518
import org.junit.jupiter.params.ParameterizedTest;
1619
import org.junit.jupiter.params.provider.MethodSource;
20+
import org.junit.platform.commons.io.ResourceFilter;
21+
import org.junit.platform.commons.support.ResourceSupport;
1722

23+
import java.io.BufferedReader;
1824
import java.io.IOException;
25+
import java.io.InputStream;
26+
import java.io.InputStreamReader;
1927
import java.nio.file.Files;
2028
import java.nio.file.Path;
2129
import java.nio.file.Paths;
2230
import java.util.ArrayList;
31+
import java.util.Arrays;
2332
import java.util.Comparator;
2433
import java.util.Iterator;
2534
import java.util.LinkedHashMap;
@@ -28,9 +37,12 @@
2837
import java.util.regex.Pattern;
2938
import java.util.stream.Collectors;
3039

40+
import static java.nio.charset.StandardCharsets.UTF_8;
3141
import static java.nio.file.Files.newOutputStream;
42+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
3243
import static java.util.Collections.emptyList;
3344
import static java.util.Collections.emptyMap;
45+
import static java.util.Comparator.comparing;
3446
import static org.hamcrest.CoreMatchers.anyOf;
3547
import static org.hamcrest.CoreMatchers.is;
3648
import static org.hamcrest.MatcherAssert.assertThat;
@@ -40,14 +52,37 @@
4052

4153
public class CompatibilityTest {
4254

43-
private static final Map<String, Map<Pattern, Matcher<?>>> exceptions = createExceptions();
44-
45-
private static Map<String, Map<Pattern, Matcher<?>>> createExceptions() {
55+
private static final List<String> unsupportedTestCases = Arrays.asList(
56+
// exception: not applicable
57+
"test-run-exception",
58+
// exception: Cucumber JVM does not support named hooks
59+
"hooks-named",
60+
// exception: Cucumber executes all hooks,
61+
// but skipped hooks can skip a scenario
62+
"hooks-skipped",
63+
// exception: Cucumber JVM does not support markdown features
64+
"markdown",
65+
// exception: Cucumber JVM does not support retrying features
66+
"retry",
67+
"retry-ambiguous",
68+
"retry-pending",
69+
// exception: Cucumber JVM does not support messages for global hooks
70+
"global-hooks",
71+
"global-hooks-afterall-error",
72+
"global-hooks-attachments",
73+
"global-hooks-beforeall-error");
74+
75+
private static final Map<String, Map<Pattern, Matcher<?>>> divergingExpectations = createDivergingExpectations();
76+
77+
private static Map<String, Map<Pattern, Matcher<?>>> createDivergingExpectations() {
4678
Map<String, Map<Pattern, Matcher<?>>> exceptions = new LinkedHashMap<>();
4779

4880
Map<Pattern, Matcher<?>> attachment = new LinkedHashMap<>();
4981
attachment.put(Pattern.compile("/testCaseStartedId"), isA(TextNode.class));
5082
attachment.put(Pattern.compile("/testStepId"), isA(TextNode.class));
83+
// exception: timestamps and durations are not predictable
84+
attachment.put(Pattern.compile("/timestamp/seconds"), isA(NumericNode.class));
85+
attachment.put(Pattern.compile("/timestamp/nanos"), isA(NumericNode.class));
5186
exceptions.put("attachment", attachment);
5287

5388
Map<Pattern, Matcher<?>> meta = new LinkedHashMap<>();
@@ -76,15 +111,17 @@ private static Map<String, Map<Pattern, Matcher<?>>> createExceptions() {
76111
exceptions.put("source", source);
77112

78113
Map<Pattern, Matcher<?>> gherkinDocument = new LinkedHashMap<>();
114+
// exception: ids are not predictable
79115
gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/id"), isA(TextNode.class));
80116
gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/steps/.*/id"), isA(TextNode.class));
81117
gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/examples/.*/id"), isA(TextNode.class));
82118
gherkinDocument.put(Pattern.compile("/feature/children/.*/rule/id"), isA(TextNode.class));
83119
gherkinDocument.put(Pattern.compile("/feature/children/.*/rule/tags/.*/id"), isA(TextNode.class));
84120
gherkinDocument.put(Pattern.compile("/feature/children/.*/scenario/tags/.*/id"), isA(TextNode.class));
85-
121+
gherkinDocument.put(Pattern.compile("/feature/children/.*/background/id"), isA(TextNode.class));
122+
gherkinDocument.put(Pattern.compile("/feature/children/.*/background/steps/.*/id"), isA(TextNode.class));
123+
// exception: the CCK uses relative paths as uris
86124
gherkinDocument.put(Pattern.compile("/uri"), isA(TextNode.class));
87-
88125
exceptions.put("gherkinDocument", gherkinDocument);
89126

90127
Map<Pattern, Matcher<?>> pickle = new LinkedHashMap<>();
@@ -94,7 +131,6 @@ private static Map<String, Map<Pattern, Matcher<?>>> createExceptions() {
94131
pickle.put(Pattern.compile("/astNodeIds/.*"), isA(TextNode.class));
95132
pickle.put(Pattern.compile("/steps/.*/id"), isA(TextNode.class));
96133
pickle.put(Pattern.compile("/steps/.*/astNodeIds/.*"), isA(TextNode.class));
97-
98134
pickle.put(Pattern.compile("/tags/.*/astNodeId"), isA(TextNode.class));
99135
pickle.put(Pattern.compile("/name"), isA(TextNode.class));
100136
exceptions.put("pickle", pickle);
@@ -196,50 +232,37 @@ private static Map<String, Map<Pattern, Matcher<?>>> createExceptions() {
196232
parameterType.put(Pattern.compile("/sourceReference/location/line"), isA(MissingNode.class));
197233
exceptions.put("parameterType", parameterType);
198234

199-
return exceptions;
200-
}
201-
202-
@ParameterizedTest
203-
@MethodSource("io.cucumber.compatibility.TestCase#testCases")
204-
void produces_expected_output_for(TestCase testCase) throws IOException {
205-
Path parentDir = Files.createDirectories(Paths.get("target", "messages",
206-
testCase.getId()));
207-
Path outputNdjson = parentDir.resolve("out.ndjson");
235+
Map<Pattern, Matcher<?>> suggestion = new LinkedHashMap<>();
236+
// exception: ids are not predictable
237+
suggestion.put(Pattern.compile("/id"), isA(TextNode.class));
238+
suggestion.put(Pattern.compile("/pickleStepId"), isA(TextNode.class));
239+
// exception: language is implementation specific
240+
suggestion.put(Pattern.compile("/snippets/.*/language"), isA(TextNode.class));
241+
// exception: code is implementation specific
242+
suggestion.put(Pattern.compile("/snippets/.*/code"), isA(TextNode.class));
208243

209-
try {
210-
Runtime.builder()
211-
.withRuntimeOptions(new RuntimeOptionsBuilder()
212-
.addGlue(testCase.getGlue())
213-
.addFeature(testCase.getFeatures()).build())
214-
.withAdditionalPlugins(
215-
new MessageFormatter(newOutputStream(outputNdjson)))
216-
.build()
217-
.run();
218-
} catch (Exception e) {
219-
// exception: Scenario with unknown parameter types fails by
220-
// throwing an exceptions
221-
if (!"unknown-parameter-type".equals(testCase.getId())) {
222-
throw e;
223-
}
224-
}
244+
exceptions.put("suggestion", suggestion);
225245

226-
// exception: Cucumber JVM does not support named hooks
227-
if ("hooks-named".equals(testCase.getId())) {
228-
return;
229-
}
246+
return exceptions;
247+
}
230248

231-
// exception: Cucumber JVM does not support markdown features
232-
if ("markdown".equals(testCase.getId())) {
233-
return;
234-
}
249+
static List<TestCase> acceptance() {
250+
ResourceFilter ndjson = ResourceFilter.of(resource -> resource.getName().endsWith(".ndjson"));
251+
return ResourceSupport.findAllResourcesInPackage(TestCase.TEST_CASES_PACKAGE, ndjson)
252+
.stream()
253+
.map(TestCase::new)
254+
.filter(testCase -> !unsupportedTestCases.contains(testCase.getId()))
255+
.sorted(comparing(TestCase::getId))
256+
.collect(Collectors.toList());
257+
}
235258

236-
// exception: Cucumber JVM does not support retrying features
237-
if ("retry".equals(testCase.getId())) {
238-
return;
239-
}
259+
@ParameterizedTest
260+
@MethodSource("acceptance")
261+
void test(TestCase testCase) throws IOException {
262+
Path actualNdjson = writeNdjsonReport(testCase);
240263

241264
List<JsonNode> expected = readAllMessages(testCase.getExpectedFile());
242-
List<JsonNode> actual = readAllMessages(outputNdjson);
265+
List<JsonNode> actual = readAllMessages(Files.newInputStream(actualNdjson));
243266

244267
Map<String, List<JsonNode>> expectedEnvelopes = openEnvelopes(expected);
245268
Map<String, List<JsonNode>> actualEnvelopes = openEnvelopes(actual);
@@ -268,6 +291,17 @@ void produces_expected_output_for(TestCase testCase) throws IOException {
268291
expectedEnvelopes.remove("testStepStarted");
269292
expectedEnvelopes.remove("testStepFinished");
270293
expectedEnvelopes.remove("testCaseFinished");
294+
expectedEnvelopes.remove("suggestion");
295+
}
296+
297+
if ("undefined".equals(testCase.getId())) {
298+
// bug: Cucumber JVM doesn't produce a suggestion that matches float
299+
((ArrayNode) expectedEnvelopes.get("suggestion").get(3).get("snippets")).remove(1);
300+
}
301+
if ("ambiguous".equals(testCase.getId())) {
302+
// bug: Cucumber JVM doesn't include the ambiguous step definitions
303+
// https://github.com/cucumber/cucumber-jvm/issues/3006
304+
expectedEnvelopes.remove("testCase");
271305
}
272306

273307
expectedEnvelopes.forEach((messageType, expectedMessages) -> assertThat(
@@ -276,14 +310,44 @@ void produces_expected_output_for(TestCase testCase) throws IOException {
276310
containsInRelativeOrder(aComparableMessage(messageType, expectedMessages)))));
277311
}
278312

279-
private static List<JsonNode> readAllMessages(Path output) throws IOException {
313+
private static Path writeNdjsonReport(TestCase testCase) throws IOException {
314+
Path parentDir = Files.createDirectories(Paths.get("target", "messages", testCase.getId()));
315+
Path actualNdjson = parentDir.resolve("actual.ndjson");
316+
Path expectedNdjson = parentDir.resolve("expected.ndjson");
317+
Files.copy(testCase.getExpectedFile(), expectedNdjson, REPLACE_EXISTING);
318+
319+
try {
320+
PickleOrder pickleOrder = StandardPickleOrders.lexicalUriOrder();
321+
if ("multiple-features-reversed".equals(testCase.getId())) {
322+
pickleOrder = StandardPickleOrders.reverseLexicalUriOrder();
323+
}
324+
Runtime.builder()
325+
.withRuntimeOptions(new RuntimeOptionsBuilder()
326+
.addGlue(testCase.getGlue())
327+
.setPickleOrder(pickleOrder)
328+
.addFeature(testCase.getFeatures()).build())
329+
.withAdditionalPlugins(
330+
new MessageFormatter(newOutputStream(actualNdjson)))
331+
.build()
332+
.run();
333+
} catch (Exception e) {
334+
// exception: Scenario with unknown parameter types fails by
335+
// throwing an exceptions
336+
if (!"unknown-parameter-type".equals(testCase.getId())) {
337+
throw e;
338+
}
339+
}
340+
return actualNdjson;
341+
}
342+
343+
private static List<JsonNode> readAllMessages(InputStream output) throws IOException {
280344
List<JsonNode> expectedEnvelopes = new ArrayList<>();
281345

282346
ObjectMapper mapper = new ObjectMapper()
283347
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
284348
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
285349

286-
Files.readAllLines(output).forEach(s -> {
350+
readAllLines(output).forEach(s -> {
287351
try {
288352
expectedEnvelopes.add(mapper.readTree(s));
289353
} catch (JsonProcessingException e) {
@@ -294,6 +358,17 @@ private static List<JsonNode> readAllMessages(Path output) throws IOException {
294358
return expectedEnvelopes;
295359
}
296360

361+
public static List<String> readAllLines(InputStream is) throws IOException {
362+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) {
363+
List<String> lines = new ArrayList<>();
364+
String line;
365+
while ((line = reader.readLine()) != null) {
366+
lines.add(line);
367+
}
368+
return lines;
369+
}
370+
}
371+
297372
@SuppressWarnings("unchecked")
298373
private static <T> Map<String, List<T>> openEnvelopes(List<JsonNode> actual) {
299374
Map<String, List<T>> map = new LinkedHashMap<>();
@@ -329,7 +404,7 @@ private void sortStepDefinitionsAndHooks(Map<String, List<JsonNode>> envelopes)
329404
private static List<Matcher<? super JsonNode>> aComparableMessage(String messageType, List<JsonNode> messages) {
330405
return messages.stream()
331406
.map(jsonNode -> new AComparableMessage(messageType, jsonNode,
332-
exceptions.getOrDefault(messageType, emptyMap())))
407+
divergingExpectations.getOrDefault(messageType, emptyMap())))
333408
.collect(Collectors.toList());
334409
}
335410

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.cucumber.compatibility;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
7+
public final class Resources {
8+
9+
private Resources() {
10+
// utility class
11+
}
12+
13+
public static byte[] read(String name) throws IOException {
14+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
15+
try (InputStream is = Resources.class.getResourceAsStream(name)) {
16+
int read;
17+
byte[] data = new byte[4096];
18+
while ((read = is.read(data, 0, data.length)) != -1) {
19+
bytes.write(data, 0, read);
20+
}
21+
}
22+
return bytes.toByteArray();
23+
}
24+
}

0 commit comments

Comments
 (0)