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