# osmo_gsm_tester: report: directory of binaries to be tested # # Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH # # Author: Pau Espin Pedrol # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # junit xml format: https://llg.cubic.org/docs/junit/ import math import sys import re from datetime import datetime import xml.etree.ElementTree as et from xml.sax.saxutils import escape from . import test invalid_xml_char_ranges = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84), (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)] if sys.maxunicode >= 0x10000: # not narrow build invalid_xml_char_ranges.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)]) invalid_xml_char_ranges_str = ['%s-%s' % (chr(low), chr(high)) for (low, high) in invalid_xml_char_ranges] invalid_xml_char_ranges_regex = re.compile('[%s]' % ''.join(invalid_xml_char_ranges_str)) def escape_xml_invalid_characters(str): replacement_char = '\uFFFD' # Unicode replacement character return invalid_xml_char_ranges_regex.sub(replacement_char, escape(str)) def trial_to_junit_write(trial, junit_path): elements = et.ElementTree(element=trial_to_junit(trial)) elements.write(junit_path) def trial_to_junit(trial): testsuites = et.Element('testsuites') num_tests = 0 num_failures = 0 num_errors = 0 time = 0 id = 0 for suite in trial.suites: testsuite = suite_to_junit(suite) testsuite.set('id', str(id)) id += 1 testsuites.append(testsuite) num_tests += int(testsuite.get('tests')) num_failures += int(testsuite.get('failures')) num_errors += int(testsuite.get('errors')) time += suite.duration testsuites.set('tests', str(num_tests)) testsuites.set('errors', str(num_errors)) testsuites.set('failures', str(num_failures)) testsuites.set('time', str(math.ceil(time))) return testsuites def suite_to_junit(suite): testsuite = et.Element('testsuite') testsuite.set('name', suite.name()) testsuite.set('hostname', 'localhost') if suite.start_timestamp: testsuite.set('timestamp', datetime.fromtimestamp(round(suite.start_timestamp)).isoformat()) testsuite.set('time', str(math.ceil(suite.duration))) testsuite.set('tests', str(len(suite.tests))) passed, skipped, failed, errors = suite.count_test_results() testsuite.set('errors', str(errors)) testsuite.set('failures', str(failed)) testsuite.set('skipped', str(skipped)) testsuite.set('disabled', str(skipped)) for suite_test in suite.tests: testcase = test_to_junit(suite_test) testcase.set('classname', suite.name()) testsuite.append(testcase) return testsuite def test_to_junit(t): testcase = et.Element('testcase') testcase.set('name', t.name()) testcase.set('time', str(math.ceil(t.duration))) if t.status == test.Test.SKIP: et.SubElement(testcase, 'skipped') elif t.status == test.Test.FAIL: failure = et.SubElement(testcase, 'failure') failure.set('type', t.fail_type or 'failure') failure.text = t.fail_message if t.fail_tb: system_err = et.SubElement(testcase, 'system-err') system_err.text = t.fail_tb elif t.status != test.Test.PASS: error = et.SubElement(testcase, 'error') error.text = 'could not run' sout = et.SubElement(testcase, 'system-out') sout.text = escape_xml_invalid_characters(t.report_stdout()) return testcase def trial_to_text(trial): suite_failures = [] count_fail = 0 count_pass = 0 for suite in trial.suites: if suite.passed(): count_pass += 1 else: count_fail += 1 suite_failures.append(suite_to_text(suite)) summary = ['%s: %s' % (trial.name(), trial.status)] if count_fail: summary.append('%d suites failed' % count_fail) if count_pass: summary.append('%d suites passed' % count_pass) msg = [', '.join(summary)] msg.extend(suite_failures) return '\n'.join(msg) def suite_to_text(suite): if not suite.tests: return 'no tests were run.' passed, skipped, failed, errors = suite.count_test_results() details = [] if failed: details.append('fail: %d' % failed) if errors: details.append('errors: %d' % errors) if passed: details.append('pass: %d' % passed) if skipped: details.append('skip: %d' % skipped) msgs = ['%s: %s (%s)' % (suite.status, suite.name(), ', '.join(details))] msgs.extend([test_to_text(t) for t in suite.tests]) return '\n '.join(msgs) def test_to_text(t): msgs = ['%s: %s' % (t.status, t.name())] if t.start_timestamp: msgs.append('(%.1f sec)' % t.duration) if t.status == test.Test.FAIL: msgs.append('%s: %s' % (t.fail_type, t.fail_message)) return ' '.join(msgs) # vim: expandtab tabstop=4 shiftwidth=4