Home | History | Annotate | Line # | Download | only in contrib
dg-extract-results.py revision 1.1
      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  mrg                                      r'|KFAIL):\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  mrg             '# of expected passes\t\t',
    138  1.1  mrg             '# of unexpected failures\t',
    139  1.1  mrg             '# of unexpected successes\t',
    140  1.1  mrg             '# of expected failures\t\t',
    141  1.1  mrg             '# of unknown successes\t\t',
    142  1.1  mrg             '# of known failures\t\t',
    143  1.1  mrg             '# of untested testcases\t\t',
    144  1.1  mrg             '# of unresolved testcases\t',
    145  1.1  mrg             '# of unsupported tests\t\t'
    146  1.1  mrg         ]
    147  1.1  mrg         self.runs = dict()
    148  1.1  mrg 
    149  1.1  mrg     def usage (self):
    150  1.1  mrg         name = sys.argv[0]
    151  1.1  mrg         sys.stderr.write ('Usage: ' + name
    152  1.1  mrg                           + ''' [-t tool] [-l variant-list] [-L] log-or-sum-file ...
    153  1.1  mrg 
    154  1.1  mrg     tool           The tool (e.g. g++, libffi) for which to create a
    155  1.1  mrg                    new test summary file.  If not specified then output
    156  1.1  mrg                    is created for all tools.
    157  1.1  mrg     variant-list   One or more test variant names.  If the list is
    158  1.1  mrg                    not specified then one is constructed from all
    159  1.1  mrg                    variants in the files for <tool>.
    160  1.1  mrg     sum-file       A test summary file with the format of those
    161  1.1  mrg                    created by runtest from DejaGnu.
    162  1.1  mrg     If -L is used, merge *.log files instead of *.sum.  In this
    163  1.1  mrg     mode the exact order of lines may not be preserved, just different
    164  1.1  mrg     Running *.exp chunks should be in correct order.
    165  1.1  mrg ''')
    166  1.1  mrg         sys.exit (1)
    167  1.1  mrg 
    168  1.1  mrg     def fatal (self, what, string):
    169  1.1  mrg         if not what:
    170  1.1  mrg             what = sys.argv[0]
    171  1.1  mrg         sys.stderr.write (what + ': ' + string + '\n')
    172  1.1  mrg         sys.exit (1)
    173  1.1  mrg 
    174  1.1  mrg     # Parse the command-line arguments.
    175  1.1  mrg     def parse_cmdline (self):
    176  1.1  mrg         try:
    177  1.1  mrg             (options, self.files) = getopt.getopt (sys.argv[1:], 'l:t:L')
    178  1.1  mrg             if len (self.files) == 0:
    179  1.1  mrg                 self.usage()
    180  1.1  mrg             for (option, value) in options:
    181  1.1  mrg                 if option == '-l':
    182  1.1  mrg                     self.variations.append (value)
    183  1.1  mrg                 elif option == '-t':
    184  1.1  mrg                     self.tools.append (value)
    185  1.1  mrg                 else:
    186  1.1  mrg                     self.do_sum = False
    187  1.1  mrg         except getopt.GetoptError as e:
    188  1.1  mrg             self.fatal (None, e.msg)
    189  1.1  mrg 
    190  1.1  mrg     # Try to parse time string TIME, returning an arbitrary time on failure.
    191  1.1  mrg     # Getting this right is just a nice-to-have so failures should be silent.
    192  1.1  mrg     def parse_time (self, time):
    193  1.1  mrg         try:
    194  1.1  mrg             return datetime.strptime (time, '%c')
    195  1.1  mrg         except ValueError:
    196  1.1  mrg             return datetime.now()
    197  1.1  mrg 
    198  1.1  mrg     # Parse an integer and abort on failure.
    199  1.1  mrg     def parse_int (self, filename, value):
    200  1.1  mrg         try:
    201  1.1  mrg             return int (value)
    202  1.1  mrg         except ValueError:
    203  1.1  mrg             self.fatal (filename, 'expected an integer, got: ' + value)
    204  1.1  mrg 
    205  1.1  mrg     # Return a list that represents no test results.
    206  1.1  mrg     def zero_counts (self):
    207  1.1  mrg         return [0 for x in self.count_names]
    208  1.1  mrg 
    209  1.1  mrg     # Return the ToolRun for tool NAME.
    210  1.1  mrg     def get_tool (self, name):
    211  1.1  mrg         if name not in self.runs:
    212  1.1  mrg             self.runs[name] = ToolRun (name)
    213  1.1  mrg         return self.runs[name]
    214  1.1  mrg 
    215  1.1  mrg     # Add the result counts in list FROMC to TOC.
    216  1.1  mrg     def accumulate_counts (self, toc, fromc):
    217  1.1  mrg         for i in range (len (self.count_names)):
    218  1.1  mrg             toc[i] += fromc[i]
    219  1.1  mrg 
    220  1.1  mrg     # Parse the list of variations after 'Schedule of variations:'.
    221  1.1  mrg     # Return the number seen.
    222  1.1  mrg     def parse_variations (self, filename, file):
    223  1.1  mrg         num_variations = 0
    224  1.1  mrg         while True:
    225  1.1  mrg             line = file.readline()
    226  1.1  mrg             if line == '':
    227  1.1  mrg                 self.fatal (filename, 'could not parse variation list')
    228  1.1  mrg             if line == '\n':
    229  1.1  mrg                 break
    230  1.1  mrg             self.known_variations.add (line.strip())
    231  1.1  mrg             num_variations += 1
    232  1.1  mrg         return num_variations
    233  1.1  mrg 
    234  1.1  mrg     # Parse from the first line after 'Running target ...' to the end
    235  1.1  mrg     # of the run's summary.
    236  1.1  mrg     def parse_run (self, filename, file, tool, variation, num_variations):
    237  1.1  mrg         header = None
    238  1.1  mrg         harness = None
    239  1.1  mrg         segment = None
    240  1.1  mrg         final_using = 0
    241  1.1  mrg 
    242  1.1  mrg         # If this is the first run for this variation, add any text before
    243  1.1  mrg         # the first harness to the header.
    244  1.1  mrg         if not variation.header:
    245  1.1  mrg             segment = Segment (filename, file.tell())
    246  1.1  mrg             variation.header = segment
    247  1.1  mrg 
    248  1.1  mrg         # Parse up until the first line of the summary.
    249  1.1  mrg         if num_variations == 1:
    250  1.1  mrg             end = '\t\t=== ' + tool.name + ' Summary ===\n'
    251  1.1  mrg         else:
    252  1.1  mrg             end = ('\t\t=== ' + tool.name + ' Summary for '
    253  1.1  mrg                    + variation.name + ' ===\n')
    254  1.1  mrg         while True:
    255  1.1  mrg             line = file.readline()
    256  1.1  mrg             if line == '':
    257  1.1  mrg                 self.fatal (filename, 'no recognised summary line')
    258  1.1  mrg             if line == end:
    259  1.1  mrg                 break
    260  1.1  mrg 
    261  1.1  mrg             # Look for the start of a new harness.
    262  1.1  mrg             if line.startswith ('Running ') and line.endswith (' ...\n'):
    263  1.1  mrg                 # Close off the current harness segment, if any.
    264  1.1  mrg                 if harness:
    265  1.1  mrg                     segment.lines -= final_using
    266  1.1  mrg                     harness.add_segment (first_key, segment)
    267  1.1  mrg                 name = line[len ('Running '):-len(' ...\n')]
    268  1.1  mrg                 harness = variation.get_harness (name)
    269  1.1  mrg                 segment = Segment (filename, file.tell())
    270  1.1  mrg                 first_key = None
    271  1.1  mrg                 final_using = 0
    272  1.1  mrg                 continue
    273  1.1  mrg 
    274  1.1  mrg             # Record test results.  Associate the first test result with
    275  1.1  mrg             # the harness segment, so that if a run for a particular harness
    276  1.1  mrg             # has been split up, we can reassemble the individual segments
    277  1.1  mrg             # in a sensible order.
    278  1.1  mrg             #
    279  1.1  mrg             # dejagnu sometimes issues warnings about the testing environment
    280  1.1  mrg             # before running any tests.  Treat them as part of the header
    281  1.1  mrg             # rather than as a test result.
    282  1.1  mrg             match = self.result_re.match (line)
    283  1.1  mrg             if match and (harness or not line.startswith ('WARNING:')):
    284  1.1  mrg                 if not harness:
    285  1.1  mrg                     self.fatal (filename, 'saw test result before harness name')
    286  1.1  mrg                 name = match.group (2)
    287  1.1  mrg                 # Ugly hack to get the right order for gfortran.
    288  1.1  mrg                 if name.startswith ('gfortran.dg/g77/'):
    289  1.1  mrg                     name = 'h' + name
    290  1.1  mrg                 key = (name, len (harness.results))
    291  1.1  mrg                 harness.results.append ((key, line))
    292  1.1  mrg                 if not first_key and sort_logs:
    293  1.1  mrg                     first_key = key
    294  1.1  mrg 
    295  1.1  mrg             # 'Using ...' lines are only interesting in a header.  Splitting
    296  1.1  mrg             # the test up into parallel runs leads to more 'Using ...' lines
    297  1.1  mrg             # than there would be in a single log.
    298  1.1  mrg             if line.startswith ('Using '):
    299  1.1  mrg                 final_using += 1
    300  1.1  mrg             else:
    301  1.1  mrg                 final_using = 0
    302  1.1  mrg 
    303  1.1  mrg             # Add other text to the current segment, if any.
    304  1.1  mrg             if segment:
    305  1.1  mrg                 segment.lines += 1
    306  1.1  mrg 
    307  1.1  mrg         # Close off the final harness segment, if any.
    308  1.1  mrg         if harness:
    309  1.1  mrg             segment.lines -= final_using
    310  1.1  mrg             harness.add_segment (first_key, segment)
    311  1.1  mrg 
    312  1.1  mrg         # Parse the rest of the summary (the '# of ' lines).
    313  1.1  mrg         if len (variation.counts) == 0:
    314  1.1  mrg             variation.counts = self.zero_counts()
    315  1.1  mrg         while True:
    316  1.1  mrg             before = file.tell()
    317  1.1  mrg             line = file.readline()
    318  1.1  mrg             if line == '':
    319  1.1  mrg                 break
    320  1.1  mrg             if line == '\n':
    321  1.1  mrg                 continue
    322  1.1  mrg             if not line.startswith ('# '):
    323  1.1  mrg                 file.seek (before)
    324  1.1  mrg                 break
    325  1.1  mrg             found = False
    326  1.1  mrg             for i in range (len (self.count_names)):
    327  1.1  mrg                 if line.startswith (self.count_names[i]):
    328  1.1  mrg                     count = line[len (self.count_names[i]):-1].strip()
    329  1.1  mrg                     variation.counts[i] += self.parse_int (filename, count)
    330  1.1  mrg                     found = True
    331  1.1  mrg                     break
    332  1.1  mrg             if not found:
    333  1.1  mrg                 self.fatal (filename, 'unknown test result: ' + line[:-1])
    334  1.1  mrg 
    335  1.1  mrg     # Parse an acats run, which uses a different format from dejagnu.
    336  1.1  mrg     # We have just skipped over '=== acats configuration ==='.
    337  1.1  mrg     def parse_acats_run (self, filename, file):
    338  1.1  mrg         # Parse the preamble, which describes the configuration and logs
    339  1.1  mrg         # the creation of support files.
    340  1.1  mrg         record = (self.acats_premable == '')
    341  1.1  mrg         if record:
    342  1.1  mrg             self.acats_premable = '\t\t=== acats configuration ===\n'
    343  1.1  mrg         while True:
    344  1.1  mrg             line = file.readline()
    345  1.1  mrg             if line == '':
    346  1.1  mrg                 self.fatal (filename, 'could not parse acats preamble')
    347  1.1  mrg             if line == '\t\t=== acats tests ===\n':
    348  1.1  mrg                 break
    349  1.1  mrg             if record:
    350  1.1  mrg                 self.acats_premable += line
    351  1.1  mrg 
    352  1.1  mrg         # Parse the test results themselves, using a dummy variation name.
    353  1.1  mrg         tool = self.get_tool ('acats')
    354  1.1  mrg         variation = tool.get_variation ('none')
    355  1.1  mrg         self.parse_run (filename, file, tool, variation, 1)
    356  1.1  mrg 
    357  1.1  mrg         # Parse the failure list.
    358  1.1  mrg         while True:
    359  1.1  mrg             before = file.tell()
    360  1.1  mrg             line = file.readline()
    361  1.1  mrg             if line.startswith ('*** FAILURES: '):
    362  1.1  mrg                 self.acats_failures.append (line[len ('*** FAILURES: '):-1])
    363  1.1  mrg                 continue
    364  1.1  mrg             file.seek (before)
    365  1.1  mrg             break
    366  1.1  mrg 
    367  1.1  mrg     # Parse the final summary at the end of a log in order to capture
    368  1.1  mrg     # the version output that follows it.
    369  1.1  mrg     def parse_final_summary (self, filename, file):
    370  1.1  mrg         record = (self.version_output == '')
    371  1.1  mrg         while True:
    372  1.1  mrg             line = file.readline()
    373  1.1  mrg             if line == '':
    374  1.1  mrg                 break
    375  1.1  mrg             if line.startswith ('# of '):
    376  1.1  mrg                 continue
    377  1.1  mrg             if record:
    378  1.1  mrg                 self.version_output += line
    379  1.1  mrg             if line == '\n':
    380  1.1  mrg                 break
    381  1.1  mrg 
    382  1.1  mrg     # Parse a .log or .sum file.
    383  1.1  mrg     def parse_file (self, filename, file):
    384  1.1  mrg         tool = None
    385  1.1  mrg         target = None
    386  1.1  mrg         num_variations = 1
    387  1.1  mrg         while True:
    388  1.1  mrg             line = file.readline()
    389  1.1  mrg             if line == '':
    390  1.1  mrg                 return
    391  1.1  mrg 
    392  1.1  mrg             # Parse the list of variations, which comes before the test
    393  1.1  mrg             # runs themselves.
    394  1.1  mrg             if line.startswith ('Schedule of variations:'):
    395  1.1  mrg                 num_variations = self.parse_variations (filename, file)
    396  1.1  mrg                 continue
    397  1.1  mrg 
    398  1.1  mrg             # Parse a testsuite run for one tool/variation combination.
    399  1.1  mrg             if line.startswith ('Running target '):
    400  1.1  mrg                 name = line[len ('Running target '):-1]
    401  1.1  mrg                 if not tool:
    402  1.1  mrg                     self.fatal (filename, 'could not parse tool name')
    403  1.1  mrg                 if name not in self.known_variations:
    404  1.1  mrg                     self.fatal (filename, 'unknown target: ' + name)
    405  1.1  mrg                 self.parse_run (filename, file, tool,
    406  1.1  mrg                                 tool.get_variation (name),
    407  1.1  mrg                                 num_variations)
    408  1.1  mrg                 # If there is only one variation then there is no separate
    409  1.1  mrg                 # summary for it.  Record any following version output.
    410  1.1  mrg                 if num_variations == 1:
    411  1.1  mrg                     self.parse_final_summary (filename, file)
    412  1.1  mrg                 continue
    413  1.1  mrg 
    414  1.1  mrg             # Parse the start line.  In the case where several files are being
    415  1.1  mrg             # parsed, pick the one with the earliest time.
    416  1.1  mrg             match = self.test_run_re.match (line)
    417  1.1  mrg             if match:
    418  1.1  mrg                 time = self.parse_time (match.group (2))
    419  1.1  mrg                 if not self.start_line or self.start_line[0] > time:
    420  1.1  mrg                     self.start_line = (time, line)
    421  1.1  mrg                 continue
    422  1.1  mrg 
    423  1.1  mrg             # Parse the form used for native testing.
    424  1.1  mrg             if line.startswith ('Native configuration is '):
    425  1.1  mrg                 self.native_line = line
    426  1.1  mrg                 continue
    427  1.1  mrg 
    428  1.1  mrg             # Parse the target triplet.
    429  1.1  mrg             if line.startswith ('Target is '):
    430  1.1  mrg                 self.target_line = line
    431  1.1  mrg                 continue
    432  1.1  mrg 
    433  1.1  mrg             # Parse the host triplet.
    434  1.1  mrg             if line.startswith ('Host   is '):
    435  1.1  mrg                 self.host_line = line
    436  1.1  mrg                 continue
    437  1.1  mrg 
    438  1.1  mrg             # Parse the acats premable.
    439  1.1  mrg             if line == '\t\t=== acats configuration ===\n':
    440  1.1  mrg                 self.parse_acats_run (filename, file)
    441  1.1  mrg                 continue
    442  1.1  mrg 
    443  1.1  mrg             # Parse the tool name.
    444  1.1  mrg             match = self.tool_re.match (line)
    445  1.1  mrg             if match:
    446  1.1  mrg                 tool = self.get_tool (match.group (1))
    447  1.1  mrg                 continue
    448  1.1  mrg 
    449  1.1  mrg             # Skip over the final summary (which we instead create from
    450  1.1  mrg             # individual runs) and parse the version output.
    451  1.1  mrg             if tool and line == '\t\t=== ' + tool.name + ' Summary ===\n':
    452  1.1  mrg                 if file.readline() != '\n':
    453  1.1  mrg                     self.fatal (filename, 'expected blank line after summary')
    454  1.1  mrg                 self.parse_final_summary (filename, file)
    455  1.1  mrg                 continue
    456  1.1  mrg 
    457  1.1  mrg             # Parse the completion line.  In the case where several files
    458  1.1  mrg             # are being parsed, pick the one with the latest time.
    459  1.1  mrg             match = self.completed_re.match (line)
    460  1.1  mrg             if match:
    461  1.1  mrg                 time = self.parse_time (match.group (1))
    462  1.1  mrg                 if not self.end_line or self.end_line[0] < time:
    463  1.1  mrg                     self.end_line = (time, line)
    464  1.1  mrg                 continue
    465  1.1  mrg 
    466  1.1  mrg             # Sanity check to make sure that important text doesn't get
    467  1.1  mrg             # dropped accidentally.
    468  1.1  mrg             if strict and line.strip() != '':
    469  1.1  mrg                 self.fatal (filename, 'unrecognised line: ' + line[:-1])
    470  1.1  mrg 
    471  1.1  mrg     # Output a segment of text.
    472  1.1  mrg     def output_segment (self, segment):
    473  1.1  mrg         with safe_open (segment.filename) as file:
    474  1.1  mrg             file.seek (segment.start)
    475  1.1  mrg             for i in range (segment.lines):
    476  1.1  mrg                 sys.stdout.write (file.readline())
    477  1.1  mrg 
    478  1.1  mrg     # Output a summary giving the number of times each type of result has
    479  1.1  mrg     # been seen.
    480  1.1  mrg     def output_summary (self, tool, counts):
    481  1.1  mrg         for i in range (len (self.count_names)):
    482  1.1  mrg             name = self.count_names[i]
    483  1.1  mrg             # dejagnu only prints result types that were seen at least once,
    484  1.1  mrg             # but acats always prints a number of unexpected failures.
    485  1.1  mrg             if (counts[i] > 0
    486  1.1  mrg                 or (tool.name == 'acats'
    487  1.1  mrg                     and name.startswith ('# of unexpected failures'))):
    488  1.1  mrg                 sys.stdout.write ('%s%d\n' % (name, counts[i]))
    489  1.1  mrg 
    490  1.1  mrg     # Output unified .log or .sum information for a particular variation,
    491  1.1  mrg     # with a summary at the end.
    492  1.1  mrg     def output_variation (self, tool, variation):
    493  1.1  mrg         self.output_segment (variation.header)
    494  1.1  mrg         for harness in sorted (variation.harnesses.values(),
    495  1.1  mrg                                key = attrgetter ('name')):
    496  1.1  mrg             sys.stdout.write ('Running ' + harness.name + ' ...\n')
    497  1.1  mrg             if self.do_sum:
    498  1.1  mrg                 harness.results.sort()
    499  1.1  mrg                 for (key, line) in harness.results:
    500  1.1  mrg                     sys.stdout.write (line)
    501  1.1  mrg             else:
    502  1.1  mrg                 # Rearrange the log segments into test order (but without
    503  1.1  mrg                 # rearranging text within those segments).
    504  1.1  mrg                 for key in sorted (harness.segments.keys()):
    505  1.1  mrg                     self.output_segment (harness.segments[key])
    506  1.1  mrg                 for segment in harness.empty:
    507  1.1  mrg                     self.output_segment (segment)
    508  1.1  mrg         if len (self.variations) > 1:
    509  1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' Summary for '
    510  1.1  mrg                               + variation.name + ' ===\n\n')
    511  1.1  mrg             self.output_summary (tool, variation.counts)
    512  1.1  mrg 
    513  1.1  mrg     # Output unified .log or .sum information for a particular tool,
    514  1.1  mrg     # with a summary at the end.
    515  1.1  mrg     def output_tool (self, tool):
    516  1.1  mrg         counts = self.zero_counts()
    517  1.1  mrg         if tool.name == 'acats':
    518  1.1  mrg             # acats doesn't use variations, so just output everything.
    519  1.1  mrg             # It also has a different approach to whitespace.
    520  1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' tests ===\n')
    521  1.1  mrg             for variation in tool.variations.values():
    522  1.1  mrg                 self.output_variation (tool, variation)
    523  1.1  mrg                 self.accumulate_counts (counts, variation.counts)
    524  1.1  mrg             sys.stdout.write ('\t\t=== ' + tool.name + ' Summary ===\n')
    525  1.1  mrg         else:
    526  1.1  mrg             # Output the results in the usual dejagnu runtest format.
    527  1.1  mrg             sys.stdout.write ('\n\t\t=== ' + tool.name + ' tests ===\n\n'
    528  1.1  mrg                               'Schedule of variations:\n')
    529  1.1  mrg             for name in self.variations:
    530  1.1  mrg                 if name in tool.variations:
    531  1.1  mrg                     sys.stdout.write ('    ' + name + '\n')
    532  1.1  mrg             sys.stdout.write ('\n')
    533  1.1  mrg             for name in self.variations:
    534  1.1  mrg                 if name in tool.variations:
    535  1.1  mrg                     variation = tool.variations[name]
    536  1.1  mrg                     sys.stdout.write ('Running target '
    537  1.1  mrg                                       + variation.name + '\n')
    538  1.1  mrg                     self.output_variation (tool, variation)
    539  1.1  mrg                     self.accumulate_counts (counts, variation.counts)
    540  1.1  mrg             sys.stdout.write ('\n\t\t=== ' + tool.name + ' Summary ===\n\n')
    541  1.1  mrg         self.output_summary (tool, counts)
    542  1.1  mrg 
    543  1.1  mrg     def main (self):
    544  1.1  mrg         self.parse_cmdline()
    545  1.1  mrg         try:
    546  1.1  mrg             # Parse the input files.
    547  1.1  mrg             for filename in self.files:
    548  1.1  mrg                 with safe_open (filename) as file:
    549  1.1  mrg                     self.parse_file (filename, file)
    550  1.1  mrg 
    551  1.1  mrg             # Decide what to output.
    552  1.1  mrg             if len (self.variations) == 0:
    553  1.1  mrg                 self.variations = sorted (self.known_variations)
    554  1.1  mrg             else:
    555  1.1  mrg                 for name in self.variations:
    556  1.1  mrg                     if name not in self.known_variations:
    557  1.1  mrg                         self.fatal (None, 'no results for ' + name)
    558  1.1  mrg             if len (self.tools) == 0:
    559  1.1  mrg                 self.tools = sorted (self.runs.keys())
    560  1.1  mrg 
    561  1.1  mrg             # Output the header.
    562  1.1  mrg             if self.start_line:
    563  1.1  mrg                 sys.stdout.write (self.start_line[1])
    564  1.1  mrg             sys.stdout.write (self.native_line)
    565  1.1  mrg             sys.stdout.write (self.target_line)
    566  1.1  mrg             sys.stdout.write (self.host_line)
    567  1.1  mrg             sys.stdout.write (self.acats_premable)
    568  1.1  mrg 
    569  1.1  mrg             # Output the main body.
    570  1.1  mrg             for name in self.tools:
    571  1.1  mrg                 if name not in self.runs:
    572  1.1  mrg                     self.fatal (None, 'no results for ' + name)
    573  1.1  mrg                 self.output_tool (self.runs[name])
    574  1.1  mrg 
    575  1.1  mrg             # Output the footer.
    576  1.1  mrg             if len (self.acats_failures) > 0:
    577  1.1  mrg                 sys.stdout.write ('*** FAILURES: '
    578  1.1  mrg                                   + ' '.join (self.acats_failures) + '\n')
    579  1.1  mrg             sys.stdout.write (self.version_output)
    580  1.1  mrg             if self.end_line:
    581  1.1  mrg                 sys.stdout.write (self.end_line[1])
    582  1.1  mrg         except IOError as e:
    583  1.1  mrg             self.fatal (e.filename, e.strerror)
    584  1.1  mrg 
    585  1.1  mrg Prog().main()
    586