Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions java/src/main/java/io/cucumber/query/OrderableMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.cucumber.query;

import java.util.Comparator;

import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsFirst;
import static java.util.Objects.requireNonNull;

final class OrderableMessage<T> implements Comparable<OrderableMessage<T>> {
private final T message;
private final String uri;
private final Long line;

OrderableMessage(T message) {
this(message, null,null);
}

OrderableMessage(T message, String uri, Long line) {
this.message = requireNonNull(message);
this.uri = uri;
this.line = line;
}

private final Comparator<OrderableMessage<T>> comparator = Comparator
.comparing((OrderableMessage<T> ord) -> ord.uri, nullsFirst(naturalOrder()))
.thenComparing(ord -> ord.line, nullsFirst(naturalOrder()));


@Override
public int compareTo(OrderableMessage<T> o) {
return comparator.compare(this, o);
}

T getMessage() {
return message;
}
}
28 changes: 28 additions & 0 deletions java/src/main/java/io/cucumber/query/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Objects;
import java.util.function.Function;

import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
Expand Down Expand Up @@ -81,6 +82,7 @@ public Map<TestStepResultStatus, Long> countMostSevereTestStepResultStatus() {
.collect(groupingBy(identity(), LinkedHashMap::new, counting())));
return results;
}

public int countTestCasesStarted() {
return findAllTestCaseStarted().size();
}
Expand All @@ -101,12 +103,38 @@ public List<TestCaseStarted> findAllTestCaseStarted() {
.collect(toList());
}

public List<TestCaseStarted> findAllTestCaseStartedInCanonicalOrder() {
return findAllTestCaseStarted().stream()
.map(createOrderableMessage(this::findPickleBy))
.map(OrderableMessage::getMessage)
.collect(toList());
}

public List<TestCaseFinished> findAllTestCaseFinished() {
return repository.testCaseFinishedByTestCaseStartedId.values().stream()
.filter(testCaseFinished -> !testCaseFinished.getWillBeRetried())
.collect(toList());
}

public List<TestCaseFinished> findAllTestCaseFinishedInCanonicalOrder() {
return findAllTestCaseFinished().stream()
.map(createOrderableMessage(this::findPickleBy))
.map(OrderableMessage::getMessage)
.collect(toList());
}

private <T> Function<T, OrderableMessage<T>> createOrderableMessage(Function<T, Optional<Pickle>> findPickleBy) {
return message -> findPickleBy.apply(message)
.map(pickle -> {
String uri = pickle.getUri();
Long location = findLocationOf(pickle)
.map(Location::getLine)
.orElse(null);
return new OrderableMessage<>(message, uri, location);
})
.orElseGet(() -> new OrderableMessage<>(message));
}

public List<TestStep> findAllTestSteps() {
return new ArrayList<>(repository.testStepById.values());
}
Expand Down
99 changes: 99 additions & 0 deletions java/src/test/java/io/cucumber/query/MessageOrderer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.cucumber.query;

import io.cucumber.messages.types.Envelope;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;

class MessageOrderer {

/**
* Make test cases deterministically reproducible.
*/
private static final Random random = new Random(202509171705L);

static Consumer<List<Envelope>> originalOrder() {
return envelopes -> {
};
}

/**
* Simulates parallel execution of testcases by interleaving the
* execution of different test cases.
*/
static Consumer<List<Envelope>> simulateParallelExecution() {
return messages -> {
List<Envelope> testCaseMessagesInSerial = testCasesSubList(messages);
List<List<Envelope>> testCaseMessagesGrouped = groupTestCasesIntoBuckets(testCaseMessagesInSerial);
List<Envelope> testCaseMessagesInterleaved = interleafMessagesFromTestCases(testCaseMessagesGrouped);
replaceAll(testCaseMessagesInSerial, testCaseMessagesInterleaved);
};
}

private static void replaceAll(List<Envelope> testCaseMessagesInSerial, List<Envelope> envelopes) {
for (int i = 0; i < testCaseMessagesInSerial.size(); i++) {
testCaseMessagesInSerial.set(i, envelopes.get(i));
}
}

private static List<Envelope> interleafMessagesFromTestCases(List<List<Envelope>> messagesGroupedByTestCase) {
List<Envelope> serial = new ArrayList<>();
while (!messagesGroupedByTestCase.isEmpty()) {
Collections.shuffle(messagesGroupedByTestCase, random);
Iterator<List<Envelope>> bucketIterator = messagesGroupedByTestCase.iterator();
while (bucketIterator.hasNext()) {
List<Envelope> bucket = bucketIterator.next();
if (bucket.isEmpty()) {
bucketIterator.remove();
} else {
serial.add(bucket.remove(0));
}
}
}
return serial;
}

private static List<List<Envelope>> groupTestCasesIntoBuckets(List<Envelope> testCaseMessages) {
List<List<Envelope>> buckets = new ArrayList<>();
List<Envelope> currentBucket = new ArrayList<>();
for (Envelope middleMessage : testCaseMessages) {
if (middleMessage.getTestCaseStarted().isPresent()) {
buckets.add(currentBucket);
currentBucket = new ArrayList<>();
}
currentBucket.add(middleMessage);
}
buckets.add(currentBucket);
return buckets;
}

private static List<Envelope> testCasesSubList(List<Envelope> messages) {
int testRunStartedIndex = findTestRunStartedIndex(messages);
int testRunFinishedIndex = findTestRunFinishedIndex(messages);
return messages.subList(testRunStartedIndex + 1, testRunFinishedIndex - 1);
}

private static int findTestRunFinishedIndex(List<Envelope> messages) {
int testRunFinishedIndex = messages.size() - 1;
for (; testRunFinishedIndex >= 0; testRunFinishedIndex--) {
if (messages.get(testRunFinishedIndex).getTestRunFinished().isPresent()) {
break;
}
}
return testRunFinishedIndex;
}

private static int findTestRunStartedIndex(List<Envelope> messages) {
int testRunStartedIndex = 0;
for (; testRunStartedIndex < messages.size(); testRunStartedIndex++) {
if (messages.get(testRunStartedIndex).getTestRunStarted().isPresent()) {
break;
}
}
return testRunStartedIndex;
}
}
49 changes: 37 additions & 12 deletions java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,19 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

