xref: /petsc/config/report_tests.py (revision 4d8a629dabb5c493d7a18e2d2fa45aadf24438ef)
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,
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]))
105f04d3c48SAlp Dener  os.chdir(startdir)
106f04d3c48SAlp Dener  return
107bbf1c217SScott Kruger
108f04d3c48SAlp Denerdef generate_xml(directory):
109f04d3c48SAlp Dener    startdir= os.getcwd()
110f04d3c48SAlp Dener    try:
111f04d3c48SAlp Dener        os.chdir(directory)
112f04d3c48SAlp Dener    except OSError:
113f04d3c48SAlp Dener        print('# No tests run')
114f04d3c48SAlp Dener        return
115f04d3c48SAlp Dener    # loop over *.counts files for all the problems tested in the test suite
116f04d3c48SAlp Dener    testdata = {}
117f04d3c48SAlp Dener    for cfile in glob.glob('*.counts'):
118f04d3c48SAlp Dener        # first we get rid of the .counts extension, then we split the name in two
119f04d3c48SAlp Dener        # to recover the problem name and the package it belongs to
120f04d3c48SAlp Dener        fname = cfile.split('.')[0]
121f04d3c48SAlp Dener        testname = fname.split('-')
122*4d8a629dSAlp Dener        probname = ''
123*4d8a629dSAlp Dener        for i in range(1,len(testname)):
124*4d8a629dSAlp Dener            probname += testname[i]
125f04d3c48SAlp Dener        # we split the package into its subcomponents of PETSc module (e.g.: snes)
126f04d3c48SAlp Dener        # and test type (e.g.: tutorial)
127f04d3c48SAlp Dener        testname_list = testname[0].split('_')
128f04d3c48SAlp Dener        pkgname = testname_list[0]
129f04d3c48SAlp Dener        testtype = testname_list[-1]
130f04d3c48SAlp Dener        # in order to correct assemble the folder path for problem outputs, we
131*4d8a629dSAlp Dener        # iterate over any possible subpackage names and test suffixes
132f04d3c48SAlp Dener        testname_short = testname_list[:-1]
133f04d3c48SAlp Dener        prob_subdir = os.path.join(*testname_short)
134*4d8a629dSAlp Dener        probfolder = 'run%s'%probname
135f04d3c48SAlp Dener        probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder)
136*4d8a629dSAlp Dener        if not os.path.exists(probdir):
137*4d8a629dSAlp Dener            probfolder = probfolder.split('_')[0]
138*4d8a629dSAlp Dener            probdir = os.path.join('..', prob_subdir, 'examples', testtype, probfolder)
139*4d8a629dSAlp Dener        # assemble the final full folder path for problem outputs and read the files
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