Home | History | Annotate | Line # | Download | only in contrib
dg-extract-results.py revision 1.1.1.5
      1      1.1  mrg #!/usr/bin/python
      2      1.1  mrg #
      3      1.1  mrg # Copyright (C) 2014 Free Software Foundation, Inc.
      4      1.1  mrg #
      5      1.1  mrg # This script is free software; you can redistribute it and/or modify
      6      1.1  mrg # it under the terms of the GNU General Public License as published by
      7      1.1  mrg # the Free Software Foundation; either version 3, or (at your option)
      8      1.1  mrg # any later version.
      9      1.1  mrg 
     10      1.1  mrg import sys
     11      1.1  mrg import getopt
     12      1.1  mrg import re
     13      1.1  mrg import io
     14      1.1  mrg from datetime import datetime
     15      1.1  mrg from operator import attrgetter
     16      1.1  mrg 
     17      1.1  mrg # True if unrecognised lines should cause a fatal error.  Might want to turn
     18      1.1  mrg # this on by default later.
     19      1.1  mrg strict = False
     20      1.1  mrg 
     21      1.1  mrg # True if the order of .log segments should match the .sum file, false if
     22      1.1  mrg # they should keep the original order.
     23      1.1  mrg sort_logs = True
     24      1.1  mrg 
     25      1.1  mrg # A version of open() that is safe against whatever binary output
     26      1.1  mrg # might be added to the log.
     27      1.1  mrg def safe_open (filename):
     28      1.1  mrg     if sys.version_info >= (3, 0):
     29      1.1  mrg         return open (filename, 'r', errors = 'surrogateescape')
     30      1.1  mrg     return open (filename, 'r')
     31      1.1  mrg 
     32      1.1  mrg # Force stdout to handle escape sequences from a safe_open file.
     33      1.1  mrg if sys.version_info >= (3, 0):
     34      1.1  mrg     sys.stdout = io.TextIOWrapper (sys.stdout.buffer,
     35      1.1  mrg                                    errors = 'surrogateescape')
     36      1.1  mrg 
     37      1.1  mrg class Named:
     38      1.1  mrg     def __init__ (self, name):
     39      1.1  mrg         self.name = name
     40      1.1  mrg 
     41      1.1  mrg class ToolRun (Named):
     42      1.1  mrg     def __init__ (self, name):
     43      1.1  mrg         Named.__init__ (self, name)
     44      1.1  mrg         # The variations run for this tool, mapped by --target_board name.
     45      1.1  mrg         self.variations = dict()
     46      1.1  mrg 
     47      1.1  mrg     # Return the VariationRun for variation NAME.
     48      1.1  mrg     def get_variation (self, name):
     49      1.1  mrg         if name not in self.variations:
     50      1.1  mrg             self.variations[name] = VariationRun (name)
     51      1.1  mrg         return self.variations[name]
     52      1.1  mrg 
     53      1.1  mrg class VariationRun (Named):
     54      1.1  mrg     def __init__ (self, name):
     55      1.1  mrg         Named.__init__ (self, name)
     56      1.1  mrg         # A segment of text before the harness runs start, describing which
     57      1.1  mrg         # baseboard files were loaded for the target.
     58      1.1  mrg         self.header = None
     59      1.1  mrg         # The harnesses run for this variation, mapped by filename.
     60      1.1  mrg         self.harnesses = dict()
     61      1.1  mrg         # A list giving the number of times each type of result has
     62      1.1  mrg         # been seen.
     63      1.1  mrg         self.counts = []
     64      1.1  mrg 
     65      1.1  mrg     # Return the HarnessRun for harness NAME.
     66      1.1  mrg     def get_harness (self, name):
     67      1.1  mrg         if name not in self.harnesses:
     68      1.1  mrg             self.harnesses[name] = HarnessRun (name)
     69      1.1  mrg         return self.harnesses[name]
     70      1.1  mrg 
     71      1.1  mrg class HarnessRun (Named):
     72      1.1  mrg     def __init__ (self, name):
     73      1.1  mrg         Named.__init__ (self, name)
     74      1.1  mrg         # Segments of text that make up the harness run, mapped by a test-based
     75      1.1  mrg         # key that can be used to order them.
     76      1.1  mrg         self.segments = dict()
     77      1.1  mrg         # Segments of text that make up the harness run but which have
     78      1.1  mrg         # no recognized test results.  These are typically harnesses that
     79      1.1  mrg         # are completely skipped for the target.
     80      1.1  mrg         self.empty = []
     81      1.1  mrg         # A list of results.  Each entry is a pair in which the first element
     82      1.1  mrg         # is a unique sorting key and in which the second is the full
     83      1.1  mrg         # PASS/FAIL line.
     84      1.1  mrg         self.results = []
     85      1.1  mrg 
     86      1.1  mrg     # Add a segment of text to the harness run.  If the segment includes
     87      1.1  mrg     # test results, KEY is an example of one of them, and can be used to
     88      1.1  mrg     # combine the individual segments in order.  If the segment has no
     89      1.1  mrg     # test results (e.g. because the harness doesn't do anything for the
     90      1.1  mrg     # current configuration) then KEY is None instead.  In that case
     91      1.1  mrg     # just collect the segments in the order that we see them.
     92      1.1  mrg     def add_segment (self, key, segment):
     93      1.1  mrg         if key:
     94      1.1  mrg             assert key not in self.segments
     95      1.1  mrg             self.segments[key] = segment
     96      1.1  mrg         else:
     97      1.1  mrg             self.empty.append (segment)
     98      1.1  mrg 
     99      1.1  mrg class Segment:
    100      1.1  mrg     def __init__ (self, filename, start):
    101      1.1  mrg         self.filename = filename
    102      1.1  mrg         self.start = start
    103      1.1  mrg         self.lines = 0
    104      1.1  mrg 
    105      1.1  mrg class Prog:
    106      1.1  mrg     def __init__ (self):
    107      1.1  mrg         # The variations specified on the command line.
    108      1.1  mrg         self.variations = []
    109      1.1  mrg         # The variations seen in the input files.
    110      1.1  mrg         self.known_variations = set()
    111      1.1  mrg         # The tools specified on the command line.
    112      1.1  mrg         self.tools = []
    113      1.1  mrg         # Whether to create .sum rather than .log output.
    114      1.1  mrg         self.do_sum = True
    115      1.1  mrg         # Regexps used while parsing.
    116      1.1  mrg         self.test_run_re = re.compile (r'^Test Run By (\S+) on (.*)$')
    117      1.1  mrg         self.tool_re = re.compile (r'^\t\t=== (.*) tests ===$')
    118      1.1  mrg         self.result_re = re.compile (r'^(PASS|XPASS|FAIL|XFAIL|UNRESOLVED'
    119      1.1  mrg                                      r'|WARNING|ERROR|UNSUPPORTED|UNTESTED'
    120  1.1.1.5  mrg                                      r'|KFAIL|KPASS|PATH|DUPLICATE):\s*(.+)')
    121      1.1  mrg         self.completed_re = re.compile (r'.* completed at (.*)')
    122      1.1  mrg         # Pieces of text to write at the head of the output.
    123      1.1  mrg         # start_line is a pair in which the first element is a datetime
    124      1.1  mrg         # and in which the second is the associated 'Test Run By' line.
    125      1.1  mrg         self.start_line = None
    126      1.1  mrg         self.native_line = ''
    127      1.1  mrg         self.target_line = ''
    128      1.1  mrg         self.host_line = ''
    129      1.1  mrg         self.acats_premable = ''
    130      1.1  mrg         # Pieces of text to write at the end of the output.
    131      1.1  mrg         # end_line is like start_line but for the 'runtest completed' line.
    132      1.1  mrg         self.acats_failures = []
    133      1.1  mrg         self.version_output = ''
    134      1.1  mrg         self.end_line = None
    135      1.1  mrg         # Known summary types.
    136      1.1  mrg         self.count_names = [
    137  1.1.1.2  mrg             '# of DejaGnu errors\t\t',
    138      1.1  mrg             '# of expected passes\t\t',
    139      1.1  mrg             '# of unexpected failures\t',
    140      1.1  mrg             '# of unexpected successes\t',
    141      1.1  mrg             '# of expected failures\t\t',
    142      1.1  mrg             '# of unknown successes\t\t',
    143      1.1  mrg             '# of known failures\t\t',
    144      1.1  mrg             '# of untested testcases\t\t',
    145      1.1  mrg             '# of unresolved testcases\t',
    146  1.1.1.5  mrg             '# of unsupported tests\t\t',
    147  1.1.1.5  mrg             '# of paths in test names\t',
    148  1.1.1.5  mrg             '# of duplicate test names\t'
    149      1.1  mrg         ]
    150      1.1  mrg         self.runs = dict()
    151      1.1  mrg 
    152      1.1  mrg     def usage (self):
    153      1.1  mrg         name = sys.argv[0]
    154      1.1  mrg         sys.stderr.write ('Usage: ' + name
    155      1.1  mrg                           + ''' [-t tool] [-l variant-list] [-L] log-or-sum-file ...
    156      1.1  mrg 
    157      1.1  mrg     tool           The tool (e.g. g++, libffi) for which to create a
    158      1.1  mrg                    new test summary file.  If not specified then output
    159      1.1  mrg                    is created for all tools.
    160      1.1  mrg     variant-list   One or more test variant names.  If the list is
    161      1.1  mrg                    not specified then one is constructed from all
    162      1.1  mrg                    variants in the files for <tool>.
    163      1.1  mrg     sum-file       A test summary file with the format of those
    164      1.1  mrg                    created by runtest from DejaGnu.
    165      1.1  mrg     If -L is used, merge *.log files instead of *.sum.  In this
    166      1.1  mrg     mode the exact order of lines may not be preserved, just different
    167      1.1  mrg     Running *.exp chunks should be in correct order.
    168      1.1  mrg ''')
    169      1.1  mrg         sys.exit (1)
    170      1.1  mrg 
    171      1.1  mrg     def fatal (self, what, string):
    172      1.1  mrg         if not what:
    173      1.1  mrg             what = sys.argv[0]
    174      1.1  mrg         sys.stderr.write (what + ': ' + string + '\n')
    175      1.1  mrg         sys.exit (1)
    176      1.1  mrg 
    177      1.1  mrg     # Parse the command-line arguments.
    178      1.1  mrg     def parse_cmdline (self):
    179      1.1  mrg         try:
    180      1.1  mrg             (options, self.files) = getopt.getopt (sys.argv[1:], 'l:t:L')
    181      1.1  mrg             if len (self.files) == 0:
    182      1.1  mrg                 self.usage()
    183      1.1  mrg             for (option, value) in options:
    184      1.1  mrg                 if option == '-l':
    185      1.1  mrg                     self.variations.append (value)
    186      1.1  mrg                 elif option == '-t':
    187      1.1  mrg                     self.tools.append (value)
    188      1.1  mrg                 else:
    189      1.1  mrg                     self.do_sum = False
    190      1.1  mrg         except getopt.GetoptError as e:
    191      1.1  mrg             self.fatal (None, e.msg)
    192      1.1  mrg 
    193      1.1  mrg     # Try to parse time string TIME, returning an arbitrary time on failure.
    194      1.1  mrg     # Getting this right is just a nice-to-have so failures should be silent.
    195      1.1  mrg     def parse_time (self, time):
    196      1.1  mrg         try:
    197      1.1  mrg             return datetime.strptime (time, '%c')
    198      1.1  mrg         except ValueError:
    199      1.1  mrg             return datetime.now()
    200      1.1  mrg 
    201      1.1  mrg     # Parse an integer and abort on failure.
    202      1.1  mrg     def parse_int (self, filename, value):
    203      1.1  mrg         try:
    204      1.1  mrg             return int (value)
    205      1.1  mrg         except ValueError:
    206      1.1  mrg             self.fatal (filename, 'expected an integer, got: ' + value)
    207      1.1  mrg 
    208      1.1  mrg     # Return a list that represents no test results.
    209      1.1  mrg     def zero_counts (self):
    210      1.1  mrg         return [0 for x in self.count_names]
    211      1.1  mrg 
    212      1.1  mrg     # Return the ToolRun for tool NAME.
    213      1.1  mrg     def get_tool (self, name):
    214      1.1  mrg         if name not in self.runs:
    215      1.1  mrg             self.runs[name] = ToolRun (name)
    216      1.1  mrg         return self.runs[name]
    217      1.1  mrg 
    218      1.1  mrg     # Add the result counts in list FROMC to TOC.
    219      1.1  mrg     def accumulate_counts (self, toc, fromc):
    220      1.1  mrg         for i in range (len (self.count_names)):
    221      1.1  mrg             toc[i] += fromc[i]
    222      1.1  mrg 
    223      1.1  mrg     # Parse the list of variations after 'Schedule of variations:'.
    224      1.1  mrg     # Return the number seen.
    225      1.1  mrg     def parse_variations (self, filename, file):
    226      1.1  mrg         num_variations = 0
    227      1.1  mrg         while True:
    228      1.1  mrg             line = file.readline()
    229      1.1  mrg             if line == '':
    230      1.1  mrg                 self.fatal (filename, 'could not parse variation list')
    231      1.1  mrg             if line == '\n':
    232      1.1  mrg                 break
    233      1.1  mrg             self.known_variations.add (line.strip())
    234      1.1  mrg             num_variations += 1
    235      1.1  mrg         return num_variations
    236      1.1  mrg 
    237      1.1  mrg     # Parse from the first line after 'Running target ...' to the end
    238      1.1  mrg     # of the run's summary.
    239      1.1  mrg     def parse_run (self, filename, file, tool, variation, num_variations):
    240      1.1  mrg         header = None
    241      1.1  mrg         harness = None
    242      1.1  mrg         segment = None
    243      1.1  mrg         final_using = 0
    244  1.1.1.3  mrg         has_warning = 0
    245      1.1  mrg 
    246      1.1  mrg         # If this is the first run for this variation, add any text before
    247      1.1  mrg         # the first harness to the header.
    248      1.1  mrg         if not variation.header:
    249      1.1  mrg             segment = Segment (filename, file.tell())
    250      1.1  mrg             variation.header = segment
    251      1.1  mrg 
    252  1.1.1.2  mrg         # Parse the rest of the summary (the '# of ' lines).
    253  1.1.1.2  mrg         if len (variation.counts) == 0:
    254  1.1.1.2  mrg             variation.counts = self.zero_counts()
    255  1.1.1.2  mrg 
    256      1.1  mrg         # Parse up until the first line of the summary.
    257      1.1  mrg         if num_variations == 1:
    258      1.1  mrg             end = '\t\t=== ' + tool.name + ' Summary ===\n'
    259      1.1  mrg         else:
    260      1.1  mrg             end = ('\t\t=== ' + tool.name + ' Summary for '
    261      1.1  mrg                    + variation.name + ' ===\n')
    262      1.1  mrg         while True:
    263      1.1  mrg             line = file.readline()
    264      1.1  mrg             if line == '':
    265      1.1  mrg                 self.fatal (filename, 'no recognised summary line')
    266      1.1  mrg             if line == end:
    267      1.1  mrg                 break
    268      1.1  mrg 
    269      1.1  mrg             # Look for the start of a new harness.
    270      1.1  mrg             if line.startswith ('Running ') and line.endswith (' ...\n'):
    271      1.1  mrg                 # Close off the current harness segment, if any.
    272      1.1  mrg                 if harness:
    273      1.1  mrg                     segment.lines -= final_using
    274      1.1  mrg                     harness.add_segment (first_key, segment)
    275      1.1  mrg                 name = line[len ('Running '):-len(' ...\n')]
    276      1.1  mrg                 harness = variation.get_harness (name)
    277      1.1  mrg                 segment = Segment (filename, file.tell())
    278      1.1  mrg                 first_key = None
    279      1.1  mrg                 final_using = 0
    280      1.1  mrg                 continue
    281      1.1  mrg 
    282      1.1  mrg             # Record test results.  Associate the first test result with
    283      1.1  mrg             # the harness segment, so that if a run for a particular harness
    284      1.1  mrg             # has been split up, we can reassemble the individual segments
    285      1.1  mrg             # in a sensible order.
    286      1.1  mrg             #
    287      1.1  mrg             # dejagnu sometimes issues warnings about the testing environment
    288      1.1  mrg             # before running any tests.  Treat them as part of the header
    289      1.1  mrg             # rather than as a test result.
    290      1.1  mrg             match = self.result_re.match (line)
    291      1.1  mrg             if match and (harness or not line.startswith ('WARNING:')):
    292      1.1  mrg                 if not harness:
    293      1.1  mrg                     self.fatal (filename, 'saw test result before harness name')
    294      1.1  mrg                 name = match.group (2)
    295      1.1  mrg                 # Ugly hack to get the right order for gfortran.
    296      1.1  mrg                 if name.startswith ('gfortran.dg/g77/'):
    297      1.1  mrg                     name = 'h' + name
    298  1.1.1.3  mrg                 # If we have a time out warning, make sure it appears
    299  1.1.1.3  mrg                 # before the following testcase diagnostic: we insert
    300  1.1.1.3  mrg                 # the testname before 'program' so that sort faces a
    301  1.1.1.3  mrg                 # list of testnames.
    302  1.1.1.3  mrg                 if line.startswith ('WARNING: program timed out'):
    303  1.1.1.3  mrg                   has_warning = 1
    304  1.1.1.3  mrg                 else:
    305  1.1.1.3  mrg                   if has_warning == 1:
    306  1.1.1.3  mrg                       key = (name, len (harness.results))
    307  1.1.1.3  mrg                       myline = 'WARNING: %s program timed out.\n' % name
    308  1.1.1.3  mrg                       harness.results.append ((key, myline))
    309  1.1.1.3  mrg                       has_warning = 0
    310  1.1.1.3  mrg                   key = (name, len (harness.results))
    311  1.1.1.3  mrg                   harness.results.append ((key, line))
    312  1.1.1.3  mrg                   if not first_key and sort_logs:
    313  1.1.1.3  mrg                       first_key = key
    314  1.1.1.2  mrg                 if line.startswith ('ERROR: (DejaGnu)'):
    315  1.1.1.2  mrg                     for i in range (len (self.count_names)):
    316  1.1.1.2  mrg                         if 'DejaGnu errors' in self.count_names[i]:
    317  1.1.1.2  mrg                             variation.counts[i] += 1
    318  1.1.1.2  mrg                             break
    319      1.1  mrg 
    320      1.1  mrg             # 'Using ...' lines are only interesting in a header.  Splitting
    321      1.1  mrg             # the test up into parallel runs leads to more 'Using ...' lines
    322      1.1  mrg             # than there would be in a single log.
    323      1.1  mrg             if line.startswith ('Using '):
    324      1.1  mrg                 final_using += 1
    325      1.1  mrg             else:
    326      1.1  mrg                 final_using = 0
    327      1.1  mrg 
    328      1.1  mrg             # Add other text to the current segment, if any.
    329      1.1  mrg             if segment:
    330      1.1  mrg                 segment.lines += 1
    331      1.1  mrg 
    332      1.1  mrg         # Close off the final harness segment, if any.
    333      1.1  mrg         if harness:
    334      1.1  mrg             segment.lines -= final_using
    335      1.1  mrg             harness.add_segment (first_key, segment)
    336      1.1  mrg 
    337      1.1  mrg         while True:
    338      1.1  mrg             before = file.tell()
    339      1.1  mrg             line = file.readline()
    340      1.1  mrg             if line == '':
    341      1.1  mrg                 break
    342      1.1  mrg             if line == '\n':
    343      1.1  mrg                 continue
    344      1.1  mrg             if not line.startswith ('# '):
    345      1.1  mrg                 file.seek (before)
    346      1.1  mrg                 break
    347      1.1  mrg             found = False
    348      1.1  mrg             for i in range (len (self.count_names)):
    349      1.1  mrg                 if line.startswith (self.count_names[i]):
    350      1.1  mrg                     count = line[len (self.count_names[i]):-1].strip()
    351      1.1  mrg                     variation.counts[i] += self.parse_int (filename, count)
    352      1.1  mrg                     found = True
    353      1.1  mrg                     break
    354      1.1  mrg             if not found:
    355      1.1  mrg                 self.fatal (filename, 'unknown test result: ' + line[:-1])
    356      1.1  mrg 
    357      1.1  mrg     # Parse an acats run, which uses a different format from dejagnu.
    358      1.1  mrg     # We have just skipped over '=== acats configuration ==='.
    359      1.1  mrg     def parse_acats_run (self, filename, file):
    360      1.1  mrg         # Parse the preamble, which describes the configuration and logs
    361      1.1  mrg         # the creation of support files.
    362      1.1  mrg         record = (self.acats_premable == '')
    363      1.1  mrg         if record:
    364      1.1  mrg             self.acats_premable = '\t\t=== acats configuration ===\n'
    365      1.1  mrg         while True:
    366      1.1  mrg             line = file.readline()
    367      1.1  mrg             if line == '':
    368      1.1  mrg                 self.fatal (filename, 'could not parse acats preamble')
    369      1.1  mrg             if line == '\t\t=== acats tests ===\n':
    370      1.1  mrg                 break
    371      1.1  mrg             if record:
    372      1.1  mrg                 self.acats_premable += line
    373      1.1  mrg 
    374      1.1  mrg         # Parse the test results themselves, using a dummy variation name.
    375      1.1  mrg         tool = self.get_tool ('acats')
    376      1.1  mrg         variation = tool.get_variation ('none')
    377      1.1  mrg         self.parse_run (filename, file, tool, variation, 1)
    378      1.1  mrg 
    379      1.1  mrg         # Parse the failure list.
    380      1.1  mrg         while True:
    381      1.1  mrg             before = file.tell()
    382      1.1  mrg             line = file.readline()
    383      1.1  mrg             if line.startswith ('*** FAILURES: '):
    384      1.1  mrg                 self.acats_failures.append (line[len ('*** FAILURES: '):-1])
    385      1.1  mrg                 continue
    386      1.1  mrg             file.seek (before)
    387      1.1  mrg             break
    388      1.1  mrg 
    389      1.1  mrg     # Parse the final summary at the end of a log in order to capture
    390      1.1  mrg     # the version output that follows it.
    391      1.1  mrg     def parse_final_summary (self, filename, file):
    392      1.1  mrg         record = (self.version_output == '')
    393      1.1  mrg         while True:
    394      1.1  mrg             line = file.readline()
    395      1.1  mrg             if line == '':
    396      1.1  mrg                 break
    397      1.1  mrg             if line.startswith ('# of '):
    398      1.1  mrg                 continue
    399      1.1  mrg             if record:
    400      1.1  mrg                 self.version_output += line
    401      1.1  mrg             if line == '\n':
    402      1.1  mrg                 break
    403      1.1  mrg 
    404      1.1  mrg     # Parse a .log or .sum file.
    405      1.1  mrg     def parse_file (self, filename, file):
    406      1.1  mrg         tool = None
    407      1.1  mrg         target = None
    408      1.1  mrg         num_variations = 1
    409      1.1  mrg         while True:
    410      1.1  mrg             line = file.readline()
    411      1.1  mrg             if line == '':
    412      1.1  mrg                 return
    413      1.1  mrg 
    414      1.1  mrg             # Parse the list of variations, which comes before the test
    415      1.1  mrg             # runs themselves.
    416      1.1  mrg             if line.startswith ('Schedule of variations:'):
    417      1.1  mrg                 num_variations = self.parse_variations (filename, file)
    418      1.1  mrg                 continue
    419      1.1  mrg 
    420      1.1  mrg             # Parse a testsuite run for one tool/variation combination.
    421      1.1  mrg             if line.startswith ('Running target '):
    422      1.1  mrg                 name = line[len ('Running target '):-1]
    423      1.1  mrg                 if not tool:
    424      1.1  mrg                     self.fatal (filename, 'could not parse tool name')
    425      1.1  mrg                 if name not in self.known_variations:
    426      1.1  mrg                     self.fatal (filename, 'unknown target: ' + name)
    427      1.1  mrg                 self.parse_run (filename, file, tool,
    428      1.1  mrg                                 tool.get_variation (name),
    429      1.1  mrg                                 num_variations)
    430      1.1  mrg                 # If there is only one variation then there is no separate
    431      1.1  mrg                 # summary for it.  Record any following version output.
    432      1.1  mrg                 if num_variations == 1:
    433      1.1  mrg                     self.parse_final_summary (filename, file)
    434      1.1  mrg                 continue
    435      1.1  mrg 
    436      1.1  mrg             # Parse the start line.  In the case where several files are being
    437      1.1  mrg             # parsed, pick the one with the earliest time.
    438      1.1  mrg             match = self.test_run_re.match (line)
    439      1.1  mrg             if match:
    440      1.1  mrg                 time = self.parse_time (match.group (2))
    441      1.1  mrg                 if not self.start_line or self.start_line[0] > time:
    442      1.1  mrg                     self.start_line = (time, line)
    443      1.1  mrg                 continue
    444      1.1  mrg 
    445      1.1  mrg             # Parse the form used for native testing.
    446      1.1  mrg             if line.startswith ('Native configuration is '):
    447      1.1  mrg                 self.native_line = line
    448      1.1  mrg                 continue
    449      1.1  mrg 
    450      1.1  mrg             # Parse the target triplet.
    451      1.1  mrg             if line.startswith ('Target is '):
    452      1.1  mrg                 self.target_line = line
    453      1.1  mrg                 continue
    454      1.1  mrg 
    455      1.1  mrg             # Parse the host triplet.
    456      1.1  mrg             if line.startswith ('Host   is '):
    457      1.1  mrg                 self.host_line = line
    458      1.1  mrg                 continue
    459      1.1  mrg 
    460      1.1  mrg             # Parse the acats premable.
    461      1.1  mrg             if line == '\t\t=== acats configuration ===\n':
    462      1.1  mrg                 self.parse_acats_run (filename, file)
    463      1.1  mrg                 continue
    464      1.1  mrg 
    465      1.1  mrg             # Parse the tool name.
    466      1.1  mrg             match = self.tool_re.match (line)
    467      1.1  mrg             if match:
    468      1.1  mrg                 tool = self.get_tool (match.group (1))
    469      1.1  mrg                 continue
    470      1.1  mrg 
    471      1.1  mrg             # Skip over the final summary (which we instead create from
    472      1.1  mrg             # individual runs) and parse the version output.
    473      1.1  mrg             if tool and line == '\t\t=== ' + tool.name + ' Summary ===\n':
    474      1.1  mrg                 if file.readline() != '\n':
    475      1.1  mrg                     self.fatal (filename, 'expected blank line after summary')
    476      1.1  mrg                 self.parse_final_summary (filename, file)
    477      1.1  mrg                 continue
    478      1.1  mrg 
    479      1.1  mrg             # Parse the completion line.  In the case where several files
    480      1.1  mrg             # are being parsed, pick the one with the latest time.
    481      1.1  mrg             match = self.completed_re.match (line)
    482      1.1  mrg             if match:
    483      1.1  mrg                 time = self.parse_time (match.group (1))
    484      1.1  mrg                 if not self.end_line or self.end_line[0] < time:
    485      1.1  mrg                     self.end_line = (time, line)
    486      1.1  mrg                 continue
    487      1.1  mrg 
    488      1.1  mrg             # Sanity check to make sure that important text doesn't get
    489      1.1  mrg             # dropped accidentally.
    490      1.1  mrg             if strict and line.strip() != '':
    491      1.1  mrg                 self.fatal (filename, 'unrecognised line: ' + line[:-1])
    492      1.1  mrg 
    493      1.1  mrg     # Output a segment of text.
    494      1.1  mrg     def output_segment (self, segment):
    495      1.1  mrg         with safe_open (segment.filename) as file:
    496      1.1  mrg             file.seek (segment.start)
    497      1.1  mrg             for i in range (segment.lines):
    498      1.1  mrg                 sys.stdout.write (file.readline())
    499      1.1  mrg 
    500      1.1  mrg     # Output a summary giving the number of times each type of result has
    501      1.1  mrg     # been seen.
    502      1.1  mrg     def output_summary (self, tool, counts):
    503      1.1  mrg         for i in range (len (self.count_names)):
    504      1.1  mrg             name = self.count_names[i]
    505      1.1  mrg             # dejagnu only prints result types that were seen at least once,
    506      1.1  mrg             # but acats always prints a number of unexpected failures.
    507      1.1  mrg             if (counts[i] > 0
    508      1.1  mrg                 or (tool.name == 'acats'
    509      1.1  mrg                     and name.startswith ('# of unexpected failures'))):
    510      1.1  mrg                 sys.stdout.write ('%s%d\n' % (name, counts[i]))
    511      1.1  mrg 
    512      1.1  mrg     # Output unified .log or .sum information for a particular variation,
    513      1.1  mrg     # with a summary at the end.
    514      1.1  mrg     def output_variation (self, tool, variation):
    515      1.1  mrg         self.output_segment (variation.header)
    516      1.1  mrg         for harness in sorted (variation.harnesses.values(),
    517      1.1  mrg                                key = attrgetter ('name')):
    518      1.1  mrg             sys.stdout.write ('Running ' + harness.name + ' ...\n')
    519      1.1  mrg             if self.do_sum:
    520      1.1  mrg                 harness.results.sort()
    521      1.1  mrg                 for (key, line) in harness.results:
    522      1.1  mrg                     sys.stdout.write (line)
    523      1.1  mrg             else:
    524      1.1  mrg                 # Rearrange the log segments into test order (but without
    525      1.1  mrg                 # rearranging text within those segments).
    526      1.1  mrg                 for key in sorted (harness.segments.keys()):
    527      1.1  mrg                     self.output_segment (harness.segments[key])
    528      1.1  mrg                 for segment in harness.empty:
    529      1.1  mrg                     self.output_segment (segment)
    530      1.1  mrg         if len (self.variations) > 1:
    531      1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' Summary for '
    532      1.1  mrg                               + variation.name + ' ===\n\n')
    533      1.1  mrg             self.output_summary (tool, variation.counts)
    534      1.1  mrg 
    535      1.1  mrg     # Output unified .log or .sum information for a particular tool,
    536      1.1  mrg     # with a summary at the end.
    537      1.1  mrg     def output_tool (self, tool):
    538      1.1  mrg         counts = self.zero_counts()
    539      1.1  mrg         if tool.name == 'acats':
    540      1.1  mrg             # acats doesn't use variations, so just output everything.
    541      1.1  mrg             # It also has a different approach to whitespace.
    542      1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' tests ===\n')
    543      1.1  mrg             for variation in tool.variations.values():
    544      1.1  mrg                 self.output_variation (tool, variation)
    545      1.1  mrg                 self.accumulate_counts (counts, variation.counts)
    546      1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' Summary ===\n')
    547      1.1  mrg         else:
    548      1.1  mrg             # Output the results in the usual dejagnu runtest format.
    549      1.1  mrg             sys.stdout.write ('\n\t\t=== ' + tool.name + ' tests ===\n\n'
    550      1.1  mrg                               'Schedule of variations:\n')
    551      1.1  mrg             for name in self.variations:
    552      1.1  mrg                 if name in tool.variations:
    553      1.1  mrg                     sys.stdout.write ('    ' + name + '\n')
    554      1.1  mrg             sys.stdout.write ('\n')
    555      1.1  mrg             for name in self.variations:
    556      1.1  mrg                 if name in tool.variations:
    557      1.1  mrg                     variation = tool.variations[name]
    558      1.1  mrg                     sys.stdout.write ('Running target '
    559      1.1  mrg                                       + variation.name + '\n')
    560      1.1  mrg                     self.output_variation (tool, variation)
    561      1.1  mrg                     self.accumulate_counts (counts, variation.counts)
    562      1.1  mrg             sys.stdout.write ('\n\t\t=== ' + tool.name + ' Summary ===\n\n')
    563      1.1  mrg         self.output_summary (tool, counts)
    564      1.1  mrg 
    565      1.1  mrg     def main (self):
    566      1.1  mrg         self.parse_cmdline()
    567      1.1  mrg         try:
    568      1.1  mrg             # Parse the input files.
    569      1.1  mrg             for filename in self.files:
    570      1.1  mrg                 with safe_open (filename) as file:
    571      1.1  mrg                     self.parse_file (filename, file)
    572      1.1  mrg 
    573      1.1  mrg             # Decide what to output.
    574      1.1  mrg             if len (self.variations) == 0:
    575      1.1  mrg                 self.variations = sorted (self.known_variations)
    576      1.1  mrg             else:
    577      1.1  mrg                 for name in self.variations:
    578      1.1  mrg                     if name not in self.known_variations:
    579      1.1  mrg                         self.fatal (None, 'no results for ' + name)
    580      1.1  mrg             if len (self.tools) == 0:
    581      1.1  mrg                 self.tools = sorted (self.runs.keys())
    582      1.1  mrg 
    583      1.1  mrg             # Output the header.
    584      1.1  mrg             if self.start_line:
    585      1.1  mrg                 sys.stdout.write (self.start_line[1])
    586      1.1  mrg             sys.stdout.write (self.native_line)
    587      1.1  mrg             sys.stdout.write (self.target_line)
    588      1.1  mrg             sys.stdout.write (self.host_line)
    589      1.1  mrg             sys.stdout.write (self.acats_premable)
    590      1.1  mrg 
    591      1.1  mrg             # Output the main body.
    592      1.1  mrg             for name in self.tools:
    593      1.1  mrg                 if name not in self.runs:
    594      1.1  mrg                     self.fatal (None, 'no results for ' + name)
    595      1.1  mrg                 self.output_tool (self.runs[name])
    596      1.1  mrg 
    597      1.1  mrg             # Output the footer.
    598      1.1  mrg             if len (self.acats_failures) > 0:
    599      1.1  mrg                 sys.stdout.write ('*** FAILURES: '
    600      1.1  mrg                                   + ' '.join (self.acats_failures) + '\n')
    601      1.1  mrg             sys.stdout.write (self.version_output)
    602      1.1  mrg             if self.end_line:
    603      1.1  mrg                 sys.stdout.write (self.end_line[1])
    604      1.1  mrg         except IOError as e:
    605      1.1  mrg             self.fatal (e.filename, e.strerror)
    606      1.1  mrg 
    607      1.1  mrg Prog().main()
    608