1#!/usr/bin/env python3 2 3# Turns a Meson testlog.json file into a JUnit XML report 4# 5# Copyright 2019 GNOME Foundation 6# 7# SPDX-License-Identifier: LGPL-2.1-or-later 8# 9# Original author: Emmanuele Bassi 10 11import argparse 12import datetime 13import json 14import os 15import sys 16import xml.etree.ElementTree as ET 17 18aparser = argparse.ArgumentParser(description='Turns a Meson test log into a JUnit report') 19aparser.add_argument('--project-name', metavar='NAME', 20 help='The project name', 21 default='unknown') 22aparser.add_argument('--job-id', metavar='ID', 23 help='The job ID for the report', 24 default='Unknown') 25aparser.add_argument('--branch', metavar='NAME', 26 help='Branch of the project being tested', 27 default='master') 28aparser.add_argument('--output', metavar='FILE', 29 help='The output file, stdout by default', 30 type=argparse.FileType('w', encoding='UTF-8'), 31 default=sys.stdout) 32aparser.add_argument('infile', metavar='FILE', 33 help='The input testlog.json, stdin by default', 34 type=argparse.FileType('r', encoding='UTF-8'), 35 default=sys.stdin) 36 37args = aparser.parse_args() 38 39outfile = args.output 40 41testsuites = ET.Element('testsuites') 42testsuites.set('id', '{}/{}'.format(args.job_id, args.branch)) 43testsuites.set('package', args.project_name) 44testsuites.set('timestamp', datetime.datetime.utcnow().isoformat()) 45 46suites = {} 47for line in args.infile: 48 data = json.loads(line) 49 (full_suite, unit_name) = data['name'].split(' / ') 50 (project_name, suite_name) = full_suite.split(':') 51 52 duration = data['duration'] 53 return_code = data['returncode'] 54 log = data['stdout'] 55 log_stderr = data.get('stderr', '') 56 57 unit = { 58 'suite': suite_name, 59 'name': unit_name, 60 'duration': duration, 61 'returncode': return_code, 62 'stdout': log, 63 'stderr': log_stderr, 64 } 65 66 units = suites.setdefault(suite_name, []) 67 units.append(unit) 68 69for name, units in suites.items(): 70 print('Processing suite {} (units: {})'.format(name, len(units))) 71 72 def if_failed(unit): 73 if unit['returncode'] != 0: 74 return True 75 return False 76 77 def if_succeded(unit): 78 if unit['returncode'] == 0: 79 return True 80 return False 81 82 successes = list(filter(if_succeded, units)) 83 failures = list(filter(if_failed, units)) 84 print(' - {}: {} pass, {} fail'.format(name, len(successes), len(failures))) 85 86 testsuite = ET.SubElement(testsuites, 'testsuite') 87 testsuite.set('name', '{}/{}'.format(args.project_name, name)) 88 testsuite.set('tests', str(len(units))) 89 testsuite.set('errors', str(len(failures))) 90 testsuite.set('failures', str(len(failures))) 91 92 for unit in successes: 93 testcase = ET.SubElement(testsuite, 'testcase') 94 testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) 95 testcase.set('name', unit['name']) 96 testcase.set('time', str(unit['duration'])) 97 98 for unit in failures: 99 testcase = ET.SubElement(testsuite, 'testcase') 100 testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) 101 testcase.set('name', unit['name']) 102 testcase.set('time', str(unit['duration'])) 103 104 failure = ET.SubElement(testcase, 'failure') 105 failure.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) 106 failure.set('name', unit['name']) 107 failure.set('type', 'error') 108 failure.text = unit['stdout'] + '\n' + unit['stderr'] 109 110output = ET.tostring(testsuites, encoding='unicode') 111outfile.write(output) 112