Files
dokku-mysql/tests/shellcheck-to-junit
2021-09-12 22:18:00 -04:00

206 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
import collections
import datetime
import re
import socket
import sys
from xml.etree import ElementTree
def CDATA(text=None):
element = ElementTree.Element('![CDATA[')
element.text = text
return element
def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs):
if elem.tag == '![CDATA[':
write("\n<{}{}]]>\n".format(elem.tag, elem.text))
if elem.tail:
write(ElementTree._escape_cdata(elem.tail))
else:
return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs)
ElementTree._original_serialize_xml = ElementTree._serialize_xml
ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml
def read_in():
lines = sys.stdin.readlines()
for i in range(len(lines)):
lines[i] = lines[i].rstrip()
return lines
def process_lines(lines):
files = {}
current_file = None
previous_line = None
line_no = None
new_issues = []
code = None
RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)")
RE_VIOLATION_NEW = re.compile(r"\^[-]+\^ (SC[\w]+): (.*)")
for line in lines:
# start a new block
if line == '':
if current_file is not None:
file_data = files.get(current_file, {})
files[current_file] = file_data
issue_data = file_data.get(line_no, {})
issue_data['code'] = code
files[current_file][line_no] = issue_data
issues = issue_data.get('issues', [])
issues.extend(new_issues)
issue_data['issues'] = issues
files[current_file][line_no] = issue_data
code = None
current_file = None
line_no = None
elif line.startswith('In ./') and not previous_line:
current_file = line.split(' ')[1].replace('./', '')
line_no = line.split(' ')[3]
new_issues = []
code = None
elif code is None and len(new_issues) == 0:
code = line
else:
match = RE_VIOLATION.match(line.strip())
if not match:
match = RE_VIOLATION_NEW.match(line.strip())
if not match:
if 'https://www.shellcheck.net/wiki/SC' in line:
continue
if 'For more information:' == line:
continue
print('Error: Issue parsing line "{0}"'.format(line.strip()))
else:
new_issues.append({
'shellcheck_id': match.group(1),
'message': match.group(2),
'original_message': line
})
previous_line = line
return files
def output_junit(files, args):
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
failures = 0
for file, data in files.items():
for line, issue_data in data.items():
code = issue_data.get('code')
for issue in issue_data.get('issues', []):
failures += 1
tests = 0
if args.files:
with open(args.files, 'r') as f:
tests = len(f.readlines())
root = ElementTree.Element("testsuite",
name="shellcheck",
tests="{0}".format(tests),
failures="{0}".format(failures),
errors="0",
skipped="0",
timestamp=timestamp,
time="0",
hostname=socket.gethostname())
properties = ElementTree.SubElement(root, "properties")
if args.exclude:
ElementTree.SubElement(properties,
"property",
name="exclude",
value=args.exclude)
if args.files:
with open(args.files, 'r') as f:
lines = f.readlines()
for i in range(len(lines)):
file = lines[i].rstrip().replace('./', '')
data = files.get(file, None)
if data:
for line, issue_data in data.items():
code = issue_data.get('code')
for issue in issue_data.get('issues', []):
testcase = ElementTree.SubElement(root,
"testcase",
classname=file,
name=file,
time="0")
shellcheck_id = issue.get('shellcheck_id')
message = 'line {0}: {1}'.format(
line, issue.get('message'))
original_message = issue.get('original_message')
e = ElementTree.Element("failure",
type=shellcheck_id,
message=message)
cdata = CDATA("\n".join([code, original_message]))
e.append(cdata)
testcase.append(e)
ElementTree.SubElement(root,
"testcase",
classname=file,
name=file,
time="0")
ElementTree.SubElement(root, "system-out")
ElementTree.SubElement(root, "system-err")
content = ElementTree.tostring(root, encoding='UTF-8', method='xml')
if args.output:
with open(args.output, 'w') as f:
try:
f.write(content)
except TypeError:
f.write(content.decode("utf-8"))
def main():
parser = argparse.ArgumentParser(
description='Process shellcheck output to junit.')
parser.add_argument('--output',
dest='output',
action='store',
default=None,
help='file to write shellcheck output')
parser.add_argument('--files',
dest='files',
action='store',
default=None,
help='a file containing a list of all files processed by shellcheck')
parser.add_argument('--exclude',
dest='exclude',
action='store',
default=None,
help='a comma-separated list of rules being excluded by shellcheck')
args = parser.parse_args()
lines = read_in()
files = process_lines(lines)
files = collections.OrderedDict(sorted(files.items()))
output_junit(files, args)
for line in lines:
print(line)
if __name__ == '__main__':
main()