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, 399b757ad5SKarl Rupp 'time':0, 'cputime':0} 40bbf1c217SScott Kruger timesummary={} 419b757ad5SKarl 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]) 539b757ad5SKarl Rupp summary['cputime'] += float(l[2]) 54521be42fSScott Kruger timesummary[cfile]=float(l[1]) 559b757ad5SKarl 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) 749b757ad5SKarl 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('cmd-','', 795e361860SScott Kruger re.sub('diff-','',failstr+' ')) 8091bc3e46SScott Kruger ) 815e361860SScott Kruger print(fail_targets) 82d8e31410SScott Kruger # Strip off characters from subtests 83d8e31410SScott Kruger fail_list=[] 84d8e31410SScott Kruger for failure in fail_targets.split(): 855e361860SScott Kruger if failure.split('-')[1].count('_')>1: 865e361860SScott Kruger froot=failure.split('-')[0] 875e361860SScott Kruger flabel='_'.join(failure.split('-')[1].split('_')[0:1]) 885e361860SScott Kruger fail_list.append(froot+'-'+flabel+'_*') 895e361860SScott Kruger elif failure.count('-')>1: 90098d3641SScott Kruger fail_list.append('-'.join(failure.split('-')[:-1])) 91d8e31410SScott Kruger else: 92d8e31410SScott Kruger fail_list.append(failure) 93d8e31410SScott Kruger fail_list=list(set(fail_list)) 94d8e31410SScott Kruger fail_targets=' '.join(fail_list) 953054ff8cSScott Kruger 963054ff8cSScott Kruger #Make the message nice 973054ff8cSScott Kruger makefile="gmakefile.test" if inInstallDir() else "gmakefile" 983054ff8cSScott Kruger 9991bc3e46SScott Kruger print("#\n# To rerun failed tests: ") 1005e361860SScott Kruger print("# "+make+" -f "+makefile+" test globsearch='" + fail_targets.strip()+"'") 101bbf1c217SScott Kruger 102bbf1c217SScott Kruger if ntime>0: 1039b757ad5SKarl Rupp print("#\n# Timing summary (actual test time / total CPU time): ") 104bbf1c217SScott Kruger timelist=list(set(timelist)) 105bbf1c217SScott Kruger timelist.sort(reverse=True) 106bbf1c217SScott Kruger nlim=(ntime if ntime<len(timelist) else len(timelist)) 107bbf1c217SScott Kruger # Do a double loop to sort in order 108bbf1c217SScott Kruger for timelimit in timelist[0:nlim]: 109bbf1c217SScott Kruger for cf in timesummary: 110bbf1c217SScott Kruger if timesummary[cf] == timelimit: 1119b757ad5SKarl Rupp print("# %s: %.2f sec / %.2f sec" % (re.sub('.counts','',cf), timesummary[cf], cputimesummary[cf])) 112f04d3c48SAlp Dener os.chdir(startdir) 113f04d3c48SAlp Dener return 114bbf1c217SScott Kruger 115*6e5deea7SScott Krugerdef get_test_data(directory): 116*6e5deea7SScott Kruger """ 117*6e5deea7SScott Kruger Create dictionary structure with test data 118*6e5deea7SScott Kruger """ 119f04d3c48SAlp Dener startdir= os.getcwd() 120f04d3c48SAlp Dener try: 121f04d3c48SAlp Dener os.chdir(directory) 122f04d3c48SAlp Dener except OSError: 123f04d3c48SAlp Dener print('# No tests run') 124f04d3c48SAlp Dener return 125f04d3c48SAlp Dener # loop over *.counts files for all the problems tested in the test suite 126f04d3c48SAlp Dener testdata = {} 127f04d3c48SAlp Dener for cfile in glob.glob('*.counts'): 128f04d3c48SAlp Dener # first we get rid of the .counts extension, then we split the name in two 129f04d3c48SAlp Dener # to recover the problem name and the package it belongs to 130f04d3c48SAlp Dener fname = cfile.split('.')[0] 131f04d3c48SAlp Dener testname = fname.split('-') 1324d8a629dSAlp Dener probname = '' 1334d8a629dSAlp Dener for i in range(1,len(testname)): 1344d8a629dSAlp Dener probname += testname[i] 135f04d3c48SAlp Dener # we split the package into its subcomponents of PETSc module (e.g.: snes) 136f04d3c48SAlp Dener # and test type (e.g.: tutorial) 137f04d3c48SAlp Dener testname_list = testname[0].split('_') 138f04d3c48SAlp Dener pkgname = testname_list[0] 139f04d3c48SAlp Dener testtype = testname_list[-1] 140f04d3c48SAlp Dener # in order to correct assemble the folder path for problem outputs, we 1414d8a629dSAlp Dener # iterate over any possible subpackage names and test suffixes 142f04d3c48SAlp Dener testname_short = testname_list[:-1] 143140150c5SJakub Kruzik prob_subdir = os.path.join('', *testname_short) 1444d8a629dSAlp Dener probfolder = 'run%s'%probname 145f04d3c48SAlp Dener probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder) 1464d8a629dSAlp Dener if not os.path.exists(probdir): 1474d8a629dSAlp Dener probfolder = probfolder.split('_')[0] 1484d8a629dSAlp Dener probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder) 1494d8a629dSAlp Dener # assemble the final full folder path for problem outputs and read the files 150f04d3c48SAlp Dener try: 151f04d3c48SAlp Dener with open('%s/diff-%s.out'%(probdir, probfolder),'r') as probdiff: 152f04d3c48SAlp Dener difflines = probdiff.readlines() 153f04d3c48SAlp Dener except IOError: 154f04d3c48SAlp Dener difflines = [] 155f04d3c48SAlp Dener try: 156f04d3c48SAlp Dener with open('%s/%s.err'%(probdir, probfolder),'r') as probstderr: 157f04d3c48SAlp Dener stderrlines = probstderr.readlines() 158f04d3c48SAlp Dener except IOError: 159f04d3c48SAlp Dener stderrlines = [] 160f04d3c48SAlp Dener try: 161f04d3c48SAlp Dener with open('%s/%s.tmp'%(probdir, probname), 'r') as probstdout: 162f04d3c48SAlp Dener stdoutlines = probstdout.readlines() 163f04d3c48SAlp Dener except IOError: 164f04d3c48SAlp Dener stdoutlines = [] 165f04d3c48SAlp Dener # join the package, subpackage and problem type names into a "class" 166f04d3c48SAlp Dener classname = pkgname 167f04d3c48SAlp Dener for item in testname_list[1:]: 168f04d3c48SAlp Dener classname += '.%s'%item 169f04d3c48SAlp Dener # if this is the first time we see this package, initialize its dict 170f04d3c48SAlp Dener if pkgname not in testdata.keys(): 171f04d3c48SAlp Dener testdata[pkgname] = { 172f04d3c48SAlp Dener 'total':0, 173f04d3c48SAlp Dener 'success':0, 174f04d3c48SAlp Dener 'failed':0, 175f04d3c48SAlp Dener 'errors':0, 176f04d3c48SAlp Dener 'todo':0, 177f04d3c48SAlp Dener 'skip':0, 178f04d3c48SAlp Dener 'time':0, 179f04d3c48SAlp Dener 'problems':{} 180f04d3c48SAlp Dener } 181f04d3c48SAlp Dener # add the dict for the problem into the dict for the package 182f04d3c48SAlp Dener testdata[pkgname]['problems'][probname] = { 183f04d3c48SAlp Dener 'classname':classname, 184f04d3c48SAlp Dener 'time':0, 185f04d3c48SAlp Dener 'failed':False, 186f04d3c48SAlp Dener 'skipped':False, 187f04d3c48SAlp Dener 'diff':difflines, 188f04d3c48SAlp Dener 'stdout':stdoutlines, 189f04d3c48SAlp Dener 'stderr':stderrlines 190f04d3c48SAlp Dener } 191f04d3c48SAlp Dener # process the *.counts file and increment problem status trackers 192f04d3c48SAlp Dener if len(testdata[pkgname]['problems'][probname]['stderr'])>0: 193f04d3c48SAlp Dener testdata[pkgname]['errors'] += 1 194f04d3c48SAlp Dener with open(cfile, 'r') as f: 195f04d3c48SAlp Dener for line in f: 196f04d3c48SAlp Dener l = line.split() 197f04d3c48SAlp Dener if l[0] == 'failed': 198f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 199f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 200f04d3c48SAlp Dener elif l[0] == 'time': 201f04d3c48SAlp Dener if len(l)==1: continue 202f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = float(l[1]) 203f04d3c48SAlp Dener testdata[pkgname][l[0]] += float(l[1]) 204f04d3c48SAlp Dener elif l[0] == 'skip': 205f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 206f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 207f04d3c48SAlp Dener elif l[0] not in testdata[pkgname].keys(): 208f04d3c48SAlp Dener continue 209f04d3c48SAlp Dener else: 210f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 211*6e5deea7SScott Kruger os.chdir(startdir) # Keep function in good state 212*6e5deea7SScott Kruger return testdata 213*6e5deea7SScott Kruger 214*6e5deea7SScott Krugerdef show_fail(testdata): 215*6e5deea7SScott Kruger """ Show the failures and commands to run them 216*6e5deea7SScott Kruger """ 217*6e5deea7SScott Kruger for pkg in testdata.keys(): 218*6e5deea7SScott Kruger testsuite = testdata[pkg] 219*6e5deea7SScott Kruger for prob in testsuite['problems'].keys(): 220*6e5deea7SScott Kruger p = testsuite['problems'][prob] 221*6e5deea7SScott Kruger if p['skipped']: 222*6e5deea7SScott Kruger # if we got here, the TAP output shows a skipped test 223*6e5deea7SScott Kruger pass 224*6e5deea7SScott Kruger elif len(p['stderr'])>0: 225*6e5deea7SScott Kruger # if we got here, the test crashed with an error 226*6e5deea7SScott Kruger # we show the stderr output under <error> 227*6e5deea7SScott Kruger print(p) 228*6e5deea7SScott Kruger pass 229*6e5deea7SScott Kruger elif len(p['diff'])>0: 230*6e5deea7SScott Kruger # if we got here, the test output did not match the stored output file 231*6e5deea7SScott Kruger # we show the diff between new output and old output under <failure> 232*6e5deea7SScott Kruger print(p) 233*6e5deea7SScott Kruger pass 234*6e5deea7SScott Kruger return 235*6e5deea7SScott Kruger 236*6e5deea7SScott Krugerdef generate_xml(testdata,directory): 237*6e5deea7SScott Kruger """ write testdata information into a jUnit formatted XLM file 238*6e5deea7SScott Kruger """ 239*6e5deea7SScott Kruger startdir= os.getcwd() 240*6e5deea7SScott Kruger try: 241*6e5deea7SScott Kruger os.chdir(directory) 242*6e5deea7SScott Kruger except OSError: 243*6e5deea7SScott Kruger print('# No tests run') 244*6e5deea7SScott Kruger return 245f04d3c48SAlp Dener junit = open('../testresults.xml', 'w') 246f04d3c48SAlp Dener junit.write('<?xml version="1.0" ?>\n') 247f04d3c48SAlp Dener junit.write('<testsuites>\n') 248f04d3c48SAlp Dener for pkg in testdata.keys(): 249f04d3c48SAlp Dener testsuite = testdata[pkg] 250f04d3c48SAlp Dener junit.write(' <testsuite errors="%i" failures="%i" name="%s" tests="%i">\n'%( 251f04d3c48SAlp Dener testsuite['errors'], testsuite['failed'], pkg, testsuite['total'])) 252f04d3c48SAlp Dener for prob in testsuite['problems'].keys(): 253f04d3c48SAlp Dener p = testsuite['problems'][prob] 254f04d3c48SAlp Dener junit.write(' <testcase classname="%s" name="%s" time="%f">\n'%( 255f04d3c48SAlp Dener p['classname'], prob, p['time'])) 256f04d3c48SAlp Dener if p['skipped']: 257f04d3c48SAlp Dener # if we got here, the TAP output shows a skipped test 258f04d3c48SAlp Dener junit.write(' <skipped/>\n') 259f04d3c48SAlp Dener elif len(p['stderr'])>0: 260f04d3c48SAlp Dener # if we got here, the test crashed with an error 261f04d3c48SAlp Dener # we show the stderr output under <error> 262f04d3c48SAlp Dener junit.write(' <error type="crash">\n') 263f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 264f04d3c48SAlp Dener for line in p['stderr']: 265f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 266f04d3c48SAlp Dener junit.write("]]>") 267f04d3c48SAlp Dener junit.write(' </error>\n') 268f04d3c48SAlp Dener elif len(p['diff'])>0: 269f04d3c48SAlp Dener # if we got here, the test output did not match the stored output file 270f04d3c48SAlp Dener # we show the diff between new output and old output under <failure> 271f04d3c48SAlp Dener junit.write(' <failure type="output">\n') 272f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 273f04d3c48SAlp Dener for line in p['diff']: 274f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 275f04d3c48SAlp Dener junit.write("]]>") 276f04d3c48SAlp Dener junit.write(' </failure>\n') 277f04d3c48SAlp Dener elif len(p['stdout'])>0: 278f04d3c48SAlp Dener # if we got here, the test succeeded so we just show the stdout 279f04d3c48SAlp Dener # for manual sanity-checks 280f04d3c48SAlp Dener junit.write(' <system-out>\n') 281f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 282f04d3c48SAlp Dener count = 0 283f04d3c48SAlp Dener for line in p['stdout']: 284f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 285f04d3c48SAlp Dener count += 1 286f04d3c48SAlp Dener if count >= 1024: 287f04d3c48SAlp Dener break 288f04d3c48SAlp Dener junit.write("]]>") 289f04d3c48SAlp Dener junit.write(' </system-out>\n') 290f04d3c48SAlp Dener junit.write(' </testcase>\n') 291f04d3c48SAlp Dener junit.write(' </testsuite>\n') 292f04d3c48SAlp Dener junit.write('</testsuites>') 293f04d3c48SAlp Dener junit.close() 294f04d3c48SAlp Dener os.chdir(startdir) 2958ccd5183SScott Kruger return 2968ccd5183SScott Kruger 2978ccd5183SScott Krugerdef main(): 2988ccd5183SScott Kruger parser = optparse.OptionParser(usage="%prog [options]") 2998ccd5183SScott Kruger parser.add_option('-d', '--directory', dest='directory', 3008ccd5183SScott Kruger help='Directory containing results of petsc test system', 301e57812dcSJed Brown default=os.path.join(os.environ.get('PETSC_ARCH',''), 302bbf1c217SScott Kruger 'tests','counts')) 30332f4009dSScott Kruger parser.add_option('-e', '--elapsed_time', dest='elapsed_time', 30432f4009dSScott Kruger help='Report elapsed time in output', 30532f4009dSScott Kruger default=None) 3063054ff8cSScott Kruger parser.add_option('-m', '--make', dest='make', 3073054ff8cSScott Kruger help='make executable to report in summary', 3083054ff8cSScott Kruger default='make') 309bbf1c217SScott Kruger parser.add_option('-t', '--time', dest='time', 310bbf1c217SScott Kruger help='-t n: Report on the n number expensive jobs', 311bbf1c217SScott Kruger default=0) 312*6e5deea7SScott Kruger parser.add_option('-f', '--fail', dest='show_fail', action="store_true", 313*6e5deea7SScott Kruger help='Show the failed tests and how to run them') 3148ccd5183SScott Kruger options, args = parser.parse_args() 3158ccd5183SScott Kruger 3168ccd5183SScott Kruger # Process arguments 3178ccd5183SScott Kruger if len(args) > 0: 3188ccd5183SScott Kruger parser.print_usage() 3198ccd5183SScott Kruger return 3208ccd5183SScott Kruger 3218ccd5183SScott Kruger 322*6e5deea7SScott Kruger if not options.show_fail: 323*6e5deea7SScott Kruger summarize_results(options.directory,options.make,int(options.time),options.elapsed_time) 324*6e5deea7SScott Kruger testresults=get_test_data(options.directory) 325*6e5deea7SScott Kruger 326*6e5deea7SScott Kruger if options.show_fail: 327*6e5deea7SScott Kruger show_fail(testresults) 328*6e5deea7SScott Kruger else: 329*6e5deea7SScott Kruger generate_xml(testresults, options.directory) 330f04d3c48SAlp Dener 3318ccd5183SScott Krugerif __name__ == "__main__": 3328ccd5183SScott Kruger main() 333