1#!/usr/bin/env python3 2 3import argparse 4import json 5import sys 6import unicodedata 7import xml.etree.ElementTree as ET 8from datetime import datetime 9 10 11aparser = argparse.ArgumentParser( 12 description='Convert Meson test log into JUnit report') 13aparser.add_argument('--project-name', metavar='NAME', 14 help='The project name', 15 default='unknown') 16aparser.add_argument('--job-id', metavar='ID', 17 help='The job ID for the report', 18 default='Unknown') 19aparser.add_argument('--branch', metavar='NAME', 20 help='Branch of the project being tested', 21 default='master') 22aparser.add_argument('--output', metavar='FILE', 23 help='The output file, stdout by default', 24 type=argparse.FileType('w', encoding='UTF-8'), 25 default=sys.stdout) 26aparser.add_argument('infile', metavar='FILE', 27 help='The input testlog.json, stdin by default', 28 type=argparse.FileType('r', encoding='UTF-8'), 29 default=sys.stdin) 30args = aparser.parse_args() 31 32outfile = args.output 33 34testsuites = ET.Element('testsuites') 35testsuites.set('id', '{}/{}'.format(args.job_id, args.branch)) 36testsuites.set('package', args.project_name) 37testsuites.set('timestamp', datetime.utcnow().isoformat(timespec='minutes')) 38 39testsuite = ET.SubElement(testsuites, 'testsuite') 40testsuite.set('name', args.project_name) 41 42successes = 0 43failures = 0 44skips = 0 45 46 47def escape_control_chars(text): 48 return "".join(c if unicodedata.category(c)[0] != "C" else 49 "<{:02x}>".format(ord(c)) for c in text) 50 51 52for line in args.infile: 53 unit = json.loads(line) 54 55 testcase = ET.SubElement(testsuite, 'testcase') 56 testcase.set('classname', '{}/{}'.format(args.project_name, unit['name'])) 57 testcase.set('name', unit['name']) 58 testcase.set('time', str(unit['duration'])) 59 60 stdout = escape_control_chars(unit.get('stdout', '')) 61 stderr = escape_control_chars(unit.get('stderr', '')) 62 if stdout: 63 ET.SubElement(testcase, 'system-out').text = stdout 64 if stderr: 65 ET.SubElement(testcase, 'system-out').text = stderr 66 67 result = unit['result'].lower() 68 if result == 'skip': 69 skips += 1 70 ET.SubElement(testcase, 'skipped') 71 elif result == 'fail': 72 failures += 1 73 failure = ET.SubElement(testcase, 'failure') 74 failure.set('message', "{} failed".format(unit['name'])) 75 failure.text = "### stdout\n{}\n### stderr\n{}\n".format(stdout, 76 stderr) 77 else: 78 successes += 1 79 assert unit['returncode'] == 0 80 81testsuite.set('tests', str(successes + failures + skips)) 82testsuite.set('skipped', str(skips)) 83testsuite.set('errors', str(failures)) 84testsuite.set('failures', str(failures)) 85 86print('{}: {} pass, {} fail, {} skip'.format(args.project_name, 87 successes, 88 failures, 89 skips)) 90 91output = ET.tostring(testsuites, encoding='unicode') 92outfile.write(output) 93