Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
mbed-cloud-client/mbed-client-pal/Utils/Scripts/junit_cmd_wrapper.py@0:276e7a263c35, 2018-07-02 (annotated)
- Committer:
- MACRUM
- Date:
- Mon Jul 02 06:30:39 2018 +0000
- Revision:
- 0:276e7a263c35
Initial commit
Who changed what in which revision?
| User | Revision | Line number | New contents of line | 
|---|---|---|---|
| MACRUM | 0:276e7a263c35 | 1 | #!/usr/bin/env python | 
| MACRUM | 0:276e7a263c35 | 2 | import argparse | 
| MACRUM | 0:276e7a263c35 | 3 | import logging | 
| MACRUM | 0:276e7a263c35 | 4 | import string | 
| MACRUM | 0:276e7a263c35 | 5 | import subprocess | 
| MACRUM | 0:276e7a263c35 | 6 | import sys | 
| MACRUM | 0:276e7a263c35 | 7 | import time | 
| MACRUM | 0:276e7a263c35 | 8 | import xml.etree.cElementTree as ElementTree | 
| MACRUM | 0:276e7a263c35 | 9 | from io import BytesIO | 
| MACRUM | 0:276e7a263c35 | 10 | from threading import Thread | 
| MACRUM | 0:276e7a263c35 | 11 | |
| MACRUM | 0:276e7a263c35 | 12 | logger = logging.getLogger('ta-junit-wrapper') | 
| MACRUM | 0:276e7a263c35 | 13 | |
| MACRUM | 0:276e7a263c35 | 14 | |
| MACRUM | 0:276e7a263c35 | 15 | class TeeBytesIO(BytesIO): | 
| MACRUM | 0:276e7a263c35 | 16 | """duplicate each write command to an additional file object""" | 
| MACRUM | 0:276e7a263c35 | 17 | def __init__(self, tee_fh): | 
| MACRUM | 0:276e7a263c35 | 18 | self.tee_fh = tee_fh | 
| MACRUM | 0:276e7a263c35 | 19 | super(TeeBytesIO, self).__init__() | 
| MACRUM | 0:276e7a263c35 | 20 | |
| MACRUM | 0:276e7a263c35 | 21 | def write(self, s): | 
| MACRUM | 0:276e7a263c35 | 22 | self.tee_fh.write(s) | 
| MACRUM | 0:276e7a263c35 | 23 | BytesIO.write(self, s) | 
| MACRUM | 0:276e7a263c35 | 24 | |
| MACRUM | 0:276e7a263c35 | 25 | |
| MACRUM | 0:276e7a263c35 | 26 | def get_parser(): | 
| MACRUM | 0:276e7a263c35 | 27 | parser = argparse.ArgumentParser(description='JUNIT wrapper') | 
| MACRUM | 0:276e7a263c35 | 28 | |
| MACRUM | 0:276e7a263c35 | 29 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 30 | '-o', | 
| MACRUM | 0:276e7a263c35 | 31 | '--output-file', | 
| MACRUM | 0:276e7a263c35 | 32 | metavar='FILE', | 
| MACRUM | 0:276e7a263c35 | 33 | type=argparse.FileType('w'), | 
| MACRUM | 0:276e7a263c35 | 34 | help='output JUNIT XML file name', | 
| MACRUM | 0:276e7a263c35 | 35 | required=True | 
| MACRUM | 0:276e7a263c35 | 36 | ) | 
| MACRUM | 0:276e7a263c35 | 37 | |
| MACRUM | 0:276e7a263c35 | 38 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 39 | '-s', | 
| MACRUM | 0:276e7a263c35 | 40 | '--test-suite', | 
| MACRUM | 0:276e7a263c35 | 41 | metavar='NAME', | 
| MACRUM | 0:276e7a263c35 | 42 | help='test suite name', | 
| MACRUM | 0:276e7a263c35 | 43 | required=True | 
| MACRUM | 0:276e7a263c35 | 44 | ) | 
| MACRUM | 0:276e7a263c35 | 45 | |
| MACRUM | 0:276e7a263c35 | 46 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 47 | '-t', | 
| MACRUM | 0:276e7a263c35 | 48 | '--test-case', | 
| MACRUM | 0:276e7a263c35 | 49 | metavar='NAME', | 
| MACRUM | 0:276e7a263c35 | 50 | help='test case name', | 
| MACRUM | 0:276e7a263c35 | 51 | required=True | 
| MACRUM | 0:276e7a263c35 | 52 | ) | 
| MACRUM | 0:276e7a263c35 | 53 | |
| MACRUM | 0:276e7a263c35 | 54 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 55 | '-v', | 
| MACRUM | 0:276e7a263c35 | 56 | '--verbose', | 
| MACRUM | 0:276e7a263c35 | 57 | action='store_true', | 
| MACRUM | 0:276e7a263c35 | 58 | help='verbose - duplicate command output to STDOUT' | 
| MACRUM | 0:276e7a263c35 | 59 | ) | 
| MACRUM | 0:276e7a263c35 | 60 | |
| MACRUM | 0:276e7a263c35 | 61 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 62 | '--validate', | 
| MACRUM | 0:276e7a263c35 | 63 | action='store_true', | 
| MACRUM | 0:276e7a263c35 | 64 | help='validate generated XML against Jenkins XSD. Requires "requests" and "lxml" libraries' | 
| MACRUM | 0:276e7a263c35 | 65 | ) | 
| MACRUM | 0:276e7a263c35 | 66 | |
| MACRUM | 0:276e7a263c35 | 67 | parser.add_argument( | 
| MACRUM | 0:276e7a263c35 | 68 | '--command', | 
| MACRUM | 0:276e7a263c35 | 69 | nargs=argparse.REMAINDER, | 
| MACRUM | 0:276e7a263c35 | 70 | help='command to be executed' | 
| MACRUM | 0:276e7a263c35 | 71 | ) | 
| MACRUM | 0:276e7a263c35 | 72 | return parser | 
| MACRUM | 0:276e7a263c35 | 73 | |
| MACRUM | 0:276e7a263c35 | 74 | |
| MACRUM | 0:276e7a263c35 | 75 | def get_file_copy_worker(infile, outfile): | 
| MACRUM | 0:276e7a263c35 | 76 | def do_work(_infile, _outfile): | 
| MACRUM | 0:276e7a263c35 | 77 | for line in iter(_infile.readline, ''): | 
| MACRUM | 0:276e7a263c35 | 78 | _outfile.write(line) | 
| MACRUM | 0:276e7a263c35 | 79 | _infile.close() | 
| MACRUM | 0:276e7a263c35 | 80 | thread = Thread(target=do_work, args=(infile, outfile)) | 
| MACRUM | 0:276e7a263c35 | 81 | thread.daemon = True | 
| MACRUM | 0:276e7a263c35 | 82 | thread.start() | 
| MACRUM | 0:276e7a263c35 | 83 | return thread | 
| MACRUM | 0:276e7a263c35 | 84 | |
| MACRUM | 0:276e7a263c35 | 85 | |
| MACRUM | 0:276e7a263c35 | 86 | def generate_junit_xml(test_suite, test_case, out_fh, stdout, stderr, return_code, duration_in_sec, command): | 
| MACRUM | 0:276e7a263c35 | 87 | test_suite_root_element = ElementTree.Element( | 
| MACRUM | 0:276e7a263c35 | 88 | 'testsuite', | 
| MACRUM | 0:276e7a263c35 | 89 | tests='1', | 
| MACRUM | 0:276e7a263c35 | 90 | name=test_suite.replace(' ', '_'), | 
| MACRUM | 0:276e7a263c35 | 91 | failures=str(1 if return_code != 0 else 0), | 
| MACRUM | 0:276e7a263c35 | 92 | time=str(duration_in_sec) | 
| MACRUM | 0:276e7a263c35 | 93 | ) | 
| MACRUM | 0:276e7a263c35 | 94 | test_case_element = ElementTree.SubElement( | 
| MACRUM | 0:276e7a263c35 | 95 | test_suite_root_element, | 
| MACRUM | 0:276e7a263c35 | 96 | 'testcase', | 
| MACRUM | 0:276e7a263c35 | 97 | time=str(duration_in_sec), | 
| MACRUM | 0:276e7a263c35 | 98 | name=test_case.replace(' ', '_') | 
| MACRUM | 0:276e7a263c35 | 99 | ) | 
| MACRUM | 0:276e7a263c35 | 100 | ElementTree.SubElement(test_case_element, 'system-out').text = filter( | 
| MACRUM | 0:276e7a263c35 | 101 | lambda x: x in string.printable, stdout.getvalue() | 
| MACRUM | 0:276e7a263c35 | 102 | ) | 
| MACRUM | 0:276e7a263c35 | 103 | ElementTree.SubElement(test_case_element, 'system-err').text = filter( | 
| MACRUM | 0:276e7a263c35 | 104 | lambda x: x in string.printable, stderr.getvalue() | 
| MACRUM | 0:276e7a263c35 | 105 | ) | 
| MACRUM | 0:276e7a263c35 | 106 | |
| MACRUM | 0:276e7a263c35 | 107 | if return_code != 0: | 
| MACRUM | 0:276e7a263c35 | 108 | failure_msg = 'Command "{cmd}" returned {ret}'.format(cmd=command, ret=return_code) | 
| MACRUM | 0:276e7a263c35 | 109 | ElementTree.SubElement( | 
| MACRUM | 0:276e7a263c35 | 110 | test_case_element, | 
| MACRUM | 0:276e7a263c35 | 111 | 'failure', | 
| MACRUM | 0:276e7a263c35 | 112 | type='Non-Zero return code', | 
| MACRUM | 0:276e7a263c35 | 113 | message=failure_msg) | 
| MACRUM | 0:276e7a263c35 | 114 | |
| MACRUM | 0:276e7a263c35 | 115 | ElementTree.ElementTree(test_suite_root_element).write(out_fh) | 
| MACRUM | 0:276e7a263c35 | 116 | |
| MACRUM | 0:276e7a263c35 | 117 | |
| MACRUM | 0:276e7a263c35 | 118 | def main(): | 
| MACRUM | 0:276e7a263c35 | 119 | parser = get_parser() | 
| MACRUM | 0:276e7a263c35 | 120 | args = parser.parse_args() | 
| MACRUM | 0:276e7a263c35 | 121 | logging.basicConfig( | 
| MACRUM | 0:276e7a263c35 | 122 | level=logging.DEBUG if args.verbose else logging.INFO, | 
| MACRUM | 0:276e7a263c35 | 123 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | 
| MACRUM | 0:276e7a263c35 | 124 | stream=sys.stdout | 
| MACRUM | 0:276e7a263c35 | 125 | ) | 
| MACRUM | 0:276e7a263c35 | 126 | |
| MACRUM | 0:276e7a263c35 | 127 | stdout = TeeBytesIO(sys.stdout) if args.verbose else BytesIO() | 
| MACRUM | 0:276e7a263c35 | 128 | stderr = TeeBytesIO(stdout) | 
| MACRUM | 0:276e7a263c35 | 129 | logger.debug('Executing + ' + ' '.join(args.command)) | 
| MACRUM | 0:276e7a263c35 | 130 | start_time = time.time() | 
| MACRUM | 0:276e7a263c35 | 131 | process = subprocess.Popen(args.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
| MACRUM | 0:276e7a263c35 | 132 | threads = [ | 
| MACRUM | 0:276e7a263c35 | 133 | get_file_copy_worker(process.stdout, stdout), | 
| MACRUM | 0:276e7a263c35 | 134 | get_file_copy_worker(process.stderr, stderr) | 
| MACRUM | 0:276e7a263c35 | 135 | ] | 
| MACRUM | 0:276e7a263c35 | 136 | for t in threads: # wait for IO completion | 
| MACRUM | 0:276e7a263c35 | 137 | t.join() | 
| MACRUM | 0:276e7a263c35 | 138 | return_code = process.wait() | 
| MACRUM | 0:276e7a263c35 | 139 | logger.debug('Wrapped process return code ' + str(return_code)) | 
| MACRUM | 0:276e7a263c35 | 140 | duration_in_sec = time.time() - start_time | 
| MACRUM | 0:276e7a263c35 | 141 | |
| MACRUM | 0:276e7a263c35 | 142 | with args.output_file as fh: # insure file object is closed - since it will be read in do_validate() | 
| MACRUM | 0:276e7a263c35 | 143 | generate_junit_xml( | 
| MACRUM | 0:276e7a263c35 | 144 | args.test_suite, | 
| MACRUM | 0:276e7a263c35 | 145 | args.test_case, | 
| MACRUM | 0:276e7a263c35 | 146 | fh, | 
| MACRUM | 0:276e7a263c35 | 147 | stdout, | 
| MACRUM | 0:276e7a263c35 | 148 | stderr, | 
| MACRUM | 0:276e7a263c35 | 149 | return_code, | 
| MACRUM | 0:276e7a263c35 | 150 | duration_in_sec, | 
| MACRUM | 0:276e7a263c35 | 151 | args.command | 
| MACRUM | 0:276e7a263c35 | 152 | ) | 
| MACRUM | 0:276e7a263c35 | 153 | logger.debug('Generated JUNIT report file ' + args.output_file.name) | 
| MACRUM | 0:276e7a263c35 | 154 | |
| MACRUM | 0:276e7a263c35 | 155 | if args.validate: | 
| MACRUM | 0:276e7a263c35 | 156 | do_validate(args.output_file.name) | 
| MACRUM | 0:276e7a263c35 | 157 | |
| MACRUM | 0:276e7a263c35 | 158 | raise SystemExit(return_code) | 
| MACRUM | 0:276e7a263c35 | 159 | |
| MACRUM | 0:276e7a263c35 | 160 | if __name__ == '__main__': | 
| MACRUM | 0:276e7a263c35 | 161 | main() |