xref: /petsc/config/report_tests.py (revision b711b6a47cf6a73fe5c40b4bac16db67e7bbac1d)
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
1156e5deea7SScott Krugerdef get_test_data(directory):
1166e5deea7SScott Kruger    """
1176e5deea7SScott Kruger    Create dictionary structure with test data
1186e5deea7SScott 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)
149*b711b6a4SScott Kruger        probfullpath=os.path.normpath(os.path.join(directory,probdir))
1504d8a629dSAlp Dener        # assemble the final full folder path for problem outputs and read the files
151f04d3c48SAlp Dener        try:
152f04d3c48SAlp Dener            with open('%s/diff-%s.out'%(probdir, probfolder),'r') as probdiff:
153f04d3c48SAlp Dener                difflines = probdiff.readlines()
154f04d3c48SAlp Dener        except IOError:
155f04d3c48SAlp Dener            difflines = []
156f04d3c48SAlp Dener        try:
157f04d3c48SAlp Dener            with open('%s/%s.err'%(probdir, probfolder),'r') as probstderr:
158f04d3c48SAlp Dener                stderrlines = probstderr.readlines()
159f04d3c48SAlp Dener        except IOError:
160f04d3c48SAlp Dener            stderrlines = []
161f04d3c48SAlp Dener        try:
162f04d3c48SAlp Dener            with open('%s/%s.tmp'%(probdir, probname), 'r') as probstdout:
163f04d3c48SAlp Dener                stdoutlines = probstdout.readlines()
164f04d3c48SAlp Dener        except IOError:
165f04d3c48SAlp Dener            stdoutlines = []
166f04d3c48SAlp Dener        # join the package, subpackage and problem type names into a "class"
167f04d3c48SAlp Dener        classname = pkgname
168f04d3c48SAlp Dener        for item in testname_list[1:]:
169f04d3c48SAlp Dener            classname += '.%s'%item
170f04d3c48SAlp Dener        # if this is the first time we see this package, initialize its dict
171f04d3c48SAlp Dener        if pkgname not in testdata.keys():
172f04d3c48SAlp Dener            testdata[pkgname] = {
173f04d3c48SAlp Dener                'total':0,
174f04d3c48SAlp Dener                'success':0,
175f04d3c48SAlp Dener                'failed':0,
176f04d3c48SAlp Dener                'errors':0,
177f04d3c48SAlp Dener                'todo':0,
178f04d3c48SAlp Dener                'skip':0,
179f04d3c48SAlp Dener                'time':0,
180f04d3c48SAlp Dener                'problems':{}
181f04d3c48SAlp Dener            }
182f04d3c48SAlp Dener        # add the dict for the problem into the dict for the package
183f04d3c48SAlp Dener        testdata[pkgname]['problems'][probname] = {
184f04d3c48SAlp Dener            'classname':classname,
185f04d3c48SAlp Dener            'time':0,
186f04d3c48SAlp Dener            'failed':False,
187f04d3c48SAlp Dener            'skipped':False,
188f04d3c48SAlp Dener            'diff':difflines,
189f04d3c48SAlp Dener            'stdout':stdoutlines,
190*b711b6a4SScott Kruger            'stderr':stderrlines,
191*b711b6a4SScott Kruger            'probdir':probfullpath,
192*b711b6a4SScott Kruger            'fullname':fname
193f04d3c48SAlp Dener        }
194f04d3c48SAlp Dener        # process the *.counts file and increment problem status trackers
195f04d3c48SAlp Dener        if len(testdata[pkgname]['problems'][probname]['stderr'])>0:
196f04d3c48SAlp Dener            testdata[pkgname]['errors'] += 1
197f04d3c48SAlp Dener        with open(cfile, 'r') as f:
198f04d3c48SAlp Dener            for line in f:
199f04d3c48SAlp Dener                l = line.split()
200f04d3c48SAlp Dener                if l[0] == 'failed':
201f04d3c48SAlp Dener                    testdata[pkgname]['problems'][probname][l[0]] = True
202f04d3c48SAlp Dener                    testdata[pkgname][l[0]] += 1
203f04d3c48SAlp Dener                elif l[0] == 'time':
204f04d3c48SAlp Dener                    if len(l)==1: continue
205f04d3c48SAlp Dener                    testdata[pkgname]['problems'][probname][l[0]] = float(l[1])
206f04d3c48SAlp Dener                    testdata[pkgname][l[0]] += float(l[1])
207f04d3c48SAlp Dener                elif l[0] == 'skip':
208f04d3c48SAlp Dener                    testdata[pkgname]['problems'][probname][l[0]] = True
209f04d3c48SAlp Dener                    testdata[pkgname][l[0]] += 1
210f04d3c48SAlp Dener                elif l[0] not in testdata[pkgname].keys():
211f04d3c48SAlp Dener                    continue
212f04d3c48SAlp Dener                else:
213f04d3c48SAlp Dener                    testdata[pkgname][l[0]] += 1
2146e5deea7SScott Kruger    os.chdir(startdir)  # Keep function in good state
2156e5deea7SScott Kruger    return testdata
2166e5deea7SScott Kruger
2176e5deea7SScott Krugerdef show_fail(testdata):
2186e5deea7SScott Kruger    """ Show the failures and commands to run them
2196e5deea7SScott Kruger    """
2206e5deea7SScott Kruger    for pkg in testdata.keys():
2216e5deea7SScott Kruger        testsuite = testdata[pkg]
2226e5deea7SScott Kruger        for prob in testsuite['problems'].keys():
2236e5deea7SScott Kruger            p = testsuite['problems'][prob]
224*b711b6a4SScott Kruger            cdbase='cd '+p['probdir']+' && '
2256e5deea7SScott Kruger            if p['skipped']:
2266e5deea7SScott Kruger                # if we got here, the TAP output shows a skipped test
2276e5deea7SScott Kruger                pass
2286e5deea7SScott Kruger            elif len(p['stderr'])>0:
2296e5deea7SScott Kruger                # if we got here, the test crashed with an error
2306e5deea7SScott Kruger                # we show the stderr output under <error>
231*b711b6a4SScott Kruger                shbase=os.path.join(p['probdir'], p['fullname'])
232*b711b6a4SScott Kruger                shfile=shbase+".sh"
233*b711b6a4SScott Kruger                if not os.path.exists(shfile):
234*b711b6a4SScott Kruger                    shfile=glob.glob(shbase+"*")[0]
235*b711b6a4SScott Kruger                with open(shfile, 'r') as sh:
236*b711b6a4SScott Kruger                    cmd = sh.read()
237*b711b6a4SScott Kruger                print(p['fullname']+': '+cdbase+cmd.split('>')[0])
2386e5deea7SScott Kruger            elif len(p['diff'])>0:
2396e5deea7SScott Kruger                # if we got here, the test output did not match the stored output file
2406e5deea7SScott Kruger                # we show the diff between new output and old output under <failure>
241*b711b6a4SScott Kruger                shbase=os.path.join(p['probdir'], 'diff-'+p['fullname'])
242*b711b6a4SScott Kruger                shfile=shbase+".sh"
243*b711b6a4SScott Kruger                if not os.path.exists(shfile):
244*b711b6a4SScott Kruger                    shfile=glob.glob(shbase+"*")[0]
245*b711b6a4SScott Kruger                with open(shfile, 'r') as sh:
246*b711b6a4SScott Kruger                    cmd = sh.read()
247*b711b6a4SScott Kruger                print(p['fullname']+': '+cdbase+cmd.split('>')[0])
2486e5deea7SScott Kruger                pass
2496e5deea7SScott Kruger    return
2506e5deea7SScott Kruger
2516e5deea7SScott Krugerdef generate_xml(testdata,directory):
2526e5deea7SScott Kruger    """ write testdata information into a jUnit formatted XLM file
2536e5deea7SScott Kruger    """
2546e5deea7SScott Kruger    startdir= os.getcwd()
2556e5deea7SScott Kruger    try:
2566e5deea7SScott Kruger        os.chdir(directory)
2576e5deea7SScott Kruger    except OSError:
2586e5deea7SScott Kruger        print('# No tests run')
2596e5deea7SScott Kruger        return
260f04d3c48SAlp Dener    junit = open('../testresults.xml', 'w')
261f04d3c48SAlp Dener    junit.write('<?xml version="1.0" ?>\n')
262f04d3c48SAlp Dener    junit.write('<testsuites>\n')
263f04d3c48SAlp Dener    for pkg in testdata.keys():
264f04d3c48SAlp Dener        testsuite = testdata[pkg]
265f04d3c48SAlp Dener        junit.write('  <testsuite errors="%i" failures="%i" name="%s" tests="%i">\n'%(
266f04d3c48SAlp Dener            testsuite['errors'], testsuite['failed'], pkg, testsuite['total']))
267f04d3c48SAlp Dener        for prob in testsuite['problems'].keys():
268f04d3c48SAlp Dener            p = testsuite['problems'][prob]
269f04d3c48SAlp Dener            junit.write('    <testcase classname="%s" name="%s" time="%f">\n'%(
270f04d3c48SAlp Dener                p['classname'], prob, p['time']))
271f04d3c48SAlp Dener            if p['skipped']:
272f04d3c48SAlp Dener                # if we got here, the TAP output shows a skipped test
273f04d3c48SAlp Dener                junit.write('      <skipped/>\n')
274f04d3c48SAlp Dener            elif len(p['stderr'])>0:
275f04d3c48SAlp Dener                # if we got here, the test crashed with an error
276f04d3c48SAlp Dener                # we show the stderr output under <error>
277f04d3c48SAlp Dener                junit.write('      <error type="crash">\n')
278f04d3c48SAlp Dener                junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace
279f04d3c48SAlp Dener                for line in p['stderr']:
280f04d3c48SAlp Dener                    junit.write("%s\n"%line.rstrip())
281f04d3c48SAlp Dener                junit.write("]]>")
282f04d3c48SAlp Dener                junit.write('      </error>\n')
283f04d3c48SAlp Dener            elif len(p['diff'])>0:
284f04d3c48SAlp Dener                # if we got here, the test output did not match the stored output file
285f04d3c48SAlp Dener                # we show the diff between new output and old output under <failure>
286f04d3c48SAlp Dener                junit.write('      <failure type="output">\n')
287f04d3c48SAlp Dener                junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace
288f04d3c48SAlp Dener                for line in p['diff']:
289f04d3c48SAlp Dener                    junit.write("%s\n"%line.rstrip())
290f04d3c48SAlp Dener                junit.write("]]>")
291f04d3c48SAlp Dener                junit.write('      </failure>\n')
292f04d3c48SAlp Dener            elif len(p['stdout'])>0:
293f04d3c48SAlp Dener                # if we got here, the test succeeded so we just show the stdout
294f04d3c48SAlp Dener                # for manual sanity-checks
295f04d3c48SAlp Dener                junit.write('      <system-out>\n')
296f04d3c48SAlp Dener                junit.write("<![CDATA[\n") # CDATA is necessary to preserve whitespace
297f04d3c48SAlp Dener                count = 0
298f04d3c48SAlp Dener                for line in p['stdout']:
299f04d3c48SAlp Dener                    junit.write("%s\n"%line.rstrip())
300f04d3c48SAlp Dener                    count += 1
301f04d3c48SAlp Dener                    if count >= 1024:
302f04d3c48SAlp Dener                        break
303f04d3c48SAlp Dener                junit.write("]]>")
304f04d3c48SAlp Dener                junit.write('      </system-out>\n')
305f04d3c48SAlp Dener            junit.write('    </testcase>\n')
306f04d3c48SAlp Dener        junit.write('  </testsuite>\n')
307f04d3c48SAlp Dener    junit.write('</testsuites>')
308f04d3c48SAlp Dener    junit.close()
309f04d3c48SAlp Dener    os.chdir(startdir)
3108ccd5183SScott Kruger    return
3118ccd5183SScott Kruger
3128ccd5183SScott Krugerdef main():
3138ccd5183SScott Kruger    parser = optparse.OptionParser(usage="%prog [options]")
3148ccd5183SScott Kruger    parser.add_option('-d', '--directory', dest='directory',
3158ccd5183SScott Kruger                      help='Directory containing results of petsc test system',
316e57812dcSJed Brown                      default=os.path.join(os.environ.get('PETSC_ARCH',''),
317bbf1c217SScott Kruger                                           'tests','counts'))
31832f4009dSScott Kruger    parser.add_option('-e', '--elapsed_time', dest='elapsed_time',
31932f4009dSScott Kruger                      help='Report elapsed time in output',
32032f4009dSScott Kruger                      default=None)
3213054ff8cSScott Kruger    parser.add_option('-m', '--make', dest='make',
3223054ff8cSScott Kruger                      help='make executable to report in summary',
3233054ff8cSScott Kruger                      default='make')
324bbf1c217SScott Kruger    parser.add_option('-t', '--time', dest='time',
325bbf1c217SScott Kruger                      help='-t n: Report on the n number expensive jobs',
326bbf1c217SScott Kruger                      default=0)
3276e5deea7SScott Kruger    parser.add_option('-f', '--fail', dest='show_fail', action="store_true",
3286e5deea7SScott Kruger                      help='Show the failed tests and how to run them')
3298ccd5183SScott Kruger    options, args = parser.parse_args()
3308ccd5183SScott Kruger
3318ccd5183SScott Kruger    # Process arguments
3328ccd5183SScott Kruger    if len(args) > 0:
3338ccd5183SScott Kruger      parser.print_usage()
3348ccd5183SScott Kruger      return
3358ccd5183SScott Kruger
3368ccd5183SScott Kruger
3376e5deea7SScott Kruger    if not options.show_fail:
3386e5deea7SScott Kruger      summarize_results(options.directory,options.make,int(options.time),options.elapsed_time)
3396e5deea7SScott Kruger    testresults=get_test_data(options.directory)
3406e5deea7SScott Kruger
3416e5deea7SScott Kruger    if options.show_fail:
3426e5deea7SScott Kruger      show_fail(testresults)
3436e5deea7SScott Kruger    else:
3446e5deea7SScott Kruger      generate_xml(testresults, options.directory)
345f04d3c48SAlp Dener
3468ccd5183SScott Krugerif __name__ == "__main__":
3478ccd5183SScott Kruger        main()
348