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