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