package hu.advancedweb.scott.runtime.report;
import java.util.HashMap;
import java.util.Map;
import org.junit.runner.Description;
import hu.advancedweb.scott.runtime.report.javasource.MethodSource;
import hu.advancedweb.scott.runtime.report.javasource.SourcePathResolver;
import hu.advancedweb.scott.runtime.track.StateData;
import hu.advancedweb.scott.runtime.track.StateRegistry;
/**
* Renders the pretty-printed report optimized for terminals.
*
* @author David Csakvari
*/
public class FailureRenderer {
private static SourcePathResolver sourcePathResolver = new SourcePathResolver();
public static String render(Description description, Throwable throwable) {
final ScottReport scottReport = new ScottReport();
MethodSource methodSource = getTestMethodSource(description);
if (methodSource != null) {
fillSource(scottReport, methodSource);
}
fillTrackedData(scottReport);
fillException(scottReport, methodSource, throwable);
return renderPlain(scottReport);
}
private static MethodSource getTestMethodSource(Description description) {
try {
String testClassName = description.getTestClass().getCanonicalName();
String testSourcePath = sourcePathResolver.getSourcePath(testClassName);
String testMethodName = description.getMethodName();
return new MethodSource(testSourcePath, testClassName, testMethodName);
} catch (Exception e) {
try {
// As a fallback, look for the currently tracked method, and try to take its source.
String testClassName = StateRegistry.getTestClassType().replace("/", ".");
String testSourcePath = sourcePathResolver.getSourcePath(testClassName);
String testMethodName = StateRegistry.getTestMethodName();
return new MethodSource(testSourcePath, testClassName, testMethodName);
} catch (Exception e2) {
// Ignore, we simply don't fill the test source for the report.
// It's better than crashing the test run.
return null;
}
}
}
private static void fillSource(ScottReport scottReport, MethodSource methodSource) {
scottReport.setBeginLine(methodSource.getBeginLine());
for (String line : methodSource.getReportLines()) {
scottReport.addLine(line);
}
}
private static void fillTrackedData(ScottReport scottReport) {
Map<String, String> trackedValue = new HashMap<>();
for (StateData event : StateRegistry.getLocalVariableStates()) {
String lastValue = trackedValue.get(event.key);
if (!event.value.equals(lastValue)) {
if (event.lineNumber == 0) {
scottReport.addInitialSnapshot(getInitLine(event), StateRegistry.getLocalVariableName(event.key, event.lineNumber), event.value);
} else {
scottReport.addSnapshot(event.lineNumber, StateRegistry.getLocalVariableName(event.key, event.lineNumber), event.value);
}
trackedValue.put(event.key, event.value);
}
}
trackedValue = new HashMap<>();
for (StateData event : StateRegistry.getFieldStates()) {
String lastValue = trackedValue.get(event.key);
if (!event.value.equals(lastValue)) {
if (event.lineNumber == 0) {
scottReport.addInitialSnapshot(0, event.key, event.value);
} else {
scottReport.addSnapshot(event.lineNumber, event.key, event.value);
}
}
trackedValue.put(event.key, event.value);
}
}
private static int getInitLine(StateData event) {
final String methodScope = event.key.substring(0, event.key.indexOf("\\"));
if (methodScope.startsWith("lambda$")) {
return StateRegistry.getMethodStartLine().get(methodScope);
} else {
return 0;
}
}
private static void fillException(ScottReport scottReport, MethodSource methodSource, Throwable throwable) {
Integer lineNumber = scottReport.getBeginLineNumber();
if (methodSource != null) {
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
if (methodSource.getClassName().equals(stackTraceElement.getClassName()) &&
methodSource.getMethodName().equals(stackTraceElement.getMethodName())) {
lineNumber = stackTraceElement.getLineNumber();
break;
}
}
}
scottReport.setException(lineNumber, throwable.getClass().getSimpleName(), throwable.getMessage());
}
private static String renderPlain(ScottReport scottReport) {
boolean firstLineWithBraketAppended = false;
boolean initialReportAppended = false;
StringBuilder sb = new StringBuilder();
sb.append("\n");
int lastLineNumber = scottReport.getSourceLines().lastKey();
for (Map.Entry<Integer, String> line : scottReport.getSourceLines().entrySet()) {
int lineNumber = line.getKey();
String lineText = line.getValue().replaceAll("\t", " ");
boolean initialAdded = false;
if (firstLineWithBraketAppended && initialReportAppended == false) {
initialReportAppended = true;
if (!scottReport.getInitialSnapshots(0).isEmpty()) {
String blankLine = lineText.replaceFirst("[^\\s].*$", "");
for (Snapshot snapshot : scottReport.getInitialSnapshots(0)) {
sb.append(" ");
sb.append("| ");
sb.append(blankLine);
sb.append("// => ");
sb.append(snapshot.name + "=" + snapshot.value.trim());
sb.append("\n");
initialAdded = true;
}
}
}
if (!scottReport.getInitialSnapshots(lineNumber - 1).isEmpty()) {
String blankLine = lineText.replaceFirst("[^\\s].*$", "");
for (Snapshot snapshot : scottReport.getInitialSnapshots(lineNumber - 1)) {
sb.append(" ");
sb.append("| ");
sb.append(blankLine);
sb.append("// => ");
sb.append(snapshot.name + "=" + snapshot.value.trim());
sb.append("\n");
initialAdded = true;
}
}
if (lineNumber == lastLineNumber) {
if (!scottReport.getInitialSnapshots(lineNumber).isEmpty()) {
String blankLine = lineText.replaceFirst("[^\\s].*$", "");
for (Snapshot snapshot : scottReport.getInitialSnapshots(lineNumber)) {
sb.append(" ");
sb.append("| ");
sb.append(blankLine);
sb.append("// => ");
sb.append(snapshot.name + "=" + snapshot.value.trim());
sb.append("\n");
initialAdded = true;
}
}
}
if (initialAdded) {
String blankLine = lineText.replaceFirst("[^\\s].*$", "");
sb.append(" ");
sb.append("| ");
sb.append(blankLine);
sb.append("\n");
}
sb.append(String.format("%1$4s", lineNumber));
if (scottReport.getExceptionLineNumber() == lineNumber) {
sb.append("|* ");
} else {
sb.append("| ");
}
sb.append(lineText);
boolean isFirstCommentInThisLine = true;
for (Snapshot variableSnapshot : scottReport.getVariableSnapshots(lineNumber)) {
String[] variableSnapshotTextLines = getVariableSnapshotComment(variableSnapshot);
for (String comment : variableSnapshotTextLines) {
renderComment(sb, lineText, comment, isFirstCommentInThisLine);
isFirstCommentInThisLine = false;
}
}
if (scottReport.getExceptionLineNumber() == lineNumber) {
String[] exceptionMessageLines = getExceptionComment(scottReport);
for (String comment : exceptionMessageLines) {
renderComment(sb, lineText, comment, isFirstCommentInThisLine);
isFirstCommentInThisLine = false;
}
}
sb.append("\n");
firstLineWithBraketAppended = firstLineWithBraketAppended || lineText.contains("{");
}
return sb.toString();
}
private static String[] getExceptionComment(ScottReport scottReport) {
final String exceptionMessage;
if (scottReport.getExceptionMessage() != null) {
exceptionMessage = scottReport.getExceptionClassName() + ": " + scottReport.getExceptionMessage().trim();
} else {
exceptionMessage = scottReport.getExceptionClassName();
}
String[] exceptionMessageLines = exceptionMessage.split("\\n");
return exceptionMessageLines;
}
private static String[] getVariableSnapshotComment(Snapshot variableSnapshot) {
final String variableSnapshotText;
if (variableSnapshot.name != null) {
variableSnapshotText = variableSnapshot.name + "=" + variableSnapshot.value.trim();
} else {
variableSnapshotText = variableSnapshot.value.trim();
}
String[] variableSnapshotTextLines = variableSnapshotText.split("\\n");
return variableSnapshotTextLines;
}
private static void renderComment(StringBuilder sb, String lineText, String comment, boolean isFirstCommentInThisLine) {
if (!isFirstCommentInThisLine) {
addBlankLine(sb, lineText);
}
sb.append(" // ");
sb.append(comment);
isFirstCommentInThisLine = false;
}
private static void addBlankLine(StringBuilder sb, String lineText) {
sb.append("\n");
sb.append(" ");
sb.append("| ");
sb.append(lineText.replaceAll(".", " "));
}
}