Skip to content

Commit dff8c94

Browse files
authored
Add script to convert valgrind xml to JUnit xml
1 parent eadd72a commit dff8c94

File tree

2 files changed

+168
-2
lines changed

2 files changed

+168
-2
lines changed

Valgrind.cmake

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,21 @@
119119
# UNDEF_VALUE_ERRORS controls whether Memcheck reports uses of undefined value
120120
# errors.
121121
#
122+
# GENERATE_JUNIT_REPORT converts the xml file output to a JUnit xml file
123+
# format that can be picked up by CI tools such as Jenkins to display test
124+
# results.
125+
#
126+
# JUNIT_OPTIONS enables additional options when generating JUnit reports.
127+
# * --input_directory: Set equal to a folder path where Valgrind Memcheck xml
128+
# files exist. [Default: `${report_directory}/${report_folder}`]
129+
# * --output_directory: Set equal to a folder path where the converted files should
130+
# be placed. [Default: `${report_directory}/junit-xml`]
131+
# * --skip_tests: Boolean flag that, if included, skips a test with
132+
# reported errors and which prevents failure of a
133+
# CI tools pipeline step. [Default: False]
134+
#
135+
# Example: `JUNIT_OPTIONS --output_directory=/path_to_location/ --skip_tests`.
136+
#
122137
### MASSIF SPECIFIC OPTIONS:
123138
#
124139
# DEPTH=<number> [default: 30], maximum depth of the allocation trees recorded
@@ -266,9 +281,9 @@ macro(setup_custom_target valgrind_tool target_name)
266281
endmacro()
267282

268283
function(swift_add_valgrind_memcheck target)
269-
set(argOption SHOW_REACHABLE TRACK_ORIGINS UNDEF_VALUE_ERRORS)
284+
set(argOption SHOW_REACHABLE TRACK_ORIGINS UNDEF_VALUE_ERRORS GENERATE_JUNIT_REPORT)
270285
set(argSingle LEAK_CHECK)
271-
set(argMulti "")
286+
set(argMulti JUNIT_OPTIONS)
272287

273288
set(valgrind_tool memcheck)
274289
_valgrind_basic_setup(${target})
@@ -299,6 +314,28 @@ function(swift_add_valgrind_memcheck target)
299314
endif()
300315

301316
setup_custom_target(${valgrind_tool} ${target_name})
317+
318+
if (x_GENERATE_JUNIT_REPORT)
319+
set(junit_input_dir -i=${report_directory}/${report_folder})
320+
set(junit_output_dir -o=${report_directory}/junit-xml)
321+
foreach (junit_option ${x_JUNIT_OPTIONS})
322+
if (${junit_option} MATCHES "--input_directory")
323+
set(junit_input_dir ${junit_option})
324+
list(REMOVE_ITEM x_JUNIT_OPTIONS ${junit_option})
325+
elseif (${junit_option} MATCHES "--output_directory")
326+
set(junit_output_dir ${junit_option})
327+
list(REMOVE_ITEM x_JUNIT_OPTIONS ${junit_option})
328+
endif()
329+
endforeach()
330+
331+
set(script_options ${x_JUNIT_OPTIONS})
332+
list(APPEND script_options ${junit_input_dir})
333+
list(APPEND script_options ${junit_output_dir})
334+
335+
add_custom_command(TARGET ${target_name} POST_BUILD
336+
COMMAND python ${CMAKE_SOURCE_DIR}/cmake/common/scripts/memcheck_xml2junit_converter.py ${script_options}
337+
)
338+
endif()
302339
endfunction()
303340

304341
function(swift_add_valgrind_callgrind target)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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("&","&amp;")
119+
bodytext = bodytext.replace("<","&lt;")
120+
bodytext = bodytext.replace(">","&gt;")
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

Comments
 (0)