package org.junit.jupiter.api;
import static java.lang.String.format;
import static java.lang.String.join;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.platform.commons.util.Preconditions.condition;
import static org.junit.platform.commons.util.Preconditions.notNull;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.regex.PatternSyntaxException;
import java.util.stream.IntStream;
class AssertLinesMatch {
private AssertLinesMatch() {
}
private final static int MAX_SNIPPET_LENGTH = 21;
static void assertLinesMatch(List<String> expectedLines, List<String> actualLines) {
notNull(expectedLines, "expectedLines must not be null");
notNull(actualLines, "actualLines must not be null");
if (expectedLines == actualLines) {
return;
}
int expectedSize = expectedLines.size();
int actualSize = actualLines.size();
if (expectedSize > actualSize) {
fail(expectedLines, actualLines, "expected %d lines, but only got %d", expectedSize, actualSize);
}
if (expectedSize == actualSize) {
if (IntStream.range(0, expectedSize).allMatch(i -> matches(expectedLines.get(i), actualLines.get(i)))) {
return;
}
}
assertLinesMatchWithFastForward(expectedLines, actualLines);
}
private static void assertLinesMatchWithFastForward(List<String> expectedLines, List<String> actualLines) {
Deque<String> expectedDeque = new ArrayDeque<>(expectedLines);
Deque<String> actualDeque = new ArrayDeque<>(actualLines);
main: while (!expectedDeque.isEmpty()) {
String expectedLine = expectedDeque.pop();
int expectedLineNumber = expectedLines.size() - expectedDeque.size();
if (actualDeque.isEmpty()) {
fail(expectedLines, actualLines, "expected line #%d:`%s` not found - actual lines depleted",
expectedLineNumber, snippet(expectedLine));
}
String actualLine = actualDeque.peek();
if (matches(expectedLine, actualLine)) {
actualDeque.pop();
continue;
}
if (isFastForwardLine(expectedLine)) {
int fastForwardLimit = parseFastForwardLimit(expectedLine);
if (expectedDeque.isEmpty()) {
int actualRemaining = actualDeque.size();
if (fastForwardLimit == Integer.MAX_VALUE || fastForwardLimit == actualRemaining) {
return;
}
fail(expectedLines, actualLines, "terminal fast-forward(%d) error: fast-forward(%d) expected",
fastForwardLimit, actualRemaining);
}
if (fastForwardLimit != Integer.MAX_VALUE) {
for (int i = 0; i < fastForwardLimit; i++) {
actualDeque.pop();
}
continue;
}
expectedLine = expectedDeque.peek();
while (true) {
if (actualDeque.isEmpty()) {
fail(expectedLines, actualLines, "fast-forward(∞) didn't find: `%s`", snippet(expectedLine));
}
if (matches(expectedLine, actualDeque.peek())) {
continue main;
}
actualDeque.pop();
}
}
fail(expectedLines, actualLines, "expected line #%d:`%s` doesn't match", expectedLineNumber,
snippet(expectedLine));
}
if (!actualDeque.isEmpty()) {
fail(expectedLines, actualLines, "more actual lines than expected: %d", actualDeque.size());
}
}
private static String snippet(String line) {
if (line.length() <= MAX_SNIPPET_LENGTH) {
return line;
}
return line.substring(0, MAX_SNIPPET_LENGTH - 5) + "[...]";
}
private static void fail(List<String> expectedLines, List<String> actualLines, String format, Object... args) {
if (expectedLines.size() > MAX_SNIPPET_LENGTH) {
expectedLines.subList(0, MAX_SNIPPET_LENGTH);
}
if (actualLines.size() > MAX_SNIPPET_LENGTH) {
actualLines.subList(0, MAX_SNIPPET_LENGTH);
}
String expected = join(System.lineSeparator(), expectedLines);
String actual = join(System.lineSeparator(), actualLines);
assertEquals(expected, actual, format(format, args));
}
static boolean isFastForwardLine(String line) {
line = line.trim();
return line.length() >= 4 && line.startsWith(">>") && line.endsWith(">>");
}
static int parseFastForwardLimit(String fastForwardLine) {
String text = fastForwardLine.trim().substring(2, fastForwardLine.length() - 2).trim();
try {
int limit = Integer.parseInt(text);
condition(limit > 0, () -> format("fast-forward(%d) limit must be greater than zero", limit));
return limit;
}
catch (NumberFormatException e) {
return Integer.MAX_VALUE;
}
}
static boolean matches(String expectedLine, String actualLine) {
notNull(expectedLine, "expected line must not be null");
notNull(actualLine, "actual line must not be null");
if (expectedLine.equals(actualLine)) {
return true;
}
try {
return actualLine.matches(expectedLine);
}
catch (PatternSyntaxException ignore) {
return false;
}
}
}