diff --git a/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java b/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java index 7322bec98..71fcbbbe9 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java @@ -3,7 +3,7 @@ import java.util.Map; import java.util.Set; -import org.apache.commons.text.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONException; @@ -15,6 +15,10 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; +import j2html.tags.ContainerTag; +import j2html.tags.Tag; +import static j2html.TagCreator.*; + import se.bjurr.violations.lib.model.Violation; import se.bjurr.violations.lib.parsers.ValgrindParser; @@ -26,6 +30,7 @@ public class ValgrindAdapter extends AbstractViolationAdapter { private static final long serialVersionUID = -6117336551972081612L; private static final int NUMBERED_STACK_THRESHOLD = 2; + private static final int NO_LINE = -1; @Override ValgrindParser createParser() { @@ -40,7 +45,6 @@ Report convertToReport(final Set violations) { for (Violation violation: violations) { updateIssueBuilder(violation, issueBuilder); issueBuilder.setCategory("valgrind:" + violation.getReporter()); - issueBuilder.setMessage(violation.getMessage()); issueBuilder.setDescription(generateDescriptionHtml(violation)); report.add(issueBuilder.buildAndClean()); } @@ -50,145 +54,141 @@ Report convertToReport(final Set violations) { } private String generateDescriptionHtml(final Violation violation) { - final StringBuilder description = new StringBuilder(1024); - final Map specifics = violation.getSpecifics(); final JSONArray auxWhats = getAuxWhatsArray(specifics); - appendGeneralTable(description, violation.getSource(), violation.getGroup(), specifics.get("tid"), specifics.get("threadname"), auxWhats); - maybeAppendStackTraces(description, specifics.get("stacks"), violation.getMessage(), auxWhats); - maybeAppendSuppression(description, specifics.get("suppression")); - - return description.toString(); + return + j2html.tags.DomContentJoiner.join( + "", + false, + generateGeneralTableHtml(violation.getSource(), violation.getGroup(), specifics.get("tid"), specifics.get("threadname"), auxWhats), + maybeGenerateStackTracesHtml(specifics.get("stacks"), violation.getMessage(), auxWhats), + maybeGenerateSuppressionHtml(specifics.get("suppression")) + ).render(); } - private void appendGeneralTable(final StringBuilder html, final String executable, final String uniqueId, @CheckForNull final String threadId, @CheckForNull final String threadName, @CheckForNull final JSONArray auxWhats) { - html.append(""); - - maybeAppendTableRow(html, "Executable", executable); - maybeAppendTableRow(html, "Unique Id", uniqueId); - maybeAppendTableRow(html, "Thread Id", threadId); - maybeAppendTableRow(html, "Thread Name", threadName); + private Tag generateGeneralTableHtml(final String executable, final String uniqueId, @CheckForNull final String threadId, @CheckForNull final String threadName, @CheckForNull final JSONArray auxWhats) { + ContainerTag generalTable = + table( + attrs(".table.table-striped"), + maybeGenerateTableRowHtml("Executable", executable), + maybeGenerateTableRowHtml("Unique Id", uniqueId), + maybeGenerateTableRowHtml("Thread Id", threadId), + maybeGenerateTableRowHtml("Thread Name", threadName) + ); if (auxWhats != null && !auxWhats.isEmpty()) { for (int auxwhatIndex = 0; auxwhatIndex < auxWhats.length(); ++auxwhatIndex) { - maybeAppendTableRow(html, "Auxiliary", auxWhats.getString(auxwhatIndex)); + generalTable.with(maybeGenerateTableRowHtml("Auxiliary", auxWhats.getString(auxwhatIndex))); } } - html.append("
"); + return generalTable; } - private void maybeAppendStackTraces(final StringBuilder html, @CheckForNull final String stacksJson, final String message, @CheckForNull final JSONArray auxWhats) { - if (stacksJson == null || stacksJson.isEmpty()) { - return; - } + private Tag maybeGenerateStackTracesHtml(@CheckForNull final String stacksJson, final String message, @CheckForNull final JSONArray auxWhats) { + ContainerTag stackTraces = null; - final JSONArray stacks = new JSONArray(new JSONTokener(stacksJson)); + if (StringUtils.isNotBlank(stacksJson)) { + final JSONArray stacks = new JSONArray(new JSONTokener(stacksJson)); - if (!stacks.isEmpty()) { - appendStackTrace(html, "Primary Stack Trace", message, stacks.getJSONArray(0)); + if (!stacks.isEmpty()) { + stackTraces = div(); - for (int stackIndex = 1; stackIndex < stacks.length(); ++stackIndex) { - String msg = null; + stackTraces.with(generateStackTraceHtml("Primary Stack Trace", message, stacks.getJSONArray(0))); - if (auxWhats != null && auxWhats.length() >= stackIndex) { - msg = auxWhats.getString(stackIndex - 1); - } + for (int stackIndex = 1; stackIndex < stacks.length(); ++stackIndex) { + String msg = null; - String title = "Auxiliary Stack Trace"; + if (auxWhats != null && auxWhats.length() >= stackIndex) { + msg = auxWhats.getString(stackIndex - 1); + } - if (stacks.length() > NUMBERED_STACK_THRESHOLD) { - title = "Auxiliary Stack Trace #" + stackIndex; - } + String title = "Auxiliary Stack Trace"; - appendStackTrace(html, title, msg, stacks.getJSONArray(stackIndex)); + if (stacks.length() > NUMBERED_STACK_THRESHOLD) { + title += " #" + stackIndex; + } + + stackTraces.with(generateStackTraceHtml(title, msg, stacks.getJSONArray(stackIndex))); + } } } + + return stackTraces; } - private void appendStackTrace(final StringBuilder html, final String title, @CheckForNull final String message, final JSONArray frames) { - html - .append("
") - .append(title) - .append("
"); - - if (message != null && !message.isEmpty()) { - html - .append("

") - .append(message) - .append("

"); - } + private Tag generateStackTraceHtml(final String title, @CheckForNull final String message, final JSONArray frames) { + ContainerTag stackTraceContainer = + div( + br(), + h4(title), + iff(StringUtils.isNotBlank(message), p(message)) + ); for (int frameIndex = 0; frameIndex < frames.length(); ++frameIndex) { final JSONObject frame = frames.getJSONObject(frameIndex); if (frameIndex > 0) { - html.append("
"); + stackTraceContainer.with(br()); } - appendStackFrame(html, frame); + stackTraceContainer.with(generateStackFrameHtml(frame)); } + + return stackTraceContainer; } - private void appendStackFrame(final StringBuilder html, final JSONObject frame) { - html.append(""); - maybeAppendTableRow(html, "Object", frame.optString("obj")); - maybeAppendTableRow(html, "Function", frame.optString("fn")); - maybeAppendStackFrameFileTableRow(html, frame); - html.append("
"); + private Tag generateStackFrameHtml(final JSONObject frame) { + return + table( + attrs(".table.table-striped"), + maybeGenerateTableRowHtml("Object", frame.optString("obj")), + maybeGenerateTableRowHtml("Function", frame.optString("fn")), + maybeGenerateStackFrameFileTableRowHtml(frame) + ); } - private void maybeAppendSuppression(final StringBuilder html, @CheckForNull final String suppression) { - if (suppression != null && !suppression.isEmpty()) { - html - .append("

Suppression

")
-                    .append(StringEscapeUtils.escapeHtml4(suppression))
-                    .append("
"); - } + private Tag maybeGenerateSuppressionHtml(@CheckForNull final String suppression) { + return + iff( + StringUtils.isNotBlank(suppression), + div(br(), h4("Suppression"), table(attrs(".table.table-striped"), tr(td(pre(suppression))))) + ); } - private void maybeAppendTableRow(final StringBuilder html, final String name, @CheckForNull final String value) { - if (value != null && !value.isEmpty()) { - html - .append("") - .append(name) - .append("") - .append(value) - .append(""); - } + private Tag maybeGenerateTableRowHtml(final String name, @CheckForNull final String value) { + return iff(StringUtils.isNotBlank(value), tr(td(text(name), td(text(value))))); } - private void maybeAppendStackFrameFileTableRow(final StringBuilder html, final JSONObject frame) throws JSONException { - String dir = frame.optString("dir"); + private Tag maybeGenerateStackFrameFileTableRowHtml(final JSONObject frame) throws JSONException { + Tag row = null; final String file = frame.optString("file"); - final int line = frame.optInt("line", -1); - if (!file.isEmpty()) { - html.append("File"); + if (StringUtils.isNotBlank(file)) { + final String dir = frame.optString("dir"); + final int line = frame.optInt("line", NO_LINE); + final StringBuilder fileBuilder = new StringBuilder(256); - if (!dir.isEmpty()) { - html.append(dir).append('/'); + if (StringUtils.isNotBlank(dir)) { + fileBuilder.append(dir).append('/'); } - html.append(file); + fileBuilder.append(file); - if (line != -1) { - html.append(':').append(line); + if (line != NO_LINE) { + fileBuilder.append(':').append(line); } - html.append(""); + row = maybeGenerateTableRowHtml("File", fileBuilder.toString()); } + + return row; } @CheckForNull private JSONArray getAuxWhatsArray(final Map specifics) { final String auxWhatsJson = specifics.get("auxwhats"); - - if (auxWhatsJson != null && !auxWhatsJson.isEmpty()) { - return new JSONArray(new JSONTokener(auxWhatsJson)); - } - - return null; + return StringUtils.isNotBlank(auxWhatsJson) ? new JSONArray(new JSONTokener(auxWhatsJson)) : null; } } \ No newline at end of file diff --git a/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java b/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java index a10e3153c..a753839bd 100644 --- a/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java +++ b/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java @@ -7,6 +7,8 @@ import se.bjurr.violations.lib.model.Violation; +import static org.assertj.core.api.Assertions.*; + /** * Tests the class {@link ValgrindAdapter}. * @@ -53,8 +55,21 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti .hasMessage("Some type of error without a stack trace") .hasFileName(Violation.NO_FILE) .hasType("Not_A_Real_Error") - .hasLineStart(0) + .hasLineStart(Violation.NO_LINE) .hasSeverity(Severity.WARNING_HIGH); + + report.forEach( + issue -> { + final String description = issue.getDescription(); + + if (!issue.getFileName().equals(Violation.NO_FILE)) { + assertThat(description.contains("Primary Stack Trace")); + assertThat(description.contains("<insert_a_suppression_name_here>")); + } else { + assertThat(!description.contains("Primary Stack Trace")); + } + } + ); } @Override diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml b/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml index 9b2f57e94..fc77bb098 100644 --- a/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml +++ b/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml @@ -253,7 +253,6 @@ 0x40550FD /lib/ld-musl-x86_64.so.1 __syscall_cp_c - /home/buildozer/aports/main/musl/src/1.2.4/src/thread pthread_cancel.c 33 @@ -383,7 +382,6 @@ make_spaghetti(int) /workspace/awful project spaghetti.cpp - 6 0x1091F2 @@ -497,4 +495,3 @@ -