1#!/usr/bin/env python
2# Copyright (c) 2018 Parsa Amini
3#
4# Distributed under the Boost Software License, Version 1.0. (See accompanying
5# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7# ## Synopsis
8# ```
9# usage: inspect_to_junit.py [-h] [source] [destination]
10#
11# Generate JUnit XML report from inspect output
12#
13# positional arguments:
14#   source       File path to read inspect output from
15#   destination  File path to write JUnit XML report to
16#
17# optional arguments:
18#   -h, --help   show this help message and exit
19# ```
20
21import argparse
22import re
23import sys
24from xml.dom import minidom
25from collections import namedtuple
26
27error_item = namedtuple('error_item', 'filename message')
28
29
30def parse_inspect8_log(fh):
31    line_pattern = re.compile('(.+):\ (\*.+\*.+)')
32    split_pattern = re.compile(',\ (?=\*)')
33    stipper_pattern = re.compile('<\/?\w+[^>]*>')
34
35    errors = []
36
37    for line in fh:
38        m = line_pattern.match(line)
39        if m:
40            for message in split_pattern.split(m.group(2)):
41                error = error_item(filename=m.group(1),
42                                   message=stipper_pattern.sub('', message))
43                errors.append(error)
44
45    return errors
46
47
48def convert(inspect8_log_fh):
49    errors = parse_inspect8_log(inspect8_log_fh)
50
51    doc = minidom.Document()
52    suite = doc.createElement('testsuite')
53    doc.appendChild(suite)
54    suite.setAttribute('name', 'inspect')
55    suite.setAttribute('errors', str(len(errors)))
56    suite.setAttribute('failures', '0')
57    suite.setAttribute('tests', str(len(errors)))
58
59    if len(errors) == 0:
60        case = doc.createElement('testcase')
61        case.setAttribute('name', 'inspect')
62        case.setAttribute('time', '')
63        suite.appendChild(case)
64
65    for error in errors:
66        case = doc.createElement('testcase')
67        case.setAttribute('name', error.filename)
68        case.setAttribute('time', '')
69        suite.appendChild(case)
70
71        failure = doc.createElement('failure')
72        case.appendChild(failure)
73
74        failure.setAttribute('file', error.filename)
75        failure.setAttribute('message', error.message)
76        message = doc.createTextNode(error.message)
77        failure.appendChild(message)
78
79    return doc
80
81
82def main():
83    parser = argparse.ArgumentParser(
84        description='Generate JUnit XML report from inspect html output')
85    parser.add_argument('source',
86                        type=argparse.FileType('r'),
87                        nargs='?',
88                        default=sys.stdin,
89                        help='File path to read inspect html output from')
90    parser.add_argument('destination',
91                        type=argparse.FileType('w'),
92                        nargs='?',
93                        default=sys.stdout,
94                        help='File path to write JUnit XML report to')
95
96    args = parser.parse_args()
97    report = convert(args.source)
98    report.writexml(args.destination,
99                    addindent='    ', newl='\n', encoding='utf-8')
100
101
102if __name__ == '__main__':
103    main()
104