blob: 476fa0b823c4a2ce8ad4031067225389f25b2f3e [file] [log] [blame]
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +02001# osmo_gsm_tester: report: directory of binaries to be tested
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Pau Espin Pedrol <pespin@sysmocom.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +02009# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +010020# junit xml format: https://llg.cubic.org/docs/junit/
21
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020022import math
Pau Espin Pedrol3f088da2020-03-02 10:44:09 +010023import sys
24import re
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020025from datetime import datetime
26import xml.etree.ElementTree as et
Pau Espin Pedrol5bbdab82020-02-24 18:19:10 +010027from xml.sax.saxutils import escape
Holger Hans Peter Freytherd03acdf2018-09-23 18:06:48 +010028from . import test
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020029
Pau Espin Pedrol3f088da2020-03-02 10:44:09 +010030invalid_xml_char_ranges = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
31 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
32if sys.maxunicode >= 0x10000: # not narrow build
33 invalid_xml_char_ranges.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
34 (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
35 (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
36 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
37 (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
38 (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
39 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
40 (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)])
41invalid_xml_char_ranges_str = ['%s-%s' % (chr(low), chr(high))
42 for (low, high) in invalid_xml_char_ranges]
43invalid_xml_char_ranges_regex = re.compile('[%s]' % ''.join(invalid_xml_char_ranges_str))
Neels Hofmeyr16c8be42020-12-03 22:45:53 +010044ansi_color_re = re.compile('\033[0-9;]{1,4}m')
Pau Espin Pedrol3f088da2020-03-02 10:44:09 +010045
46def escape_xml_invalid_characters(str):
47 replacement_char = '\uFFFD' # Unicode replacement character
48 return invalid_xml_char_ranges_regex.sub(replacement_char, escape(str))
49
Neels Hofmeyr16c8be42020-12-03 22:45:53 +010050def strip_ansi_colors(text):
51 return ''.join(ansi_color_re.split(text))
52
Pau Espin Pedrolef919c02020-06-05 13:19:02 +020053def hash_info_to_junit(testsuite, hash_info):
54 properties = et.SubElement(testsuite, 'properties')
55 for key, val in hash_info.items():
56 prop = et.SubElement(properties, 'property')
57 prop.set('name', 'ref:' + key)
58 prop.set('value', val)
59
Pau Espin Pedrole3d1b612020-06-15 14:27:50 +020060def dict_to_junit(parent, d):
61 for key, val in d.items():
62 if isinstance(val, dict):
63 node = et.SubElement(parent, 'kpi_node')
64 node.set('name', key)
65 dict_to_junit(node, val)
66 continue
67 if isinstance(val, (tuple, list)):
68 node = et.SubElement(parent, 'kpi_node')
69 node.set('name', key)
70 list_to_junit(node, val)
71 continue
72 # scalar:
73 node = et.SubElement(parent, 'property')
74 node.set('name', key)
75 node.set('value', str(val))
76
77def list_to_junit(parent, li):
78 for i in range(len(li)):
79 if isinstance(li[i], dict):
80 node = et.SubElement(parent, 'kpi_node')
81 node.set('name', str(i))
82 dict_to_junit(node, li[i])
83 continue
84 if isinstance(val, (tuple, list)):
85 node = et.SubElement(parent, 'kpi_node')
86 node.set('name', str(i))
87 list_to_junit(node, li[i])
88 continue
89 # scalar:
90 node = et.SubElement(parent, 'property')
91 node.set('name', str(i))
92 node.set('value', str(li[i]))
93
94def kpis_to_junit(parent, kpis):
95 if not kpis:
96 return
97 assert isinstance(kpis, dict)
98 knode = et.SubElement(parent, 'kpis')
99 dict_to_junit(knode, kpis)
Pau Espin Pedrolef919c02020-06-05 13:19:02 +0200100
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200101def trial_to_junit_write(trial, junit_path):
102 elements = et.ElementTree(element=trial_to_junit(trial))
103 elements.write(junit_path)
104
105def trial_to_junit(trial):
106 testsuites = et.Element('testsuites')
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100107 num_tests = 0
108 num_failures = 0
109 num_errors = 0
110 time = 0
111 id = 0
Pau Espin Pedrolef919c02020-06-05 13:19:02 +0200112 hash_info = trial.get_all_inst_hash_info()
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200113 for suite in trial.suites:
114 testsuite = suite_to_junit(suite)
Pau Espin Pedrolef919c02020-06-05 13:19:02 +0200115 hash_info_to_junit(testsuite, hash_info)
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100116 testsuite.set('id', str(id))
117 id += 1
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200118 testsuites.append(testsuite)
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100119 num_tests += int(testsuite.get('tests'))
120 num_failures += int(testsuite.get('failures'))
121 num_errors += int(testsuite.get('errors'))
122 time += suite.duration
123 testsuites.set('tests', str(num_tests))
124 testsuites.set('errors', str(num_errors))
125 testsuites.set('failures', str(num_failures))
126 testsuites.set('time', str(math.ceil(time)))
Pau Espin Pedrolf3df1e42020-06-05 11:53:24 +0200127 testsuites.set('name', trial.name())
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200128 return testsuites
129
130def suite_to_junit(suite):
131 testsuite = et.Element('testsuite')
132 testsuite.set('name', suite.name())
133 testsuite.set('hostname', 'localhost')
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200134 if suite.start_timestamp:
135 testsuite.set('timestamp', datetime.fromtimestamp(round(suite.start_timestamp)).isoformat())
136 testsuite.set('time', str(math.ceil(suite.duration)))
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200137 testsuite.set('tests', str(len(suite.tests)))
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100138 passed, skipped, failed, errors = suite.count_test_results()
Holger Hans Peter Freytherf922a992019-02-27 04:35:17 +0000139 for suite_test in suite.tests:
140 testcase = test_to_junit(suite_test)
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100141 testcase.set('classname', suite.name())
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200142 testsuite.append(testcase)
Neels Hofmeyr9596b212020-12-02 09:39:01 +0100143
144 for report_fragment in suite_test.report_fragments:
145 full_name = '%s/%s' % (suite_test.name(), report_fragment.name)
146 el = et.Element('testcase')
147 el.set('name', full_name)
148 el.set('time', str(math.ceil(report_fragment.duration)))
149 if report_fragment.result == test.Test.SKIP:
150 et.SubElement(el, 'skipped')
151 skipped += 1
152 elif report_fragment.result == test.Test.FAIL:
153 failure = et.SubElement(el, 'failure')
154 failure.set('type', suite_test.fail_type or 'failure')
155 failed += 1
156 elif report_fragment.result != test.Test.PASS:
157 error = et.SubElement(el, 'error')
158 error.text = 'could not run'
159 errors += 1
160
161 if report_fragment.output:
162 sout = et.SubElement(el, 'system-out')
Neels Hofmeyr16c8be42020-12-03 22:45:53 +0100163 sout.text = escape_xml_invalid_characters(strip_ansi_colors(report_fragment.output))
Neels Hofmeyr9596b212020-12-02 09:39:01 +0100164 testsuite.append(el)
165
166 testsuite.set('errors', str(errors))
167 testsuite.set('failures', str(failed))
168 testsuite.set('skipped', str(skipped))
169 testsuite.set('disabled', str(skipped))
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200170 return testsuite
171
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100172def test_to_junit(t):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200173 testcase = et.Element('testcase')
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100174 testcase.set('name', t.name())
175 testcase.set('time', str(math.ceil(t.duration)))
176 if t.status == test.Test.SKIP:
Holger Hans Peter Freytherd03acdf2018-09-23 18:06:48 +0100177 et.SubElement(testcase, 'skipped')
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100178 elif t.status == test.Test.FAIL:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200179 failure = et.SubElement(testcase, 'failure')
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100180 failure.set('type', t.fail_type or 'failure')
181 failure.text = t.fail_message
182 if t.fail_tb:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200183 system_err = et.SubElement(testcase, 'system-err')
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100184 system_err.text = t.fail_tb
185 elif t.status != test.Test.PASS:
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200186 error = et.SubElement(testcase, 'error')
187 error.text = 'could not run'
Pau Espin Pedrole3d1b612020-06-15 14:27:50 +0200188 kpis_to_junit(testcase, t.kpis())
Pau Espin Pedrol5bbdab82020-02-24 18:19:10 +0100189 sout = et.SubElement(testcase, 'system-out')
Pau Espin Pedrol644cb412020-03-04 16:14:31 +0100190 sout.text = escape_xml_invalid_characters(t.report_stdout())
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200191 return testcase
192
193def trial_to_text(trial):
Neels Hofmeyr7d79ea42020-11-28 10:06:14 +0100194 suite_passes = []
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200195 suite_failures = []
196 count_fail = 0
197 count_pass = 0
198 for suite in trial.suites:
199 if suite.passed():
200 count_pass += 1
Neels Hofmeyr7d79ea42020-11-28 10:06:14 +0100201 suite_passes.append(suite_to_text(suite))
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200202 else:
203 count_fail += 1
204 suite_failures.append(suite_to_text(suite))
205
206 summary = ['%s: %s' % (trial.name(), trial.status)]
207 if count_fail:
208 summary.append('%d suites failed' % count_fail)
209 if count_pass:
210 summary.append('%d suites passed' % count_pass)
211 msg = [', '.join(summary)]
212 msg.extend(suite_failures)
Neels Hofmeyr7d79ea42020-11-28 10:06:14 +0100213 msg.extend(suite_passes)
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200214 return '\n'.join(msg)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200215
216def suite_to_text(suite):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200217 if not suite.tests:
218 return 'no tests were run.'
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200219
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100220 passed, skipped, failed, errors = suite.count_test_results()
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200221 details = []
222 if failed:
223 details.append('fail: %d' % failed)
Pau Espin Pedrol02e8a8d2020-03-05 17:22:40 +0100224 if errors:
225 details.append('errors: %d' % errors)
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200226 if passed:
227 details.append('pass: %d' % passed)
228 if skipped:
229 details.append('skip: %d' % skipped)
230 msgs = ['%s: %s (%s)' % (suite.status, suite.name(), ', '.join(details))]
231 msgs.extend([test_to_text(t) for t in suite.tests])
232 return '\n '.join(msgs)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200233
Pau Espin Pedrolfd5de3d2017-11-09 14:26:35 +0100234def test_to_text(t):
235 msgs = ['%s: %s' % (t.status, t.name())]
236 if t.start_timestamp:
237 msgs.append('(%.1f sec)' % t.duration)
238 if t.status == test.Test.FAIL:
239 msgs.append('%s: %s' % (t.fail_type, t.fail_message))
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200240 return ' '.join(msgs)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200241
242# vim: expandtab tabstop=4 shiftwidth=4