mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 04:30:06 +01:00
gitlab CI: add a JUnit XML report for scan-build
Use a scan-build wrapper to generate plist files, then parse those into a JUnit xml format. This makes the errors appear on the main MR page as opposed to being hidden in the artifacts somewhere. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
b1c9667aca
commit
5dc000323d
4 changed files with 145 additions and 12 deletions
|
|
@ -724,16 +724,16 @@ scan-build@fedora:34:
|
|||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
NINJA_ARGS: scan-build
|
||||
NINJA_ARGS: ''
|
||||
MESON_TEST_ARGS: ''
|
||||
before_script:
|
||||
- dnf install -y clang-analyzer findutils
|
||||
- dnf install -y clang-analyzer
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- test ! -d "$MESON_BUILDDIR"/meson-logs/scanbuild && exit 0
|
||||
- test $(find "$MESON_BUILDDIR"/meson-logs/scanbuild -maxdepth 0 ! -empty -exec echo "not empty" \; | wc -l) -eq 0 && exit 0
|
||||
- echo "Check scan-build results"
|
||||
- /bin/false
|
||||
- export SCANBUILD="$PWD/.gitlab-ci/scanbuild-wrapper.sh"
|
||||
- ninja -C "$MESON_BUILDDIR" scan-build
|
||||
after_script:
|
||||
- .gitlab-ci/scanbuild-plist-to-junit.py "$MESON_BUILDDIR"/meson-logs/scanbuild/ > "$MESON_BUILDDIR"/junit-scan-build.xml
|
||||
|
||||
# Below jobs are build option combinations. We only
|
||||
# run them on one image, they shouldn't fail on one distro
|
||||
|
|
|
|||
|
|
@ -454,16 +454,16 @@ scan-build@{{distro.name}}:{{version}}:
|
|||
extends:
|
||||
- .{{distro.name}}-build@template
|
||||
variables:
|
||||
NINJA_ARGS: scan-build
|
||||
NINJA_ARGS: ''
|
||||
MESON_TEST_ARGS: ''
|
||||
before_script:
|
||||
- dnf install -y clang-analyzer findutils
|
||||
- dnf install -y clang-analyzer
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- test ! -d "$MESON_BUILDDIR"/meson-logs/scanbuild && exit 0
|
||||
- test $(find "$MESON_BUILDDIR"/meson-logs/scanbuild -maxdepth 0 ! -empty -exec echo "not empty" \; | wc -l) -eq 0 && exit 0
|
||||
- echo "Check scan-build results"
|
||||
- /bin/false
|
||||
- export SCANBUILD="$PWD/.gitlab-ci/scanbuild-wrapper.sh"
|
||||
- ninja -C "$MESON_BUILDDIR" scan-build
|
||||
after_script:
|
||||
- .gitlab-ci/scanbuild-plist-to-junit.py "$MESON_BUILDDIR"/meson-logs/scanbuild/ > "$MESON_BUILDDIR"/junit-scan-build.xml
|
||||
|
||||
# Below jobs are build option combinations. We only
|
||||
# run them on one image, they shouldn't fail on one distro
|
||||
|
|
|
|||
131
.gitlab-ci/scanbuild-plist-to-junit.py
Executable file
131
.gitlab-ci/scanbuild-plist-to-junit.py
Executable file
|
|
@ -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>")
|
||||
2
.gitlab-ci/scanbuild-wrapper.sh
Executable file
2
.gitlab-ci/scanbuild-wrapper.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
scan-build -v --status-bugs -plist-html "$@"
|
||||
Loading…
Add table
Reference in a new issue