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''' 32*f04d3c48SAlp 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, 39bbf1c217SScott Kruger 'time':0} 40bbf1c217SScott Kruger timesummary={} 41bbf1c217SScott Kruger timelist=[] 428ccd5183SScott Kruger for cfile in glob.glob('*.counts'): 43100ef3c7SJed Brown with open(cfile, 'r') as f: 44100ef3c7SJed Brown for line in f: 45100ef3c7SJed Brown l = line.split() 46521be42fSScott Kruger if l[0] == 'failures': 47d8e31410SScott Kruger if len(l)>1: 480bffd254SScott Kruger summary[l[0]] += l[1:] 49521be42fSScott Kruger elif l[0] == 'time': 50521be42fSScott Kruger if len(l)==1: continue 51521be42fSScott Kruger summary[l[0]] += float(l[1]) 52521be42fSScott Kruger timesummary[cfile]=float(l[1]) 53521be42fSScott Kruger timelist.append(float(l[1])) 54d8e31410SScott Kruger elif l[0] not in summary: 55d8e31410SScott Kruger continue 56521be42fSScott Kruger else: 57521be42fSScott Kruger summary[l[0]] += int(l[1]) 58a2766241SScott Kruger 5991bc3e46SScott Kruger failstr=' '.join(summary['failures']) 60100ef3c7SJed Brown print("\n# -------------") 61100ef3c7SJed Brown print("# Summary ") 62100ef3c7SJed Brown print("# -------------") 631a3f486dSScott Kruger if failstr.strip(): print("# FAILED " + failstr) 648ccd5183SScott Kruger 658ccd5183SScott Kruger for t in "success failed todo skip".split(): 668ccd5183SScott Kruger percent=summary[t]/float(summary['total'])*100 67100ef3c7SJed Brown print("# %s %d/%d tests (%3.1f%%)" % (t, summary[t], summary['total'], percent)) 6832f4009dSScott Kruger print("#") 6932f4009dSScott Kruger if etime: 7032f4009dSScott Kruger print("# Wall clock time for tests: %s sec"% etime) 7132f4009dSScott Kruger print("# Approximate CPU time (not incl. build time): %s sec"% summary['time']) 7291bc3e46SScott Kruger 73c687a870SSatish Balay if failstr.strip(): 7491bc3e46SScott Kruger fail_targets=( 7591bc3e46SScott Kruger re.sub('(?<=[0-9]_\w)_.*','', 7691bc3e46SScott Kruger re.sub('cmd-','', 77521be42fSScott Kruger re.sub('diff-','',failstr+' '))) 7891bc3e46SScott Kruger ) 79d8e31410SScott Kruger # Strip off characters from subtests 80d8e31410SScott Kruger fail_list=[] 81d8e31410SScott Kruger for failure in fail_targets.split(): 82d8e31410SScott Kruger if failure.count('-')>1: 83098d3641SScott Kruger fail_list.append('-'.join(failure.split('-')[:-1])) 84d8e31410SScott Kruger else: 85d8e31410SScott Kruger fail_list.append(failure) 86d8e31410SScott Kruger fail_list=list(set(fail_list)) 87d8e31410SScott Kruger fail_targets=' '.join(fail_list) 883054ff8cSScott Kruger 893054ff8cSScott Kruger #Make the message nice 903054ff8cSScott Kruger makefile="gmakefile.test" if inInstallDir() else "gmakefile" 913054ff8cSScott Kruger 9291bc3e46SScott Kruger print("#\n# To rerun failed tests: ") 933054ff8cSScott Kruger print("# "+make+" -f "+makefile+" test search='" + fail_targets.strip()+"'") 94bbf1c217SScott Kruger 95bbf1c217SScott Kruger if ntime>0: 96d8e31410SScott Kruger print("#\n# Timing summary: ") 97bbf1c217SScott Kruger timelist=list(set(timelist)) 98bbf1c217SScott Kruger timelist.sort(reverse=True) 99bbf1c217SScott Kruger nlim=(ntime if ntime<len(timelist) else len(timelist)) 100bbf1c217SScott Kruger # Do a double loop to sort in order 101bbf1c217SScott Kruger for timelimit in timelist[0:nlim]: 102bbf1c217SScott Kruger for cf in timesummary: 103bbf1c217SScott Kruger if timesummary[cf] == timelimit: 104521be42fSScott Kruger print("# %s: %.2f sec" % (re.sub('.counts','',cf), timesummary[cf])) 105*f04d3c48SAlp Dener os.chdir(startdir) 106*f04d3c48SAlp Dener return 107bbf1c217SScott Kruger 108*f04d3c48SAlp Denerdef generate_xml(directory): 109*f04d3c48SAlp Dener startdir= os.getcwd() 110*f04d3c48SAlp Dener try: 111*f04d3c48SAlp Dener os.chdir(directory) 112*f04d3c48SAlp Dener except OSError: 113*f04d3c48SAlp Dener print('# No tests run') 114*f04d3c48SAlp Dener return 115*f04d3c48SAlp Dener # loop over *.counts files for all the problems tested in the test suite 116*f04d3c48SAlp Dener testdata = {} 117*f04d3c48SAlp Dener for cfile in glob.glob('*.counts'): 118*f04d3c48SAlp Dener # first we get rid of the .counts extension, then we split the name in two 119*f04d3c48SAlp Dener # to recover the problem name and the package it belongs to 120*f04d3c48SAlp Dener fname = cfile.split('.')[0] 121*f04d3c48SAlp Dener testname = fname.split('-') 122*f04d3c48SAlp Dener probname = testname[1] 123*f04d3c48SAlp Dener # we split the package into its subcomponents of PETSc module (e.g.: snes) 124*f04d3c48SAlp Dener # and test type (e.g.: tutorial) 125*f04d3c48SAlp Dener testname_list = testname[0].split('_') 126*f04d3c48SAlp Dener pkgname = testname_list[0] 127*f04d3c48SAlp Dener testtype = testname_list[-1] 128*f04d3c48SAlp Dener # in order to correct assemble the folder path for problem outputs, we 129*f04d3c48SAlp Dener # iterate over any possible subpackage names 130*f04d3c48SAlp Dener probfolder = 'run%s'%probname 131*f04d3c48SAlp Dener if probfolder.split('_')[1] == '1': 132*f04d3c48SAlp Dener probfolder = probfolder.split('_')[0] 133*f04d3c48SAlp Dener testname_short = testname_list[:-1] 134*f04d3c48SAlp Dener prob_subdir = os.path.join(*testname_short) 135*f04d3c48SAlp Dener # assemble the final full folder path for problem outputs and read the files 136*f04d3c48SAlp Dener probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder) 137*f04d3c48SAlp Dener try: 138*f04d3c48SAlp Dener with open('%s/diff-%s.out'%(probdir, probfolder),'r') as probdiff: 139*f04d3c48SAlp Dener difflines = probdiff.readlines() 140*f04d3c48SAlp Dener except IOError: 141*f04d3c48SAlp Dener difflines = [] 142*f04d3c48SAlp Dener try: 143*f04d3c48SAlp Dener with open('%s/%s.err'%(probdir, probfolder),'r') as probstderr: 144*f04d3c48SAlp Dener stderrlines = probstderr.readlines() 145*f04d3c48SAlp Dener except IOError: 146*f04d3c48SAlp Dener stderrlines = [] 147*f04d3c48SAlp Dener try: 148*f04d3c48SAlp Dener with open('%s/%s.tmp'%(probdir, probname), 'r') as probstdout: 149*f04d3c48SAlp Dener stdoutlines = probstdout.readlines() 150*f04d3c48SAlp Dener except IOError: 151*f04d3c48SAlp Dener stdoutlines = [] 152*f04d3c48SAlp Dener # join the package, subpackage and problem type names into a "class" 153*f04d3c48SAlp Dener classname = pkgname 154*f04d3c48SAlp Dener for item in testname_list[1:]: 155*f04d3c48SAlp Dener classname += '.%s'%item 156*f04d3c48SAlp Dener # if this is the first time we see this package, initialize its dict 157*f04d3c48SAlp Dener if pkgname not in testdata.keys(): 158*f04d3c48SAlp Dener testdata[pkgname] = { 159*f04d3c48SAlp Dener 'total':0, 160*f04d3c48SAlp Dener 'success':0, 161*f04d3c48SAlp Dener 'failed':0, 162*f04d3c48SAlp Dener 'errors':0, 163*f04d3c48SAlp Dener 'todo':0, 164*f04d3c48SAlp Dener 'skip':0, 165*f04d3c48SAlp Dener 'time':0, 166*f04d3c48SAlp Dener 'problems':{} 167*f04d3c48SAlp Dener } 168*f04d3c48SAlp Dener # add the dict for the problem into the dict for the package 169*f04d3c48SAlp Dener testdata[pkgname]['problems'][probname] = { 170*f04d3c48SAlp Dener 'classname':classname, 171*f04d3c48SAlp Dener 'time':0, 172*f04d3c48SAlp Dener 'failed':False, 173*f04d3c48SAlp Dener 'skipped':False, 174*f04d3c48SAlp Dener 'diff':difflines, 175*f04d3c48SAlp Dener 'stdout':stdoutlines, 176*f04d3c48SAlp Dener 'stderr':stderrlines 177*f04d3c48SAlp Dener } 178*f04d3c48SAlp Dener # process the *.counts file and increment problem status trackers 179*f04d3c48SAlp Dener if len(testdata[pkgname]['problems'][probname]['stderr'])>0: 180*f04d3c48SAlp Dener testdata[pkgname]['errors'] += 1 181*f04d3c48SAlp Dener with open(cfile, 'r') as f: 182*f04d3c48SAlp Dener for line in f: 183*f04d3c48SAlp Dener l = line.split() 184*f04d3c48SAlp Dener if l[0] == 'failed': 185*f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 186*f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 187*f04d3c48SAlp Dener elif l[0] == 'time': 188*f04d3c48SAlp Dener if len(l)==1: continue 189*f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = float(l[1]) 190*f04d3c48SAlp Dener testdata[pkgname][l[0]] += float(l[1]) 191*f04d3c48SAlp Dener elif l[0] == 'skip': 192*f04d3c48SAlp Dener testdata[pkgname]['problems'][probname][l[0]] = True 193*f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 194*f04d3c48SAlp Dener elif l[0] not in testdata[pkgname].keys(): 195*f04d3c48SAlp Dener continue 196*f04d3c48SAlp Dener else: 197*f04d3c48SAlp Dener testdata[pkgname][l[0]] += 1 198*f04d3c48SAlp Dener # at this point we have the complete test results in dictionary structures 199*f04d3c48SAlp Dener # we can now write this information into a jUnit formatted XLM file 200*f04d3c48SAlp Dener junit = open('../testresults.xml', 'w') 201*f04d3c48SAlp Dener junit.write('<?xml version="1.0" ?>\n') 202*f04d3c48SAlp Dener junit.write('<testsuites>\n') 203*f04d3c48SAlp Dener for pkg in testdata.keys(): 204*f04d3c48SAlp Dener testsuite = testdata[pkg] 205*f04d3c48SAlp Dener junit.write(' <testsuite errors="%i" failures="%i" name="%s" tests="%i">\n'%( 206*f04d3c48SAlp Dener testsuite['errors'], testsuite['failed'], pkg, testsuite['total'])) 207*f04d3c48SAlp Dener for prob in testsuite['problems'].keys(): 208*f04d3c48SAlp Dener p = testsuite['problems'][prob] 209*f04d3c48SAlp Dener junit.write(' <testcase classname="%s" name="%s" time="%f">\n'%( 210*f04d3c48SAlp Dener p['classname'], prob, p['time'])) 211*f04d3c48SAlp Dener if p['skipped']: 212*f04d3c48SAlp Dener # if we got here, the TAP output shows a skipped test 213*f04d3c48SAlp Dener junit.write(' <skipped/>\n') 214*f04d3c48SAlp Dener elif len(p['stderr'])>0: 215*f04d3c48SAlp Dener # if we got here, the test crashed with an error 216*f04d3c48SAlp Dener # we show the stderr output under <error> 217*f04d3c48SAlp Dener junit.write(' <error type="crash">\n') 218*f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 219*f04d3c48SAlp Dener for line in p['stderr']: 220*f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 221*f04d3c48SAlp Dener junit.write("]]>") 222*f04d3c48SAlp Dener junit.write(' </error>\n') 223*f04d3c48SAlp Dener elif len(p['diff'])>0: 224*f04d3c48SAlp Dener # if we got here, the test output did not match the stored output file 225*f04d3c48SAlp Dener # we show the diff between new output and old output under <failure> 226*f04d3c48SAlp Dener junit.write(' <failure type="output">\n') 227*f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 228*f04d3c48SAlp Dener for line in p['diff']: 229*f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 230*f04d3c48SAlp Dener junit.write("]]>") 231*f04d3c48SAlp Dener junit.write(' </failure>\n') 232*f04d3c48SAlp Dener elif len(p['stdout'])>0: 233*f04d3c48SAlp Dener # if we got here, the test succeeded so we just show the stdout 234*f04d3c48SAlp Dener # for manual sanity-checks 235*f04d3c48SAlp Dener junit.write(' <system-out>\n') 236*f04d3c48SAlp Dener junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace 237*f04d3c48SAlp Dener count = 0 238*f04d3c48SAlp Dener for line in p['stdout']: 239*f04d3c48SAlp Dener junit.write("%s\n"%line.rstrip()) 240*f04d3c48SAlp Dener count += 1 241*f04d3c48SAlp Dener if count >= 1024: 242*f04d3c48SAlp Dener break 243*f04d3c48SAlp Dener junit.write("]]>") 244*f04d3c48SAlp Dener junit.write(' </system-out>\n') 245*f04d3c48SAlp Dener junit.write(' </testcase>\n') 246*f04d3c48SAlp Dener junit.write(' </testsuite>\n') 247*f04d3c48SAlp Dener junit.write('</testsuites>') 248*f04d3c48SAlp Dener junit.close() 249*f04d3c48SAlp Dener os.chdir(startdir) 2508ccd5183SScott Kruger return 2518ccd5183SScott Kruger 2528ccd5183SScott Krugerdef main(): 2538ccd5183SScott Kruger parser = optparse.OptionParser(usage="%prog [options]") 2548ccd5183SScott Kruger parser.add_option('-d', '--directory', dest='directory', 2558ccd5183SScott Kruger help='Directory containing results of petsc test system', 256e57812dcSJed Brown default=os.path.join(os.environ.get('PETSC_ARCH',''), 257bbf1c217SScott Kruger 'tests','counts')) 25832f4009dSScott Kruger parser.add_option('-e', '--elapsed_time', dest='elapsed_time', 25932f4009dSScott Kruger help='Report elapsed time in output', 26032f4009dSScott Kruger default=None) 2613054ff8cSScott Kruger parser.add_option('-m', '--make', dest='make', 2623054ff8cSScott Kruger help='make executable to report in summary', 2633054ff8cSScott Kruger default='make') 264bbf1c217SScott Kruger parser.add_option('-t', '--time', dest='time', 265bbf1c217SScott Kruger help='-t n: Report on the n number expensive jobs', 266bbf1c217SScott Kruger default=0) 2678ccd5183SScott Kruger options, args = parser.parse_args() 2688ccd5183SScott Kruger 2698ccd5183SScott Kruger # Process arguments 2708ccd5183SScott Kruger if len(args) > 0: 2718ccd5183SScott Kruger parser.print_usage() 2728ccd5183SScott Kruger return 2738ccd5183SScott Kruger 27432f4009dSScott Kruger summarize_results(options.directory,options.make,int(options.time),options.elapsed_time) 2758ccd5183SScott Kruger 276*f04d3c48SAlp Dener generate_xml(options.directory) 277*f04d3c48SAlp Dener 2788ccd5183SScott Krugerif __name__ == "__main__": 2798ccd5183SScott Kruger main() 280