Wetterstation v2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
4.6 KiB

5 years ago
#!/usr/bin/env python3
# https://github.com/PSPDFKit-labs/clang-tidy-to-junit/
import sys
import collections
import re
import logging
import itertools
from xml.sax.saxutils import escape
# Create a `ErrorDescription` tuple with all the information we want to keep.
ErrorDescription = collections.namedtuple(
'ErrorDescription', 'file line column error error_identifier description')
class ClangTidyConverter:
# All the errors encountered.
errors = []
# Parses the error.
# Group 1: file path
# Group 2: line
# Group 3: column
# Group 4: error message
# Group 5: error identifier
error_regex = re.compile(
r"^([\w\/\.\-\ ]+):(\d+):(\d+): (.+) (\[[\w\-,\.]+\])$")
# This identifies the main error line (it has a [the-warning-type] at the end)
# We only create a new error when we encounter one of those.
main_error_identifier = re.compile(r'\[[\w\-,\.]+\]$')
def __init__(self, basename):
self.basename = basename
def print_junit_file(self, output_file):
# Write the header.
output_file.write("""<?xml version="1.0" encoding="UTF-8" ?>
<testsuites id="1" name="Clang-Tidy" tests="{error_count}" errors="{error_count}" failures="0" time="0">""".format(error_count=len(self.errors)))
sorted_errors = sorted(self.errors, key=lambda x: x.file)
# Iterate through the errors, grouped by file.
for file, errorIterator in itertools.groupby(sorted_errors, key=lambda x: x.file):
errors = list(errorIterator)
error_count = len(errors)
# Each file gets a test-suite
output_file.write("""\n <testsuite errors="{error_count}" name="{file}" tests="{error_count}" failures="0" time="0">\n"""
.format(error_count=error_count, file=file))
for error in errors:
# Write each error as a test case.
output_file.write("""
<testcase id="{id}" name="{id}" time="0">
<failure message="{message}">
{htmldata}
</failure>
</testcase>""".format(id="[{}/{}] {}".format(error.line, error.column, error.error_identifier), message=escape(error.error),
htmldata=escape(error.description)))
output_file.write("\n </testsuite>\n")
output_file.write("</testsuites>\n")
def process_error(self, error_array):
if len(error_array) == 0:
return
result = self.error_regex.match(error_array[0])
if result is None:
logging.warning(
'Could not match error_array to regex: %s', error_array)
return
# We remove the `basename` from the `file_path` to make prettier filenames in the JUnit file.
file_path = result.group(1).replace(self.basename, "")
error = ErrorDescription(file_path, int(result.group(2)), int(
result.group(3)), result.group(4), result.group(5), "\n".join(error_array[1:]))
self.errors.append(error)
def convert(self, input_file, output_file):
# Collect all lines related to one error.
current_error = []
for line in input_file:
# If the line starts with a `/`, it is a line about a file.
if line[0] == '/':
# Look if it is the start of a error
if self.main_error_identifier.search(line, re.M):
# If so, process any `current_error` we might have
self.process_error(current_error)
# Initialize `current_error` with the first line of the error.
current_error = [line]
else:
# Otherwise, append the line to the error.
current_error.append(line)
elif len(current_error) > 0:
# If the line didn't start with a `/` and we have a `current_error`, we simply append
# the line as additional information.
current_error.append(line)
else:
pass
# If we still have any current_error after we read all the lines,
# process it.
if len(current_error) > 0:
self.process_error(current_error)
# Print the junit file.
self.print_junit_file(output_file)
if __name__ == "__main__":
if len(sys.argv) < 2:
logging.error("Usage: %s base-filename-path", sys.argv[0])
logging.error(
" base-filename-path: Removed from the filenames to make nicer paths.")
sys.exit(1)
converter = ClangTidyConverter(sys.argv[1])
converter.convert(sys.stdin, sys.stdout)