CI: Publish test results from Meson
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
diff --git a/.azure-pipelines/steps/meson.yml b/.azure-pipelines/steps/meson.yml
index 0854414..e9ec6dd 100644
--- a/.azure-pipelines/steps/meson.yml
+++ b/.azure-pipelines/steps/meson.yml
@@ -31,6 +31,15 @@ steps:
for file in "$(pwd)"/meson-logs/* ; do
echo "##vso[task.uploadfile]${file}"
done
- displayName: 'Save Results (Meson)'
+ for file in "$(pwd)"/meson-logs/*.json ; do
+ python3 ../scripts/meson-junit-report.py --project-name=xkbcommon \
+ --job-id='$(Build.BuildId)' --branch='$(Build.SourceBranch)' \
+ --output="${file}-junit.xml" "${file}"
+ done
+ displayName: 'Process Results (Meson)'
workingDirectory: ${{ parameters.workdir }}
condition: always()
+ - task: PublishTestResults@2
+ inputs:
+ testResultsFiles: '**/*-junit.xml'
+ failTaskOnFailedTests: true
diff --git a/scripts/meson-junit-report.py b/scripts/meson-junit-report.py
new file mode 100644
index 0000000..a6cbb0b
--- /dev/null
+++ b/scripts/meson-junit-report.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import sys
+import unicodedata
+import xml.etree.ElementTree as ET
+from datetime import datetime
+
+
+aparser = argparse.ArgumentParser(
+ description='Convert Meson test log into JUnit report')
+aparser.add_argument('--project-name', metavar='NAME',
+ help='The project name',
+ default='unknown')
+aparser.add_argument('--job-id', metavar='ID',
+ help='The job ID for the report',
+ default='Unknown')
+aparser.add_argument('--branch', metavar='NAME',
+ help='Branch of the project being tested',
+ default='master')
+aparser.add_argument('--output', metavar='FILE',
+ help='The output file, stdout by default',
+ type=argparse.FileType('w', encoding='UTF-8'),
+ default=sys.stdout)
+aparser.add_argument('infile', metavar='FILE',
+ help='The input testlog.json, stdin by default',
+ type=argparse.FileType('r', encoding='UTF-8'),
+ default=sys.stdin)
+args = aparser.parse_args()
+
+outfile = args.output
+
+testsuites = ET.Element('testsuites')
+testsuites.set('id', '{}/{}'.format(args.job_id, args.branch))
+testsuites.set('package', args.project_name)
+testsuites.set('timestamp', datetime.utcnow().isoformat(timespec='minutes'))
+
+testsuite = ET.SubElement(testsuites, 'testsuite')
+testsuite.set('name', args.project_name)
+
+successes = 0
+failures = 0
+skips = 0
+
+
+def escape_control_chars(text):
+ return "".join(c if unicodedata.category(c)[0] != "C" else
+ "<{:02x}>".format(ord(c)) for c in text)
+
+
+for line in args.infile:
+ unit = json.loads(line)
+
+ testcase = ET.SubElement(testsuite, 'testcase')
+ testcase.set('classname', '{}/{}'.format(args.project_name, unit['name']))
+ testcase.set('name', unit['name'])
+ testcase.set('time', str(unit['duration']))
+
+ stdout = escape_control_chars(unit.get('stdout', ''))
+ stderr = escape_control_chars(unit.get('stderr', ''))
+ if stdout:
+ ET.SubElement(testcase, 'system-out').text = stdout
+ if stderr:
+ ET.SubElement(testcase, 'system-out').text = stderr
+
+ result = unit['result'].lower()
+ if result == 'skip':
+ skips += 1
+ ET.SubElement(testcase, 'skipped')
+ elif result == 'fail':
+ failures += 1
+ failure = ET.SubElement(testcase, 'failure')
+ failure.set('message', "{} failed".format(unit['name']))
+ failure.text = "### stdout\n{}\n### stderr\n{}\n".format(stdout,
+ stderr)
+ else:
+ successes += 1
+ assert unit['returncode'] == 0
+
+testsuite.set('tests', str(successes + failures + skips))
+testsuite.set('skipped', str(skips))
+testsuite.set('errors', str(failures))
+testsuite.set('failures', str(failures))
+
+print('{}: {} pass, {} fail, {} skip'.format(args.project_name,
+ successes,
+ failures,
+ skips))
+
+output = ET.tostring(testsuites, encoding='unicode')
+outfile.write(output)