DiffOutputStream.java
/*
* Copyright (C) 2018-2021 Google, LLC.
* SPDX-License-Identifier: BSD-3-Clause
*/
package de.jflex.testing.diff;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.base.Strings;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import org.junit.ComparisonFailure;
/**
* An output streams that asserts that every printed lined is equal to the one from the expected
* input.
*
* <p>Each input line must be less than 2048 bytes.
*
* <p>The output is not saved anywhere.
*/
public class DiffOutputStream extends OutputStream {
/** Constant for the Unicode {@code UTF-8}charset. */
private static final String UTF_8 = "UTF-8";
/** The golden content this OutputStream will compare against. */
private final BufferedReader in;
/** The internal buffer where actual data is stored. */
private final byte[] buf = new byte[2048];
/** The current lines being compared. Only {@code \n} serves as a line separator. */
private int line = 1;
/**
* Handles assertion failures when the content differs. By default, just throws the {@link
* ComparisonFailure}.
*/
private ComparisonFailureHandler comparisonFailureHandler =
failure -> {
throw failure;
};
/**
* The number of valid bytes in the buffer.
*
* <p>The useful buffer count is in range {@code 0} - {@code count} (excl) of {@link #buf}.
*/
protected int count;
public DiffOutputStream(Reader in) {
this(new BufferedReader(in));
}
@SuppressWarnings("WeakerAccess")
public DiffOutputStream(BufferedReader in) {
this.in = in;
}
@Override
public void write(int b) throws IOException {
buf[count] = (byte) b;
if (b == '\n') {
String expectedLine = in.readLine();
try {
assertThatWrittenWasExpected(expectedLine);
} catch (ComparisonFailure failure) {
comparisonFailureHandler.handle(failure);
}
count = 0;
line++;
} else {
count++;
}
}
private void assertThatWrittenWasExpected(String expectedLine) throws IOException {
if (Strings.isNullOrEmpty(expectedLine)) {
failOnDifferentLine("");
return;
}
byte[] expectedRaw = expectedLine.getBytes(UTF_8);
if (count != expectedLine.length()) {
failOnDifferentLine(expectedLine);
}
int length = count;
for (int i = 0; i < length; i++) {
if (buf[i] != expectedRaw[i]) {
failOnDifferentLine(expectedLine);
}
}
}
private void failOnDifferentLine(String expectedLine) throws UnsupportedEncodingException {
byte[] actualRaw = new byte[count];
System.arraycopy(buf, 0, actualRaw, 0, count);
String actualLine = new String(actualRaw, UTF_8);
assertWithMessage("Content differs on line %s:\n", line)
.that(actualLine)
.isEqualTo(expectedLine);
}
public String remainingContent() {
char[] extraInput = new char[140];
try {
int read = in.read(extraInput);
if (read <= 0) {
return "";
}
String extraContent = new String(extraInput, 0, read);
if (read == extraInput.length) {
return extraContent + "\n[...]\n";
} else {
return extraContent;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() throws IOException {
assertWithMessage("All expected content has been output").that(remainingContent()).isEmpty();
super.close();
}
public void setComparisonFailureHandler(ComparisonFailureHandler comparisonFailureHandler) {
this.comparisonFailureHandler = comparisonFailureHandler;
}
public interface ComparisonFailureHandler {
void handle(ComparisonFailure failure);
}
}