[gssdp/wip/phako/ci] ci: Add reports to scan-build




commit 15a168348812d701db81f0042655c1ab21713c60
Author: Jens Georg <mail jensge org>
Date:   Sat May 29 15:52:31 2021 +0200

    ci: Add reports to scan-build

 .gitlab-ci.yml                         |   7 +-
 .gitlab-ci/scanbuild-plist-to-junit.py | 131 +++++++++++++++++++++++++++++++++
 .gitlab-ci/scanbuild-wrapper.sh        |   2 +
 3 files changed, 139 insertions(+), 1 deletion(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f11b77b..3fd4f4a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -170,11 +170,16 @@ static-scan:
     - build-fedora-container@x86_64
   script:
     - meson --buildtype=debug _scan_build
+    - export SCANBUILD="$PWD/.gitlab-ci/scanbuild-wrapper.sh"
     - ninja -C _scan_build scan-build
   artifacts:
     paths:
       - _scan_build/meson-logs
-  allow_failure: true
+  after_script:
+    - .gitlab-ci/scanbuild-plist-to-junit.py _scan_build/meson-logs/scanbuild/ > 
_scan_build/junit-scan-build.xml
+  artifacts:
+    reports:
+      junit: "_scan_build/junit-scan-build.xml"
 
 pages:
   extends:
diff --git a/.gitlab-ci/scanbuild-plist-to-junit.py b/.gitlab-ci/scanbuild-plist-to-junit.py
new file mode 100755
index 0000000..987148a
--- /dev/null
+++ b/.gitlab-ci/scanbuild-plist-to-junit.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: MIT
+#
+# Usage:
+#   $ scanbuild-plist-to-junit.py /path/to/meson-logs/scanbuild/ > junit-report.xml
+#
+# Converts the plist output from scan-build into a JUnit-compatible XML.
+#
+# For use with meson, use a wrapper script with this content:
+#   scan-build -v --status-bugs -plist-html "$@"
+# then build with
+#  SCANBUILD="/abs/path/to/wrapper.sh" ninja -C builddir scan-build
+#
+# For file context, $PWD has to be the root source directory.
+#
+# Note that the XML format is tailored towards being useful in the gitlab
+# CI, the JUnit format supports more features.
+#
+# This file is formatted with Python Black
+
+import argparse
+import plistlib
+import re
+import sys
+from pathlib import Path
+
+errors = []
+
+
+class Error(object):
+    pass
+
+
+parser = argparse.ArgumentParser(
+    description="This tool convers scan-build's plist format to JUnit XML"
+)
+parser.add_argument(
+    "directory", help="Path to a scan-build output directory", type=Path
+)
+args = parser.parse_args()
+
+if not args.directory.exists():
+    print(f"Invalid directory: {args.directory}", file=sys.stderr)
+    sys.exit(1)
+
+# Meson places scan-build runs into a timestamped directory. To make it
+# easier to invoke this script, we just glob everything on the assumption
+# that there's only one scanbuild/$timestamp/ directory anyway.
+for file in Path(args.directory).glob("**/*.plist"):
+    with open(file, "rb") as fd:
+        plist = plistlib.load(fd, fmt=plistlib.FMT_XML)
+        try:
+            sources = plist["files"]
+            for elem in plist["diagnostics"]:
+                e = Error()
+                e.type = elem["type"]  # Human-readable error type
+                e.description = elem["description"]  # Longer description
+                e.func = elem["issue_context"]  # function name
+                e.lineno = elem["location"]["line"]
+                filename = sources[elem["location"]["file"]]
+                # Remove the ../../../ prefix from the file
+                e.file = re.sub(r"^(\.\./)*", "", filename)
+                errors.append(e)
+        except KeyError:
+            print(
+                "Failed to access plist content, incompatible format?", file=sys.stderr
+            )
+            sys.exit(1)
+
+
+# Add a few lines of context for each error that we can print in the xml
+# output. Note that e.lineno is 1-indexed.
+#
+# If one of the files fail, we stop doing this, we're probably in the wrong
+# directory.
+try:
+    current_file = None
+    lines = []
+    for e in sorted(errors, key=lambda x: x.file):
+        if current_file != e.file:
+            current_file = e.file
+            lines = open(current_file).readlines()
+
+        # e.lineno is 1-indexed, lineno is our 0-indexed line number
+        lineno = e.lineno - 1
+        start = max(0, lineno - 4)
+        end = min(len(lines), lineno + 5)  # end is exclusive
+        e.context = [
+            f"{'>' if line == e.lineno else ' '} {line}: {content}"
+            for line, content in zip(range(start + 1, end), lines[start:end])
+        ]
+except FileNotFoundError:
+    pass
+
+print('<?xml version="1.0" encoding="utf-8"?>')
+print("<testsuites>")
+if errors:
+    suites = sorted(set([s.type for s in errors]))
+    # Use a counter to ensure test names are unique, otherwise the CI
+    # display ignores duplicates.
+    counter = 0
+    for suite in suites:
+        errs = [e for e in errors if e.type == suite]
+        # Note: the grouping by suites doesn't actually do anything in gitlab. Oh well
+        print(f'<testsuite name="{suite}" failures="{len(errs)}" tests="{len(errs)}">')
+        for error in errs:
+            print(
+                f"""\
+<testcase name="{counter}. {error.type} - {error.file}:{error.lineno}" classname="{error.file}">
+<failure message="{error.description}">
+<![CDATA[
+In function {error.func}(),
+{error.description}
+
+{error.file}:{error.lineno}
+---
+{"".join(error.context)}
+]]>
+</failure>
+</testcase>"""
+            )
+            counter += 1
+        print("</testsuite>")
+else:
+    # In case of success, add one test case so that registers in the UI
+    # properly
+    print('<testsuite name="scanbuild" failures="0" tests="1">')
+    print('<testcase name="scanbuild" classname="scanbuild"/>')
+    print("</testsuite>")
+print("</testsuites>")
diff --git a/.gitlab-ci/scanbuild-wrapper.sh b/.gitlab-ci/scanbuild-wrapper.sh
new file mode 100755
index 0000000..58243b0
--- /dev/null
+++ b/.gitlab-ci/scanbuild-wrapper.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+scan-build -v --status-bugs -plist-html "$@"


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]