18ccd5183SScott Kruger#!/usr/bin/env python 25b6bfdb9SJed Brownfrom __future__ import print_function 391bc3e46SScott Krugerimport glob, os, re 48ccd5183SScott Krugerimport optparse 53054ff8cSScott Krugerimport inspect 63054ff8cSScott Kruger 78ccd5183SScott Kruger""" 88ccd5183SScott KrugerQuick script for parsing the output of the test system and summarizing the results. 98ccd5183SScott Kruger""" 108ccd5183SScott Kruger 113054ff8cSScott Krugerdef inInstallDir(): 123054ff8cSScott Kruger """ 133054ff8cSScott Kruger When petsc is installed then this file in installed in: 143054ff8cSScott Kruger <PREFIX>/share/petsc/examples/config/gmakegentest.py 153054ff8cSScott Kruger otherwise the path is: 163054ff8cSScott Kruger <PETSC_DIR>/config/gmakegentest.py 173054ff8cSScott Kruger We use this difference to determine if we are in installdir 183054ff8cSScott Kruger """ 193054ff8cSScott Kruger thisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 203054ff8cSScott Kruger dirlist=thisscriptdir.split(os.path.sep) 213054ff8cSScott Kruger if len(dirlist)>4: 223054ff8cSScott Kruger lastfour=os.path.sep.join(dirlist[len(dirlist)-4:]) 233054ff8cSScott Kruger if lastfour==os.path.join('share','petsc','examples','config'): 243054ff8cSScott Kruger return True 253054ff8cSScott Kruger else: 263054ff8cSScott Kruger return False 273054ff8cSScott Kruger else: 283054ff8cSScott Kruger return False 293054ff8cSScott Kruger 3032f4009dSScott Krugerdef summarize_results(directory,make,ntime,etime): 318ccd5183SScott Kruger ''' Loop over all of the results files and summarize the results''' 32f04d3c48SAlp Dener startdir = os.getcwd() 336d7e7da8SJed Brown try: 348ccd5183SScott Kruger os.chdir(directory) 356d7e7da8SJed Brown except OSError: 366d7e7da8SJed Brown print('# No tests run') 376d7e7da8SJed Brown return 38bbf1c217SScott Kruger summary={'total':0,'success':0,'failed':0,'failures':[],'todo':0,'skip':0, 39*9b757ad5SKarl Rupp 'time':0, 'cputime':0} 40bbf1c217SScott Kruger timesummary={} 41*9b757ad5SKarl Rupp cputimesummary={} 42bbf1c217SScott Kruger timelist=[] 438ccd5183SScott Kruger for cfile in glob.glob('*.counts'): 44100ef3c7SJed Brown with open(cfile, 'r') as f: 45100ef3c7SJed Brown for line in f: 46100ef3c7SJed Brown l = line.split() 47521be42fSScott Kruger if l[0] == 'failures': 48d8e31410SScott Kruger if len(l)>1: 490bffd254SScott Kruger summary[l[0]] += l[1:] 50521be42fSScott Kruger elif l[0] == 'time': 51521be42fSScott Kruger if len(l)==1: continue 52521be42fSScott Kruger summary[l[0]] += float(l[1]) 53*9b757ad5SKarl Rupp summary['cputime'] += float(l[2]) 54521be42fSScott Kruger timesummary[cfile]=float(l[1]) 55*9b757ad5SKarl Rupp cputimesummary[cfile]=float(l[2]) 56521be42fSScott Kruger timelist.append(float(l[1])) 57d8e31410SScott Kruger elif l[0] not in summary: 58d8e31410SScott Kruger continue 59521be42fSScott Kruger else: 60521be42fSScott Kruger summary[l[0]] += int(l[1]) 61a2766241SScott Kruger 6291bc3e46SScott Kruger failstr=' '.join(summary['failures']) 63100ef3c7SJed Brown print("\n# -------------") 64100ef3c7SJed Brown print("# Summary ") 65100ef3c7SJed Brown print("# -------------") 661a3f486dSScott Kruger if failstr.strip(): print("# FAILED " + failstr) 678ccd5183SScott Kruger 688ccd5183SScott Kruger for t in "success failed todo skip".split(): 698ccd5183SScott Kruger percent=summary[t]/float(summary['total'])*100 70100ef3c7SJed Brown print("# %s %d/%d tests (%3.1f%%)" % (t, summary[t], summary['total'], percent)) 7132f4009dSScott Kruger print("#") 7232f4009dSScott Kruger if etime: 7332f4009dSScott Kruger print("# Wall clock time for tests: %s sec"% etime) 74*9b757ad5SKarl Rupp print("# Approximate CPU time (not incl. build time): %s sec"% summary['cputime']) 7591bc3e46SScott Kruger 76c687a870SSatish Balay if failstr.strip(): 7791bc3e46SScott Kruger fail_targets=( 7891bc3e46SScott Kruger re.sub('(?<=[0-9]_\w)_.*','', 7991bc3e46SScott Kruger re.sub('cmd-','', 80521be42fSScott Kruger re.sub('diff-','',failstr+' '))) 8191bc3e46SScott Kruger ) 82d8e31410SScott Kruger # Strip off characters from subtests 83d8e31410SScott Kruger fail_list=[] 84d8e31410SScott Kruger for failure in fail_targets.split(): 85d8e31410SScott Kruger if failure.count('-')>1: 86098d3641SScott Kruger fail_list.append('-'.join(failure.split('-')[:-1])) 87d8e31410SScott Kruger else: 88d8e31410SScott Kruger fail_list.append(failure) 89d8e31410SScott Kruger fail_list=list(set(fail_list)) 90d8e31410SScott Kruger fail_targets=' '.join(fail_list) 913054ff8cSScott Kruger 923054ff8cSScott Kruger #Make the message nice 933054ff8cSScott Kruger makefile="gmakefile.test" if inInstallDir() else "gmakefile" 943054ff8cSScott Kruger 9591bc3e46SScott Kruger print("#\n# To rerun failed tests: ") 963054ff8cSScott Kruger print("# "+make+" -f "+makefile+" test search='" + fail_targets.strip()+"'") 97bbf1c217SScott Kruger 98bbf1c217SScott Kruger if ntime>0: 99*9b757ad5SKarl Rupp print("#\n# Timing summary (actual test time / total CPU time): ") 100bbf1c217SScott Kruger timelist=list(set(timelist)) 101bbf1c217SScott Kruger timelist.sort(reverse=True) 102bbf1c217SScott Kruger nlim=(ntime if ntime<len(timelist) else len(timelist)) 103bbf1c217SScott Kruger # Do a double loop to sort in order 104bbf1c217SScott Kruger for timelimit in timelist[0:nlim]: 105bbf1c217SScott Kruger for cf in timesummary: 106bbf1c217SScott Kruger if timesummary[cf] == timelimit: 107*9b757ad5SKarl Rupp print("# %s: %.2f sec / %.2f sec" % (re.sub('.counts','',cf), timesummary[cf], cputimesummary[cf])) 108f04d3c48SAlp Dener os.chdir(startdir) 109f04d3c48SAlp Dener return 110bbf1c217SScott Kruger 111f04d3c48SAlp Denerdef generate_xml(directory): 112f04d3c48SAlp Dener startdir= os.getcwd() 113f04d3c48SAlp Dener try: 114f04d3c48SAlp Dener os.chdir(directory) 115f04d3c48SAlp Dener except OSError: 116f04d3c48SAlp Dener print('# No tests run') 117f04d3c48SAlp Dener return 118f04d3c48SAlp Dener # loop over *.counts files for all the problems tested in the test suite 119f04d3c48SAlp Dener testdata = {} 120f04d3c48SAlp Dener for cfile in glob.glob('*.counts'): 121f04d3c48SAlp Dener # first we get rid of the .counts extension, then we split the name in two 122f04d3c48SAlp Dener # to recover the problem name and the package it belongs to 123f04d3c48SAlp Dener fname = cfile.split('.')[0] 124f04d3c48SAlp Dener testname = fname.split('-') 125f04d3c48SAlp Dener probname = testname[1] 126f04d3c48SAlp Dener # we split the package into its subcomponents of PETSc module (e.g.: snes) 127f04d3c48SAlp Dener # and test type (e.g.: tutorial) 128f04d3c48SAlp Dener testname_list = testname[0].split('_') 129f04d3c48SAlp Dener pkgname = testname_list[0] 130f04d3c48SAlp Dener testtype = testname_list[-1] 131f04d3c48SAlp Dener # in order to correct assemble the folder path for problem outputs, we 132f04d3c48SAlp Dener # iterate over any possible subpackage names 133f04d3c48SAlp Dener probfolder = 'run%s'%probname 134f04d3c48SAlp Dener if probfolder.split('_')[1] == '1': 135f04d3c48SAlp Dener probfolder = probfolder.split('_')[0] 136f04d3c48SAlp Dener testname_short = testname_list[:-1] 137f04d3c48SAlp Dener prob_subdir = os.path.join(*testname_short) 138f04d3c48SAlp Dener # assemble the final full folder path for problem outputs and read the files 139f04d3c48SAlp Dener probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder) 140f04d3c48SAlp Dener try: 141f04d3c48SAlp Dener with open('%s/diff-%s.out'%(probdir, probfolder),'r') as probdiff: 142f04d3c48SAlp Dener difflines = probdiff.readlines() 143f04d3c48SAlp Dener except IOError: 144f04d3c48SAlp Dener difflines = [] 145f04d3c48SAlp Dener try: 146f04d3c48SAlp Dener with open('%s/%s.err'%(probdir, probfolder),'r') as probstderr: 147f04d3c48SAlp Dener stderrlines = probstderr.readlines() 148f04d3c48SAlp Dener except IOError: 149f04d3c48SAlp Dener stderrlines = [] 150f04d3c48SAlp Dener try: 151f04d3c48SAlp Dener with open('%s/%s.tmp'%(probdir, probname), 'r') as probstdout: 152f04d3c48SAlp Dener stdoutlines = probstdout.readlines() 153f04d3c48SAlp Dener except IOError: 154f04d3c48SAlp Dener stdoutlines = [] 155f04d3c48SAlp Dener # join the package, subpackage and problem type names into a "class" 156f04d3c48SAlp Dener classname = pkgname 157f04d3c48SAlp Dener for item in testname_list[1:]: 158f04d3c48SAlp Dener classname += '.%s'%item 159f04d3c48SAlp Dener # if this is the first time we see this package, initialize its dict 160f04d3c48SAlp Dener if pkgname not in testdata.keys(): 161f04d3c48SAlp Dener testdata[pkgname] = { 162f04d3c48SAlp Dener 'total':0, 163f04d3c48SAlp Dener 'success':0, 164f04d3c48SAlp Dener 'failed':0, 165f04d3c48SAlp Dener 'errors':0, 166f04d3c48SAlp Dener 'todo':0, 167f04d3c48SAlp Dener 'skip':0, 168f04d3c48SAlp Dener 'time':0, 169f04d3c48SAlp Dener 'problems':{} 170f04d3c48SAlp Dener } 171f04d3c48SAlp Dener # add the dict for the problem into the dict for the package 172f04d3c48SAlp Dener testdata[pkgname]['problems'][probname] = { 173f04d3c48SAlp Dener 'classname':classname, 174f04d3c48SAlp Dener 'time':0, 175f04d3c48SAlp Dener 'failed':False, 176f04d3c48SAlp Dener 'skipped':False, 177f04d3c48SAlp Dener 'diff':difflines, 178f04d3c48SAlp Dener 'stdout':stdoutlines, 179f04d3c48SAlp Dener 'stderr':stderrlines 180f04d3c48SAlp Dener } 181f04d3c48SAlp Dener # process the *.counts file and increment problem status trackers 182f04d3c48SAlp Dener if len(testdata[pkgname]['problems'][probname]['stderr'])>0: 183f04d3c48SAlp Dener testdata[pkgname]['errors'] += 1 184f04d3c48SAlp Dener with open(cfile, 'r') as f: 185f04d3c48SAlp Dener for line in f: 186f04d3c48SAlp Dener l = line.split() 187f04d3c48SAlp Dener if l[0] == 'failed': 188f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 189f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 190f04d3c48SAlp Dener elif l[0] == 'time': 191f04d3c48SAlp Dener if len(l)==1: continue 192f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = float(l[1]) 193f04d3c48SAlp Dener testdata[pkgname][l[0]] += float(l[1]) 194f04d3c48SAlp Dener elif l[0] == 'skip': 195f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 196f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 197f04d3c48SAlp Dener elif l[0] not in testdata[pkgname].keys(): 198f04d3c48SAlp Dener continue 199f04d3c48SAlp Dener else: 200f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 201f04d3c48SAlp Dener # at this point we have the complete test results in dictionary structures 202f04d3c48SAlp Dener # we can now write this information into a jUnit formatted XLM file 203f04d3c48SAlp Dener junit = open('../testresults.xml', 'w') 204f04d3c48SAlp Dener junit.write('<?xml version="1.0" ?>\n') 205f04d3c48SAlp Dener junit.write('<testsuites>\n') 206f04d3c48SAlp Dener for pkg in testdata.keys(): 207f04d3c48SAlp Dener testsuite = testdata[pkg] 208f04d3c48SAlp Dener junit.write(' <testsuite errors="%i" failures="%i" name="%s" tests="%i">\n'%( 209f04d3c48SAlp Dener testsuite['errors'], testsuite['failed'], pkg, testsuite['total'])) 210f04d3c48SAlp Dener for prob in testsuite['problems'].keys(): 211f04d3c48SAlp Dener p = testsuite['problems'][prob] 212f04d3c48SAlp Dener junit.write(' <testcase classname="%s" name="%s" time="%f">\n'%( 213f04d3c48SAlp Dener p['classname'], prob, p['time'])) 214f04d3c48SAlp Dener if p['skipped']: 215f04d3c48SAlp Dener # if we got here, the TAP output shows a skipped test 216f04d3c48SAlp Dener junit.write(' <skipped/>\n') 217f04d3c48SAlp Dener elif len(p['stderr'])>0: 218f04d3c48SAlp Dener # if we got here, the test crashed with an error 219f04d3c48SAlp Dener # we show the stderr output under <error> 220f04d3c48SAlp Dener junit.write(' <error type="crash">\n') 221f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 222f04d3c48SAlp Dener for line in p['stderr']: 223f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 224f04d3c48SAlp Dener junit.write("]]>") 225f04d3c48SAlp Dener junit.write(' </error>\n') 226f04d3c48SAlp Dener elif len(p['diff'])>0: 227f04d3c48SAlp Dener # if we got here, the test output did not match the stored output file 228f04d3c48SAlp Dener # we show the diff between new output and old output under <failure> 229f04d3c48SAlp Dener junit.write(' <failure type="output">\n') 230f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 231f04d3c48SAlp Dener for line in p['diff']: 232f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 233f04d3c48SAlp Dener junit.write("]]>") 234f04d3c48SAlp Dener junit.write(' </failure>\n') 235f04d3c48SAlp Dener elif len(p['stdout'])>0: 236f04d3c48SAlp Dener # if we got here, the test succeeded so we just show the stdout 237f04d3c48SAlp Dener # for manual sanity-checks 238f04d3c48SAlp Dener junit.write(' <system-out>\n') 239f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 240f04d3c48SAlp Dener count = 0 241f04d3c48SAlp Dener for line in p['stdout']: 242f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 243f04d3c48SAlp Dener count += 1 244f04d3c48SAlp Dener if count >= 1024: 245f04d3c48SAlp Dener break 246f04d3c48SAlp Dener junit.write("]]>") 247f04d3c48SAlp Dener junit.write(' </system-out>\n') 248f04d3c48SAlp Dener junit.write(' </testcase>\n') 249f04d3c48SAlp Dener junit.write(' </testsuite>\n') 250f04d3c48SAlp Dener junit.write('</testsuites>') 251f04d3c48SAlp Dener junit.close() 252f04d3c48SAlp Dener os.chdir(startdir) 2538ccd5183SScott Kruger return 2548ccd5183SScott Kruger 2558ccd5183SScott Krugerdef main(): 2568ccd5183SScott Kruger parser = optparse.OptionParser(usage="%prog [options]") 2578ccd5183SScott Kruger parser.add_option('-d', '--directory', dest='directory', 2588ccd5183SScott Kruger help='Directory containing results of petsc test system', 259e57812dcSJed Brown default=os.path.join(os.environ.get('PETSC_ARCH',''), 260bbf1c217SScott Kruger 'tests','counts')) 26132f4009dSScott Kruger parser.add_option('-e', '--elapsed_time', dest='elapsed_time', 26232f4009dSScott Kruger help='Report elapsed time in output', 26332f4009dSScott Kruger default=None) 2643054ff8cSScott Kruger parser.add_option('-m', '--make', dest='make', 2653054ff8cSScott Kruger help='make executable to report in summary', 2663054ff8cSScott Kruger default='make') 267bbf1c217SScott Kruger parser.add_option('-t', '--time', dest='time', 268bbf1c217SScott Kruger help='-t n: Report on the n number expensive jobs', 269bbf1c217SScott Kruger default=0) 2708ccd5183SScott Kruger options, args = parser.parse_args() 2718ccd5183SScott Kruger 2728ccd5183SScott Kruger # Process arguments 2738ccd5183SScott Kruger if len(args) > 0: 2748ccd5183SScott Kruger parser.print_usage() 2758ccd5183SScott Kruger return 2768ccd5183SScott Kruger 27732f4009dSScott Kruger summarize_results(options.directory,options.make,int(options.time),options.elapsed_time) 2788ccd5183SScott Kruger 279f04d3c48SAlp Dener generate_xml(options.directory) 280f04d3c48SAlp Dener 2818ccd5183SScott Krugerif __name__ == "__main__": 2828ccd5183SScott Kruger main() 283