blob: d4ea1f38976224aedad15aba236bbe87c4aabf37 [file] [log] [blame]
#!/bin/env python
# $Id$
"""
gen_junit_report.py -- The script is to generate the junit report for
Subversion tests. The script uses the log file, tests.log created by
"make check" process. It parses the log file and generate the junit
files for each test separately in the specified output directory. The
script can take --log-file and --output-dir arguments.
"""
import sys
import os
import getopt
def xml_encode(data):
"""encode the xml characters in the data"""
encode = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&apos;'
}
for char in encode.keys():
data = data.replace(char, encode[char])
return data
def start_junit():
"""define the beginning of xml document"""
head = """<?xml version="1.0" encoding="UTF-8"?>"""
return head
def start_testsuite(test_name):
"""start testsuite. The value for the attributes are replaced later
when the junit file handling is concluded"""
sub_test_name = test_name.replace('.', '-')
start = """<testsuite time="ELAPSED_%s" tests="TOTAL_%s" name="%s"
failures="FAIL_%s" errors="FAIL_%s" skipped="SKIP_%s">""" % \
(test_name, test_name, sub_test_name, test_name, test_name, test_name)
return start
def junit_testcase_ok(test_name, casename):
"""mark the test case as PASSED"""
casename = xml_encode(casename)
sub_test_name = test_name.replace('.', '-')
case = """<testcase time="ELAPSED_CASE_%s" name="%s" classname="%s"/>""" % \
(test_name, casename, sub_test_name)
return case
def junit_testcase_fail(test_name, casename, reason=None):
"""mark the test case as FAILED"""
casename = xml_encode(casename)
sub_test_name = test_name.replace('.', '-')
case = """<testcase time="ELAPSED_CASE_%s" name="%s" classname="%s">
<failure type="Failed"><![CDATA[%s]]></failure>
</testcase>""" % (test_name, casename, sub_test_name, reason)
return case
def junit_testcase_xfail(test_name, casename, reason=None):
"""mark the test case as XFAILED"""
casename = xml_encode(casename)
sub_test_name = test_name.replace('.', '-')
case = """<testcase time="ELAPSED_CASE_%s" name="%s" classname="%s">
<system-out><![CDATA[%s]]></system-out>
</testcase>""" % (test_name, casename, sub_test_name, reason)
return case
def junit_testcase_skip(test_name, casename):
"""mark the test case as SKIPPED"""
casename = xml_encode(casename)
sub_test_name = test_name.replace('.', '-')
case = """<testcase time="ELAPSED_CASE_%s" name="%s" classname="%s">
<skipped message="Skipped"/>
</testcase>""" % (test_name, casename, sub_test_name)
return case
def end_testsuite():
"""mark the end of testsuite"""
end = """</testsuite>"""
return end
def update_stat(test_name, junit, count):
"""update the test statistics in the junit string"""
junit_str = '\n'.join(junit)
t_count = count[test_name]
total = float(t_count['pass'] + t_count['fail'] + t_count['skip'])
elapsed = float(t_count['elapsed'])
case_time = 0
if total > 0: # there are tests with no test cases
case_time = elapsed/total
total_patt = 'TOTAL_%s' % test_name
fail_patt = 'FAIL_%s' % test_name
skip_patt = 'SKIP_%s' % test_name
elapsed_patt = 'ELAPSED_%s' % test_name
elapsed_case_patt = 'ELAPSED_CASE_%s' % test_name
# replace the pattern in junit string with actual statistics
junit_str = junit_str.replace(total_patt, "%s" % total)
junit_str = junit_str.replace(fail_patt, "%s" % t_count['fail'])
junit_str = junit_str.replace(skip_patt, "%s" % t_count['skip'])
junit_str = junit_str.replace(elapsed_patt, "%.3f" % elapsed)
junit_str = junit_str.replace(elapsed_case_patt, "%.3f" % case_time)
return junit_str
def main():
"""main method"""
try:
opts, args = getopt.getopt(sys.argv[1:], 'l:d:h',
['log-file=', 'output-dir=', 'help'])
except getopt.GetoptError, err:
usage(err)
log_file = None
output_dir = None
for opt, value in opts:
if (opt in ('-h', '--help')):
usage()
elif (opt in ('-l', '--log-file')):
log_file = value
elif (opt in ('-d', '--output-dir')):
output_dir = value
else:
usage('Unable to recognize option')
if not log_file or not output_dir:
usage("The options --log-file and --output-dir are mandatory")
# create junit output directory, if not exists
if not os.path.exists(output_dir):
print("Directory '%s' not exists, creating ..." % output_dir)
try:
os.makedirs(output_dir)
except OSError, err:
sys.stderr.write("ERROR: %s\n" % err)
sys.exit(1)
patterns = {
'start' : 'START:',
'end' : 'END:',
'pass' : 'PASS:',
'skip' : 'SKIP:',
'fail' : 'FAIL:',
'xfail' : 'XFAIL:',
'elapsed' : 'ELAPSED:'
}
junit = []
junit.append(start_junit())
reason = None
count = {}
fp = None
try:
fp = open(log_file, 'r')
except IOError, err:
sys.stderr.write("ERROR: %s\n" % err)
sys.exit(1)
for line in fp.readlines():
line = line.strip()
if line.startswith(patterns['start']):
reason = ""
test_name = line.split(' ')[1]
# replace '.' in test name with '_' to avoid confusing class
# name in test result displayed in the CI user interface
test_name.replace('.', '_')
count[test_name] = {
'pass' : 0,
'skip' : 0,
'fail' : 0,
'xfail' : 0,
'elapsed' : 0,
'total' : 0
}
junit.append(start_testsuite(test_name))
elif line.startswith(patterns['end']):
junit.append(end_testsuite())
elif line.startswith(patterns['pass']):
reason = ""
casename = line.strip(patterns['pass']).strip()
junit.append(junit_testcase_ok(test_name, casename))
count[test_name]['pass'] += 1
elif line.startswith(patterns['skip']):
reason = ""
casename = line.strip(patterns['skip']).strip()
junit.append(junit_testcase_skip(test_name, casename))
count[test_name]['skip'] += 1
elif line.startswith(patterns['fail']):
casename = line.strip(patterns['fail']).strip()
junit.append(junit_testcase_fail(test_name, casename, reason))
count[test_name]['fail'] += 1
reason = ""
elif line.startswith(patterns['xfail']):
casename = line.strip(patterns['xfail']).strip()
junit.append(junit_testcase_xfail(test_name, casename, reason))
count[test_name]['pass'] += 1
reason = ""
elif line.startswith(patterns['elapsed']):
reason = ""
elapsed = line.split(' ')[2].strip()
(hrs, mins, secs) = elapsed.split(':')
secs_taken = int(hrs)*24 + int(mins)*60 + float(secs)
count[test_name]['elapsed'] = secs_taken
junit_str = update_stat(test_name, junit, count)
test_junit_file = os.path.join(output_dir,
"%s.junit.xml" % test_name)
w_fp = open (test_junit_file, 'w')
w_fp.writelines(junit_str)
w_fp.close()
junit = []
elif len(line):
reason = "%s\n%s" % (reason, line)
fp.close()
def usage(errorMsg=None):
script_name = os.path.basename(sys.argv[0])
sys.stdout.write("""USAGE: %s: [--help|h] --log-file|l --output-dir|d
Options:
--help|-h Display help message
--log-file|l The log file to parse for generating junit xml files
--output-dir|d The directory to create the junit xml file for each
test
""" % script_name)
if errorMsg is not None:
sys.stderr.write("\nERROR: %s\n" % errorMsg)
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()