xref: /petsc/config/report_tests.py (revision f04d3c48a9aea784b8a49f8e291242d28671f55a)
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