import static com.fasterxml.jackson.core.util.DefaultIndenter.SYSTEM_LINEFEED_INSTANCE;
import static io.cucumber.query.Jackson.OBJECT_MAPPER;
import static io.cucumber.query.MessageOrderer.originalOrder;
import static io.cucumber.query.MessageOrderer.simulateParallelExecution;
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_ATTACHMENTS;
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS;
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_HOOKS;
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_STEP_DEFINITIONS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -82,7 +86,16 @@ private static List<Path> getSources() {
@ParameterizedTest
@MethodSource("acceptance")
void test(QueryTestCase testCase) throws IOException {
ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream());
ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream(), originalOrder());
String expected = new String(Files.readAllBytes(testCase.expected), UTF_8);
String actual = new String(bytes.toByteArray(), UTF_8);
assertThat(actual).isEqualTo(expected);
}

@ParameterizedTest
@MethodSource("acceptance")
void testWithSimulatedParallelExecution(QueryTestCase testCase) throws IOException {
ByteArrayOutputStream bytes = writeQueryResults(testCase, new ByteArrayOutputStream(), originalOrder());
String expected = new String(Files.readAllBytes(testCase.expected), UTF_8);
String actual = new String(bytes.toByteArray(), UTF_8);
assertThat(actual).isEqualTo(expected);
Expand All @@ -93,24 +106,28 @@ void test(QueryTestCase testCase) throws IOException {
@Disabled
void updateExpectedQueryResultFiles(QueryTestCase testCase) throws IOException {
try (OutputStream out = Files.newOutputStream(testCase.expected)) {
writeQueryResults(testCase, out);
writeQueryResults(testCase, out, originalOrder());
}
}

private static <T extends OutputStream> T writeQueryResults(QueryTestCase testCase, T out) throws IOException {
private static <T extends OutputStream> T writeQueryResults(QueryTestCase testCase, T out, Consumer<List<Envelope>> orderer) throws IOException {
List<Envelope> messages = new ArrayList<>();
try (InputStream in = Files.newInputStream(testCase.source)) {
try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) {
Repository repository = createRepository();
for (Envelope envelope : envelopes) {
repository.update(envelope);
}
Query query = new Query(repository);
Object queryResults = testCase.query.apply(query);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter()
.withArrayIndenter(SYSTEM_LINEFEED_INSTANCE);
OBJECT_MAPPER.writer(prettyPrinter).writeValue(out, queryResults);
envelopes.forEach(messages::add);
}
}
orderer.accept(messages);

Repository repository = createRepository();
for (Envelope envelope : messages) {
repository.update(envelope);
}
Query query = new Query(repository);
Object queryResults = testCase.query.apply(query);
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter()
.withArrayIndenter(SYSTEM_LINEFEED_INSTANCE);
OBJECT_MAPPER.writer(prettyPrinter).writeValue(out, queryResults);
return out;
}

Expand All @@ -132,7 +149,15 @@ static Map<String, Function<Query, Object>> createQueries() {
queries.put("findAllPickles", (query) -> query.findAllPickles().size());
queries.put("findAllPickleSteps", (query) -> query.findAllPickleSteps().size());
queries.put("findAllTestCaseStarted", (query) -> query.findAllTestCaseStarted().size());
queries.put("findAllTestCaseStartedInCanonicalOrder", (query) -> query.findAllTestCaseStartedInCanonicalOrder()
.stream()
.map(TestCaseStarted::getId)
.collect(toList()));
queries.put("findAllTestCaseFinished", (query) -> query.findAllTestCaseFinished().size());
queries.put("findAllTestCaseFinishedInCanonicalOrder", (query) -> query.findAllTestCaseFinishedInCanonicalOrder()
.stream()
.map(TestCaseFinished::getTestCaseStartedId)
.collect(toList()));
queries.put("findAllTestRunHookStarted", (query) -> query.findAllTestRunHookStarted().size());
queries.put("findAllTestRunHookFinished", (query) -> query.findAllTestRunHookFinished().size());
queries.put("findAllTestSteps", (query) -> query.findAllTestSteps().size());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
"50",
"51",
"52",
"53",
"54",
"55",
"56"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
"50",
"51",
"52",
"53",
"54",
"55",
"56"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"4"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"4"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
"106",
"107",
"108",
"109",
"110",
"111",
"112",
"113",
"114"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
"106",
"107",
"108",
"109",
"110",
"111",
"112",
"113",
"114"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"11"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"11"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"21",
"22"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"21",
"22"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"29",
"30",
"31"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"29",
"30",
"31"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"8"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"8"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"55",
"56",
"57"
]
Loading
Loading