summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2021-05-29 15:52:31 +0200
committerJens Georg <mail@jensge.org>2021-05-29 15:56:39 +0200
commit15a168348812d701db81f0042655c1ab21713c60 (patch)
tree9a0cd0bc01db825f6ff381705c2edabb4aaa0e18
parentcf1a570c474951e4a8f1ceeb5a343b9da50426e2 (diff)
downloadgssdp-wip/phako/ci.tar.gz
ci: Add reports to scan-buildwip/phako/ci
-rw-r--r--.gitlab-ci.yml7
-rwxr-xr-x.gitlab-ci/scanbuild-plist-to-junit.py131
-rwxr-xr-x.gitlab-ci/scanbuild-wrapper.sh2
3 files changed, 139 insertions, 1 deletions
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 "$@"