|
| 1 | +#!/usr/bin/env python |
| 2 | +# |
| 3 | +# OVERVIEW |
| 4 | +# |
| 5 | +# This script converts an xml file generated by the tool Valgrind Memcheck |
| 6 | +# into JUnit xml format. JUnit is a testing framework supported by CI tools |
| 7 | +# such as Jenkins to display test results. |
| 8 | +# |
| 9 | +# The script searches for all `error` elements in a Valgrind Memcheck xml |
| 10 | +# file. If none is found, the xml file is converted to a passing test. All |
| 11 | +# errors found are inserted as a `test case` element in the JUnit xml file. |
| 12 | +# If the option `skip_tests` is defined, that replaces all `error` elements |
| 13 | +# with `skipped` elements. |
| 14 | +# |
| 15 | +# USAGE |
| 16 | +# |
| 17 | +# python memcheck_xml2junit_converter.py [OPTIONS] |
| 18 | +# |
| 19 | +# Run the script by supplying an input directory containing one or several xml |
| 20 | +# files and an output directory where the converted files are collected. |
| 21 | +# |
| 22 | +# OPTIONS |
| 23 | +# * -i, --input_directory: Sets the folder path to where the script searches |
| 24 | +# for xml files to convert. Subdirectories within the |
| 25 | +# directory are also included. |
| 26 | +# * -o, --output_directory: Defines the output folder where the converted JUnit |
| 27 | +# xml files are collected. |
| 28 | +# * -s, --skip_tests: Error elements in a Valgrind Memcheck xml file are |
| 29 | +# replaced by a skipped message type in the converted |
| 30 | +# JUnit xml file. |
| 31 | +# |
| 32 | +import xml.etree.ElementTree as ET |
| 33 | +import sys, os, argparse |
| 34 | + |
| 35 | +parser = argparse.ArgumentParser(description='Convert Valgrind Memcheck xml into JUnit xml format.') |
| 36 | +optional = parser._action_groups.pop() |
| 37 | +required = parser.add_argument_group('required arguments') |
| 38 | +required.add_argument('-i','--input_directory', |
| 39 | + help='Directory where Valgrind Memcheck xml files are located', |
| 40 | + required=True) |
| 41 | +required.add_argument('-o','--output_directory', |
| 42 | + help='Directory where the converted JUnit xml files are collected', |
| 43 | + required=True) |
| 44 | +optional.add_argument('-s','--skip_tests', |
| 45 | + help='Error elements in a Valgrind Memcheck xml file is replaced by a skipped message type in the converted JUnit xml file', |
| 46 | + action='store_true') |
| 47 | +parser._action_groups.append(optional) |
| 48 | +args = parser.parse_args() |
| 49 | + |
| 50 | +if not os.path.exists(args.output_directory): |
| 51 | + os.mkdir(args.output_directory) |
| 52 | + |
| 53 | +for subdir, dirs, files in os.walk(args.input_directory): |
| 54 | + if os.path.basename(subdir) == os.path.basename(args.output_directory): |
| 55 | + continue |
| 56 | + for filename in files: |
| 57 | + if "xml" in filename: |
| 58 | + # read errors in valgrind memcheck xml |
| 59 | + input_filepath = os.path.join(subdir, filename) |
| 60 | + try: |
| 61 | + doc = ET.parse(input_filepath) |
| 62 | + except ET.ParseError: |
| 63 | + continue |
| 64 | + errors = doc.findall('.//error') |
| 65 | + |
| 66 | + # create output filename |
| 67 | + output_filename = os.path.join(args.output_directory, filename) |
| 68 | + if not output_filename.endswith('.xml'): |
| 69 | + output_filename += '.xml' |
| 70 | + |
| 71 | + test_type = "error" |
| 72 | + plural = "s" |
| 73 | + if args.skip_tests: |
| 74 | + test_type = "skipped" |
| 75 | + plural = "" |
| 76 | + |
| 77 | + out = open(output_filename,"w") |
| 78 | + out.write('<?xml version="1.0" encoding="UTF-8"?>\n') |
| 79 | + if len(errors) == 0: |
| 80 | + out.write('<testsuite name="valgrind" tests="1" '+test_type+''+plural+'="'+str(len(errors))+'">\n') |
| 81 | + out.write(' <testcase classname="valgrind-memcheck" name="'+str(filename)+'"/>\n') |
| 82 | + else: |
| 83 | + out.write('<testsuite name="valgrind" tests="'+str(len(errors))+'" '+test_type+''+plural+'="'+str(len(errors))+'">\n') |
| 84 | + errorcount=0 |
| 85 | + for error in errors: |
| 86 | + errorcount += 1 |
| 87 | + |
| 88 | + kind = error.find('kind') |
| 89 | + what = error.find('what') |
| 90 | + if what == None: |
| 91 | + what = error.find('xwhat/text') |
| 92 | + |
| 93 | + stack = error.find('stack') |
| 94 | + frames = stack.findall('frame') |
| 95 | + |
| 96 | + for frame in frames: |
| 97 | + fi = frame.find('file') |
| 98 | + li = frame.find('line') |
| 99 | + if fi != None and li != None: |
| 100 | + break |
| 101 | + |
| 102 | + if fi != None and li != None: |
| 103 | + out.write(' <testcase classname="valgrind-memcheck" name="'+str(filename)+' '+str(errorcount)+' ('+kind.text+', '+fi.text+':'+li.text+')">\n') |
| 104 | + else: |
| 105 | + out.write(' <testcase classname="valgrind-memcheck" name="'+str(filename)+' '+str(errorcount)+' ('+kind.text+')">\n') |
| 106 | + out.write(' <'+test_type+' type="'+kind.text+'">\n') |
| 107 | + out.write(' '+what.text+'\n\n') |
| 108 | + |
| 109 | + for frame in frames: |
| 110 | + ip = frame.find('ip') |
| 111 | + fn = frame.find('fn') |
| 112 | + fi = frame.find('file') |
| 113 | + li = frame.find('line') |
| 114 | + if fn != None: |
| 115 | + bodytext = fn.text |
| 116 | + else: |
| 117 | + bodytext = "unknown function name" |
| 118 | + bodytext = bodytext.replace("&","&") |
| 119 | + bodytext = bodytext.replace("<","<") |
| 120 | + bodytext = bodytext.replace(">",">") |
| 121 | + if fi != None and li != None: |
| 122 | + out.write(' '+ip.text+': '+bodytext+' ('+fi.text+':'+li.text+')\n') |
| 123 | + else: |
| 124 | + out.write(' '+ip.text+': '+bodytext+'\n') |
| 125 | + out.write(' </'+test_type+'>\n') |
| 126 | + out.write(' </testcase>\n') |
| 127 | + out.write('</testsuite>\n') |
| 128 | + out.close() |
| 129 | + |
0 commit comments