public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* RFA: speeding up dg-extract-results.sh
@ 2014-02-13  9:18 Richard Sandiford
  2014-05-03  7:34 ` Ping: " Richard Sandiford
                   ` (4 more replies)
  0 siblings, 5 replies; 18+ messages in thread
From: Richard Sandiford @ 2014-02-13  9:18 UTC (permalink / raw)
  To: gcc-patches

dg-extract-results.sh is used to combine the various .sum.sep and .log.sep
files produced by parallel testing into single .sum and .log files.
It's written in a combination of shell scripts and awk, so stays well
within the minimum system requirements.

However, it seems to be quadratic in the number of test variations,
since the size of the .sums and .logs are linear in it and the script
parses them all once per variation.  This means that when I'm doing the
mipsisa64-sde-elf testing:

    http://gcc.gnu.org/ml/gcc-testresults/2014-02/msg00025.html

the script takes just over 5 hours to produce the gcc.log file.

This patch tries to reduce that by providing an alternative single-script
version.  I was torn between Python and Tcl, but given how most people
tend to react to Tcl, I thought I'd better go for Python.  I wouldn't
mind rewriting it in Tcl if that seems better though, not least because
expect is already a prerequisite.

Python isn't yet required and I'm pretty sure this script needs 2.6
or later.  I'm also worried that the seek/tell stuff might not work on
Windows.  The patch therefore gets dg-extract-results.sh to check the
environment first and call into the python version if possible,
otherwise it falls back on the current approach.  This also means
that the patch is contained entirely within contrib/.  If this does
indeed not work on Windows then we should either fix the python code
(obviously preferred) or get dg-extract-results.sh to skip it on
Windows for now.

The new version processes the mipsisa64-sde-elf gcc.log in just over a minute.
It's also noticeably faster for more normal runs, e.g. for my 4-variant
mips64-linux-gnu testing the time taken to process gcc.log goes from 114s
to 11s.  But that's probably in the noise given how long testing takes anyway.

For completeness, although the basic approach was heavily based on the
original script, there are some minor differences in output:

- the 'Host is ' line is copied over.

- not all sorts in the .sh version were protected by LC_ALL=C, so the
  order of .exp files in the .sum could depend on locale.  The new version
  always follows the LC_ALL=C ordering (since that's what Python uses
  unless the script forces it not to).

- when the run for a particular .exp is split over several .log.seps,
  the separate logs are now reassembled in the same order as the .sum
  output, based on the first test in each .log fragment.  I've left this
  under the control of an internal variable for easier comparison though.

- the new version tries to keep the earliest start message and latest
  end message (based on the time in the message).  I thought this would
  give a better idea how long the full run took.

- the .log output now contains the tool version information at the end
  (as both versions do for .sum).

- the .log output only contains one set of 'Using foo.exp as the blah.'
  messages per run.  The .sh version drops most of the others but not all.

I checked that the outputs were otherwise identical for a set of
mips64-linux-gnu, mipsisa64-sde-elf and x86_64-linux-gnu runs.  I also
reran the acats tests with some nobbled testcases in order to test the
failure paths there.

Also bootstrapped & regression-tested on x86_64-linux-gnu.  OK to install?

Thanks,
Richard


contrib/
	* dg-extract-results.py: New file.
	* dg-extract-results.sh: Use it if the environment seems suitable.

Index: contrib/dg-extract-results.py
===================================================================
--- /dev/null	2014-02-10 23:36:59.384652914 +0000
+++ contrib/dg-extract-results.py	2014-02-13 07:50:18.877804877 +0000
@@ -0,0 +1,577 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Free Software Foundation, Inc.
+#
+# This script is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+
+import sys
+import getopt
+import re
+from datetime import datetime
+
+# True if unrecognised lines should cause a fatal error.  Might want to turn
+# this on by default later.
+strict = False
+
+# True if the order of .log segments should match the .sum file, false if
+# they should keep the original order.
+sort_logs = True
+
+class Named:
+    def __init__ (self, name):
+        self.name = name
+
+    def __cmp__ (self, other):
+        return cmp (self.name, other.name)
+
+class ToolRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # The variations run for this tool, mapped by --target_board name.
+        self.variations = dict()
+
+    # Return the VariationRun for variation NAME.
+    def get_variation (self, name):
+        if name not in self.variations:
+            self.variations[name] = VariationRun (name)
+        return self.variations[name]
+
+class VariationRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # A segment of text before the harness runs start, describing which
+        # baseboard files were loaded for the target.
+        self.header = None
+        # The harnesses run for this variation, mapped by filename.
+        self.harnesses = dict()
+        # A list giving the number of times each type of result has
+        # been seen.
+        self.counts = []
+
+    # Return the HarnessRun for harness NAME.
+    def get_harness (self, name):
+        if name not in self.harnesses:
+            self.harnesses[name] = HarnessRun (name)
+        return self.harnesses[name]
+
+class HarnessRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # Segments of text that make up the harness run, mapped by a test-based
+        # key that can be used to order them.
+        self.segments = dict()
+        # Segments of text that make up the harness run but which have
+        # no recognized test results.  These are typically harnesses that
+        # are completely skipped for the target.
+        self.empty = []
+        # A list of results.  Each entry is a pair in which the first element
+        # is a unique sorting key and in which the second is the full
+        # PASS/FAIL line.
+        self.results = []
+
+    # Add a segment of text to the harness run.  If the segment includes
+    # test results, KEY is an example of one of them, and can be used to
+    # combine the individual segments in order.  If the segment has no
+    # test results (e.g. because the harness doesn't do anything for the
+    # current configuration) then KEY is None instead.  In that case
+    # just collect the segments in the order that we see them.
+    def add_segment (self, key, segment):
+        if key:
+            assert key not in self.segments
+            self.segments[key] = segment
+        else:
+            self.empty.append (segment)
+
+class Segment:
+    def __init__ (self, filename, start):
+        self.filename = filename
+        self.start = start
+        self.lines = 0
+
+class Prog:
+    def __init__ (self):
+        # The variations specified on the command line.
+        self.variations = []
+        # The variations seen in the input files.
+        self.known_variations = set()
+        # The tools specified on the command line.
+        self.tools = []
+        # Whether to create .sum rather than .log output.
+        self.do_sum = True
+        # Regexps used while parsing.
+        self.test_run_re = re.compile (r'^Test Run By (\S+) on (.*)$')
+        self.tool_re = re.compile (r'^\t\t=== (.*) tests ===$')
+        self.result_re = re.compile (r'^(PASS|XPASS|FAIL|XFAIL|UNRESOLVED'
+                                     r'|WARNING|ERROR|UNSUPPORTED|UNTESTED'
+                                     r'|KFAIL):\s*(\S+)')
+        self.completed_re = re.compile (r'.* completed at (.*)')
+        # Pieces of text to write at the head of the output.
+        # start_line is a pair in which the first element is a datetime
+        # and in which the second is the associated 'Test Run By' line.
+        self.start_line = None
+        self.native_line = ''
+        self.target_line = ''
+        self.host_line = ''
+        self.acats_premable = ''
+        # Pieces of text to write at the end of the output.
+        # end_line is like start_line but for the 'runtest completed' line.
+        self.acats_failures = []
+        self.version_output = ''
+        self.end_line = None
+        # Known summary types.
+        self.count_names = [
+            '# of expected passes\t\t',
+            '# of unexpected failures\t',
+            '# of unexpected successes\t',
+            '# of expected failures\t\t',
+            '# of unknown successes\t\t',
+            '# of known failures\t\t',
+            '# of untested testcases\t\t',
+            '# of unresolved testcases\t',
+            '# of unsupported tests\t\t'
+        ]
+        self.runs = dict()
+
+    def usage (self):
+        name = sys.argv[0]
+        sys.stderr.write ('Usage: ' + name
+                          + ''' [-t tool] [-l variant-list] [-L] log-or-sum-file ...
+
+    tool           The tool (e.g. g++, libffi) for which to create a
+                   new test summary file.  If not specified then output
+                   is created for all tools.
+    variant-list   One or more test variant names.  If the list is
+                   not specified then one is constructed from all
+                   variants in the files for <tool>.
+    sum-file       A test summary file with the format of those
+                   created by runtest from DejaGnu.
+    If -L is used, merge *.log files instead of *.sum.  In this
+    mode the exact order of lines may not be preserved, just different
+    Running *.exp chunks should be in correct order.
+''')
+        sys.exit (1)
+
+    def fatal (self, what, string):
+        if not what:
+            what = sys.argv[0]
+        sys.stderr.write (what + ': ' + string + '\n')
+        sys.exit (1)
+
+    # Parse the command-line arguments.
+    def parse_cmdline (self):
+        try:
+            (options, self.files) = getopt.getopt (sys.argv[1:], 'l:t:L')
+            if len (self.files) == 0:
+                self.usage()
+            for (option, value) in options:
+                if option == '-l':
+                    self.variations.append (value)
+                elif option == '-t':
+                    self.tools.append (value)
+                else:
+                    self.do_sum = False
+        except getopt.GetoptError as e:
+            self.fatal (None, e.msg)
+
+    # Try to parse time string TIME, returning an arbitrary time on failure.
+    # Getting this right is just a nice-to-have so failures should be silent.
+    def parse_time (self, time):
+        try:
+            return datetime.strptime (time, '%c')
+        except ValueError:
+            return datetime.now()
+
+    # Parse an integer and abort on failure.
+    def parse_int (self, filename, value):
+        try:
+            return int (value)
+        except ValueError:
+            self.fatal (filename, 'expected an integer, got: ' + value)
+
+    # Return a list that represents no test results.
+    def zero_counts (self):
+        return [0 for x in self.count_names]
+
+    # Return the ToolRun for tool NAME.
+    def get_tool (self, name):
+        if name not in self.runs:
+            self.runs[name] = ToolRun (name)
+        return self.runs[name]
+
+    # Add the result counts in list FROMC to TOC.
+    def accumulate_counts (self, toc, fromc):
+        for i in range (len (self.count_names)):
+            toc[i] += fromc[i]
+
+    # Parse the list of variations after 'Schedule of variations:'.
+    # Return the number seen.
+    def parse_variations (self, filename, file):
+        num_variations = 0
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'could not parse variation list')
+            if line == '\n':
+                break
+            self.known_variations.add (line.strip())
+            num_variations += 1
+        return num_variations
+
+    # Parse from the first line after 'Running target ...' to the end
+    # of the run's summary.
+    def parse_run (self, filename, file, tool, variation, num_variations):
+        header = None
+        harness = None
+        segment = None
+        final_using = 0
+
+        # If this is the first run for this variation, add any text before
+        # the first harness to the header.
+        if not variation.header:
+            segment = Segment (filename, file.tell())
+            variation.header = segment
+
+        # Parse up until the first line of the summary.
+        if num_variations == 1:
+            end = '\t\t=== ' + tool.name + ' Summary ===\n'
+        else:
+            end = ('\t\t=== ' + tool.name + ' Summary for '
+                   + variation.name + ' ===\n')
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'no recognised summary line')
+            if line == end:
+                break
+
+            # Look for the start of a new harness.
+            if line.startswith ('Running ') and line.endswith (' ...\n'):
+                # Close off the current harness segment, if any.
+                if harness:
+                    segment.lines -= final_using
+                    harness.add_segment (first_key, segment)
+                name = line[len ('Running '):-len(' ...\n')]
+                harness = variation.get_harness (name)
+                segment = Segment (filename, file.tell())
+                first_key = None
+                final_using = 0
+                continue
+
+            # Record test results.  Associate the first test result with
+            # the harness segment, so that if a run for a particular harness
+            # has been split up, we can reassemble the individual segments
+            # in a sensible order.
+            match = self.result_re.match (line)
+            if match:
+                if not harness:
+                    self.fatal (filename, 'saw test result before harness name')
+                name = match.group (2)
+                # Ugly hack to get the right order for gfortran.
+                if name.startswith ('gfortran.dg/g77/'):
+                    name = 'h' + name
+                key = (name, len (harness.results))
+                harness.results.append ((key, line))
+                if not first_key and sort_logs:
+                    first_key = key
+
+            # 'Using ...' lines are only interesting in a header.  Splitting
+            # the test up into parallel runs leads to more 'Using ...' lines
+            # than there would be in a single log.
+            if line.startswith ('Using '):
+                final_using += 1
+            else:
+                final_using = 0
+
+            # Add other text to the current segment, if any.
+            if segment:
+                segment.lines += 1
+
+        # Close off the final harness segment, if any.
+        if harness:
+            segment.lines -= final_using
+            harness.add_segment (first_key, segment)
+
+        # Parse the rest of the summary (the '# of ' lines).
+        if len (variation.counts) == 0:
+            variation.counts = self.zero_counts()
+        while True:
+            before = file.tell()
+            line = file.readline()
+            if line == '':
+                break
+            if line == '\n':
+                continue
+            if not line.startswith ('# '):
+                file.seek (before)
+                break
+            found = False
+            for i in range (len (self.count_names)):
+                if line.startswith (self.count_names[i]):
+                    count = line[len (self.count_names[i]):-1].strip()
+                    variation.counts[i] += self.parse_int (filename, count)
+                    found = True
+                    break
+            if not found:
+                self.fatal (filename, 'unknown test result: ' + line[:-1])
+
+    # Parse an acats run, which uses a different format from dejagnu.
+    # We have just skipped over '=== acats configuration ==='.
+    def parse_acats_run (self, filename, file):
+        # Parse the preamble, which describes the configuration and logs
+        # the creation of support files.
+        record = (self.acats_premable == '')
+        if record:
+            self.acats_premable = '\t\t=== acats configuration ===\n'
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'could not parse acats preamble')
+            if line == '\t\t=== acats tests ===\n':
+                break
+            if record:
+                self.acats_premable += line
+
+        # Parse the test results themselves, using a dummy variation name.
+        tool = self.get_tool ('acats')
+        variation = tool.get_variation ('none')
+        self.parse_run (filename, file, tool, variation, 1)
+
+        # Parse the failure list.
+        while True:
+            before = file.tell()
+            line = file.readline()
+            if line.startswith ('*** FAILURES: '):
+                self.acats_failures.append (line[len ('*** FAILURES: '):-1])
+                continue
+            file.seek (before)
+            break
+
+    # Parse the final summary at the end of a log in order to capture
+    # the version output that follows it.
+    def parse_final_summary (self, filename, file):
+        record = (self.version_output == '')
+        while True:
+            line = file.readline()
+            if line == '':
+                break
+            if line.startswith ('# of '):
+                continue
+            if record:
+                self.version_output += line
+            if line == '\n':
+                break
+
+    # Parse a .log or .sum file.
+    def parse_file (self, filename, file):
+        tool = None
+        target = None
+        num_variations = 1
+        while True:
+            line = file.readline()
+            if line == '':
+                return
+
+            # Parse the list of variations, which comes before the test
+            # runs themselves.
+            if line.startswith ('Schedule of variations:'):
+                num_variations = self.parse_variations (filename, file)
+                continue
+
+            # Parse a testsuite run for one tool/variation combination.
+            if line.startswith ('Running target '):
+                name = line[len ('Running target '):-1]
+                if not tool:
+                    self.fatal (filename, 'could not parse tool name')
+                if name not in self.known_variations:
+                    self.fatal (filename, 'unknown target: ' + name)
+                self.parse_run (filename, file, tool,
+                                tool.get_variation (name),
+                                num_variations)
+                # If there is only one variation then there is no separate
+                # summary for it.  Record any following version output.
+                if num_variations == 1:
+                    self.parse_final_summary (filename, file)
+                continue
+
+            # Parse the start line.  In the case where several files are being
+            # parsed, pick the one with the earliest time.
+            match = self.test_run_re.match (line)
+            if match:
+                time = self.parse_time (match.group (2))
+                if not self.start_line or self.start_line[0] > time:
+                    self.start_line = (time, line)
+                continue
+
+            # Parse the form used for native testing.
+            if line.startswith ('Native configuration is '):
+                self.native_line = line
+                continue
+
+            # Parse the target triplet.
+            if line.startswith ('Target is '):
+                self.target_line = line
+                continue
+
+            # Parse the host triplet.
+            if line.startswith ('Host   is '):
+                self.host_line = line
+                continue
+
+            # Parse the acats premable.
+            if line == '\t\t=== acats configuration ===\n':
+                self.parse_acats_run (filename, file)
+                continue
+
+            # Parse the tool name.
+            match = self.tool_re.match (line)
+            if match:
+                tool = self.get_tool (match.group (1))
+                continue
+
+            # Skip over the final summary (which we instead create from
+            # individual runs) and parse the version output.
+            if tool and line == '\t\t=== ' + tool.name + ' Summary ===\n':
+                if file.readline() != '\n':
+                    self.fatal (filename, 'expected blank line after summary')
+                self.parse_final_summary (filename, file)
+                continue
+
+            # Parse the completion line.  In the case where several files
+            # are being parsed, pick the one with the latest time.
+            match = self.completed_re.match (line)
+            if match:
+                time = self.parse_time (match.group (1))
+                if not self.end_line or self.end_line[0] < time:
+                    self.end_line = (time, line)
+                continue
+
+            # Sanity check to make sure that important text doesn't get
+            # dropped accidentally.
+            if strict and line.strip() != '':
+                self.fatal (filename, 'unrecognised line: ' + line[:-1])
+
+    # Output a segment of text.
+    def output_segment (self, segment):
+        with open (segment.filename, 'r') as file:
+            file.seek (segment.start)
+            for i in range (segment.lines):
+                sys.stdout.write (file.readline())
+
+    # Output a summary giving the number of times each type of result has
+    # been seen.
+    def output_summary (self, tool, counts):
+        for i in range (len (self.count_names)):
+            name = self.count_names[i]
+            # dejagnu only prints result types that were seen at least once,
+            # but acats always prints a number of unexpected failures.
+            if (counts[i] > 0
+                or (tool.name == 'acats'
+                    and name.startswith ('# of unexpected failures'))):
+                sys.stdout.write ('%s%d\n' % (name, counts[i]))
+
+    # Output unified .log or .sum information for a particular variation,
+    # with a summary at the end.
+    def output_variation (self, tool, variation):
+        self.output_segment (variation.header)
+        for harness in sorted (variation.harnesses.values()):
+            sys.stdout.write ('Running ' + harness.name + ' ...\n')
+            if self.do_sum:
+                # Keep the original test result order if there was only
+                # one segment for this harness.  This is needed for
+                # unsorted.exp, which has unusual test names.  Otherwise
+                # sort the tests by test filename.  If there are several
+                # subtests for the same test filename (such as 'compilation',
+                # 'test for excess errors', etc.) then keep the subtests
+                # in the original order.
+                if len (harness.segments) > 1:
+                    harness.results.sort()
+                for (key, line) in harness.results:
+                    sys.stdout.write (line)
+            else:
+                # Rearrange the log segments into test order (but without
+                # rearranging text within those segments).
+                for key in sorted (harness.segments.keys()):
+                    self.output_segment (harness.segments[key])
+                for segment in harness.empty:
+                    self.output_segment (segment)
+        if len (self.variations) > 1:
+            sys.stdout.write ('\t\t=== ' + tool.name + ' Summary for '
+                              + variation.name + ' ===\n\n')
+            self.output_summary (tool, variation.counts)
+
+    # Output unified .log or .sum information for a particular tool,
+    # with a summary at the end.
+    def output_tool (self, tool):
+        counts = self.zero_counts()
+        if tool.name == 'acats':
+            # acats doesn't use variations, so just output everything.
+            # It also has a different approach to whitespace.
+            sys.stdout.write ('\t\t=== ' + tool.name + ' tests ===\n')
+            for variation in tool.variations.values():
+                self.output_variation (tool, variation)
+                self.accumulate_counts (counts, variation.counts)
+            sys.stdout.write ('\t\t=== ' + tool.name + ' Summary ===\n')
+        else:
+            # Output the results in the usual dejagnu runtest format.
+            sys.stdout.write ('\n\t\t=== ' + tool.name + ' tests ===\n\n'
+                              'Schedule of variations:\n')
+            for name in self.variations:
+                if name in tool.variations:
+                    sys.stdout.write ('    ' + name + '\n')
+            sys.stdout.write ('\n')
+            for name in self.variations:
+                if name in tool.variations:
+                    variation = tool.variations[name]
+                    sys.stdout.write ('Running target '
+                                      + variation.name + '\n')
+                    self.output_variation (tool, variation)
+                    self.accumulate_counts (counts, variation.counts)
+            sys.stdout.write ('\n\t\t=== ' + tool.name + ' Summary ===\n\n')
+        self.output_summary (tool, counts)
+
+    def main (self):
+        self.parse_cmdline()
+        try:
+            # Parse the input files.
+            for filename in self.files:
+                with open (filename, 'r') as file:
+                    self.parse_file (filename, file)
+
+            # Decide what to output.
+            if len (self.variations) == 0:
+                self.variations = sorted (self.known_variations)
+            else:
+                for name in self.variations:
+                    if name not in self.known_variations:
+                        self.fatal (None, 'no results for ' + name)
+            if len (self.tools) == 0:
+                self.tools = sorted (self.runs.keys())
+
+            # Output the header.
+            if self.start_line:
+                sys.stdout.write (self.start_line[1])
+            sys.stdout.write (self.native_line)
+            sys.stdout.write (self.target_line)
+            sys.stdout.write (self.host_line)
+            sys.stdout.write (self.acats_premable)
+
+            # Output the main body.
+            for name in self.tools:
+                if name not in self.runs:
+                    self.fatal (None, 'no results for ' + name)
+                self.output_tool (self.runs[name])
+
+            # Output the footer.
+            if len (self.acats_failures) > 0:
+                sys.stdout.write ('*** FAILURES: '
+                                  + ' '.join (self.acats_failures) + '\n')
+            sys.stdout.write (self.version_output)
+            if self.end_line:
+                sys.stdout.write (self.end_line[1])
+        except IOError as e:
+            self.fatal (e.filename, e.strerror)
+
+Prog().main()
Index: contrib/dg-extract-results.sh
===================================================================
--- contrib/dg-extract-results.sh	2014-02-12 10:59:15.406262175 +0000
+++ contrib/dg-extract-results.sh	2014-02-12 22:33:35.774034098 +0000
@@ -28,6 +28,15 @@
 
 PROGNAME=dg-extract-results.sh
 
+# Try to use the python version if possible, since it tends to be faster.
+PYTHON_VER=`echo "$0" | sed 's/sh$/py/'`
+if test "$PYTHON_VER" != "$0" &&
+   test -f "$PYTHON_VER" &&
+   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) else 1)' \
+     > /dev/null 2> /dev/null; then
+  exec python $PYTHON_VER "$@"
+fi
+
 usage() {
   cat <<EOF >&2
 Usage: $PROGNAME [-t tool] [-l variant-list] [-L] sum-file ...

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Ping: RFA: speeding up dg-extract-results.sh
  2014-02-13  9:18 RFA: speeding up dg-extract-results.sh Richard Sandiford
@ 2014-05-03  7:34 ` Richard Sandiford
  2014-05-05 19:17   ` Jakub Jelinek
  2014-05-03 20:07 ` Mike Stump
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 18+ messages in thread
From: Richard Sandiford @ 2014-05-03  7:34 UTC (permalink / raw)
  To: gcc-patches

Ping for this old patch.  I didn't ping it before because I can imagine
it wasn't a good idea at that stage in 4.8.  But I've been using it locally
since and it really does make a big difference when testing lots of
multilibs.

Thanks,
Richard

Richard Sandiford <rdsandiford@googlemail.com> writes:
> dg-extract-results.sh is used to combine the various .sum.sep and .log.sep
> files produced by parallel testing into single .sum and .log files.
> It's written in a combination of shell scripts and awk, so stays well
> within the minimum system requirements.
>
> However, it seems to be quadratic in the number of test variations,
> since the size of the .sums and .logs are linear in it and the script
> parses them all once per variation.  This means that when I'm doing the
> mipsisa64-sde-elf testing:
>
>     http://gcc.gnu.org/ml/gcc-testresults/2014-02/msg00025.html
>
> the script takes just over 5 hours to produce the gcc.log file.
>
> This patch tries to reduce that by providing an alternative single-script
> version.  I was torn between Python and Tcl, but given how most people
> tend to react to Tcl, I thought I'd better go for Python.  I wouldn't
> mind rewriting it in Tcl if that seems better though, not least because
> expect is already a prerequisite.
>
> Python isn't yet required and I'm pretty sure this script needs 2.6
> or later.  I'm also worried that the seek/tell stuff might not work on
> Windows.  The patch therefore gets dg-extract-results.sh to check the
> environment first and call into the python version if possible,
> otherwise it falls back on the current approach.  This also means
> that the patch is contained entirely within contrib/.  If this does
> indeed not work on Windows then we should either fix the python code
> (obviously preferred) or get dg-extract-results.sh to skip it on
> Windows for now.
>
> The new version processes the mipsisa64-sde-elf gcc.log in just over a minute.
> It's also noticeably faster for more normal runs, e.g. for my 4-variant
> mips64-linux-gnu testing the time taken to process gcc.log goes from 114s
> to 11s.  But that's probably in the noise given how long testing takes anyway.
>
> For completeness, although the basic approach was heavily based on the
> original script, there are some minor differences in output:
>
> - the 'Host is ' line is copied over.
>
> - not all sorts in the .sh version were protected by LC_ALL=C, so the
>   order of .exp files in the .sum could depend on locale.  The new version
>   always follows the LC_ALL=C ordering (since that's what Python uses
>   unless the script forces it not to).
>
> - when the run for a particular .exp is split over several .log.seps,
>   the separate logs are now reassembled in the same order as the .sum
>   output, based on the first test in each .log fragment.  I've left this
>   under the control of an internal variable for easier comparison though.
>
> - the new version tries to keep the earliest start message and latest
>   end message (based on the time in the message).  I thought this would
>   give a better idea how long the full run took.
>
> - the .log output now contains the tool version information at the end
>   (as both versions do for .sum).
>
> - the .log output only contains one set of 'Using foo.exp as the blah.'
>   messages per run.  The .sh version drops most of the others but not all.
>
> I checked that the outputs were otherwise identical for a set of
> mips64-linux-gnu, mipsisa64-sde-elf and x86_64-linux-gnu runs.  I also
> reran the acats tests with some nobbled testcases in order to test the
> failure paths there.
>
> Also bootstrapped & regression-tested on x86_64-linux-gnu.  OK to install?
>
> Thanks,
> Richard

contrib/
	* dg-extract-results.py: New file.
	* dg-extract-results.sh: Use it if the environment seems suitable.

Index: contrib/dg-extract-results.py
===================================================================
--- /dev/null	2014-02-10 23:36:59.384652914 +0000
+++ contrib/dg-extract-results.py	2014-02-13 10:07:24.845979555 +0000
@@ -0,0 +1,577 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Free Software Foundation, Inc.
+#
+# This script is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+
+import sys
+import getopt
+import re
+from datetime import datetime
+
+# True if unrecognised lines should cause a fatal error.  Might want to turn
+# this on by default later.
+strict = False
+
+# True if the order of .log segments should match the .sum file, false if
+# they should keep the original order.
+sort_logs = True
+
+class Named:
+    def __init__ (self, name):
+        self.name = name
+
+    def __cmp__ (self, other):
+        return cmp (self.name, other.name)
+
+class ToolRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # The variations run for this tool, mapped by --target_board name.
+        self.variations = dict()
+
+    # Return the VariationRun for variation NAME.
+    def get_variation (self, name):
+        if name not in self.variations:
+            self.variations[name] = VariationRun (name)
+        return self.variations[name]
+
+class VariationRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # A segment of text before the harness runs start, describing which
+        # baseboard files were loaded for the target.
+        self.header = None
+        # The harnesses run for this variation, mapped by filename.
+        self.harnesses = dict()
+        # A list giving the number of times each type of result has
+        # been seen.
+        self.counts = []
+
+    # Return the HarnessRun for harness NAME.
+    def get_harness (self, name):
+        if name not in self.harnesses:
+            self.harnesses[name] = HarnessRun (name)
+        return self.harnesses[name]
+
+class HarnessRun (Named):
+    def __init__ (self, name):
+        Named.__init__ (self, name)
+        # Segments of text that make up the harness run, mapped by a test-based
+        # key that can be used to order them.
+        self.segments = dict()
+        # Segments of text that make up the harness run but which have
+        # no recognized test results.  These are typically harnesses that
+        # are completely skipped for the target.
+        self.empty = []
+        # A list of results.  Each entry is a pair in which the first element
+        # is a unique sorting key and in which the second is the full
+        # PASS/FAIL line.
+        self.results = []
+
+    # Add a segment of text to the harness run.  If the segment includes
+    # test results, KEY is an example of one of them, and can be used to
+    # combine the individual segments in order.  If the segment has no
+    # test results (e.g. because the harness doesn't do anything for the
+    # current configuration) then KEY is None instead.  In that case
+    # just collect the segments in the order that we see them.
+    def add_segment (self, key, segment):
+        if key:
+            assert key not in self.segments
+            self.segments[key] = segment
+        else:
+            self.empty.append (segment)
+
+class Segment:
+    def __init__ (self, filename, start):
+        self.filename = filename
+        self.start = start
+        self.lines = 0
+
+class Prog:
+    def __init__ (self):
+        # The variations specified on the command line.
+        self.variations = []
+        # The variations seen in the input files.
+        self.known_variations = set()
+        # The tools specified on the command line.
+        self.tools = []
+        # Whether to create .sum rather than .log output.
+        self.do_sum = True
+        # Regexps used while parsing.
+        self.test_run_re = re.compile (r'^Test Run By (\S+) on (.*)$')
+        self.tool_re = re.compile (r'^\t\t=== (.*) tests ===$')
+        self.result_re = re.compile (r'^(PASS|XPASS|FAIL|XFAIL|UNRESOLVED'
+                                     r'|WARNING|ERROR|UNSUPPORTED|UNTESTED'
+                                     r'|KFAIL):\s*(\S+)')
+        self.completed_re = re.compile (r'.* completed at (.*)')
+        # Pieces of text to write at the head of the output.
+        # start_line is a pair in which the first element is a datetime
+        # and in which the second is the associated 'Test Run By' line.
+        self.start_line = None
+        self.native_line = ''
+        self.target_line = ''
+        self.host_line = ''
+        self.acats_premable = ''
+        # Pieces of text to write at the end of the output.
+        # end_line is like start_line but for the 'runtest completed' line.
+        self.acats_failures = []
+        self.version_output = ''
+        self.end_line = None
+        # Known summary types.
+        self.count_names = [
+            '# of expected passes\t\t',
+            '# of unexpected failures\t',
+            '# of unexpected successes\t',
+            '# of expected failures\t\t',
+            '# of unknown successes\t\t',
+            '# of known failures\t\t',
+            '# of untested testcases\t\t',
+            '# of unresolved testcases\t',
+            '# of unsupported tests\t\t'
+        ]
+        self.runs = dict()
+
+    def usage (self):
+        name = sys.argv[0]
+        sys.stderr.write ('Usage: ' + name
+                          + ''' [-t tool] [-l variant-list] [-L] log-or-sum-file ...
+
+    tool           The tool (e.g. g++, libffi) for which to create a
+                   new test summary file.  If not specified then output
+                   is created for all tools.
+    variant-list   One or more test variant names.  If the list is
+                   not specified then one is constructed from all
+                   variants in the files for <tool>.
+    sum-file       A test summary file with the format of those
+                   created by runtest from DejaGnu.
+    If -L is used, merge *.log files instead of *.sum.  In this
+    mode the exact order of lines may not be preserved, just different
+    Running *.exp chunks should be in correct order.
+''')
+        sys.exit (1)
+
+    def fatal (self, what, string):
+        if not what:
+            what = sys.argv[0]
+        sys.stderr.write (what + ': ' + string + '\n')
+        sys.exit (1)
+
+    # Parse the command-line arguments.
+    def parse_cmdline (self):
+        try:
+            (options, self.files) = getopt.getopt (sys.argv[1:], 'l:t:L')
+            if len (self.files) == 0:
+                self.usage()
+            for (option, value) in options:
+                if option == '-l':
+                    self.variations.append (value)
+                elif option == '-t':
+                    self.tools.append (value)
+                else:
+                    self.do_sum = False
+        except getopt.GetoptError as e:
+            self.fatal (None, e.msg)
+
+    # Try to parse time string TIME, returning an arbitrary time on failure.
+    # Getting this right is just a nice-to-have so failures should be silent.
+    def parse_time (self, time):
+        try:
+            return datetime.strptime (time, '%c')
+        except ValueError:
+            return datetime.now()
+
+    # Parse an integer and abort on failure.
+    def parse_int (self, filename, value):
+        try:
+            return int (value)
+        except ValueError:
+            self.fatal (filename, 'expected an integer, got: ' + value)
+
+    # Return a list that represents no test results.
+    def zero_counts (self):
+        return [0 for x in self.count_names]
+
+    # Return the ToolRun for tool NAME.
+    def get_tool (self, name):
+        if name not in self.runs:
+            self.runs[name] = ToolRun (name)
+        return self.runs[name]
+
+    # Add the result counts in list FROMC to TOC.
+    def accumulate_counts (self, toc, fromc):
+        for i in range (len (self.count_names)):
+            toc[i] += fromc[i]
+
+    # Parse the list of variations after 'Schedule of variations:'.
+    # Return the number seen.
+    def parse_variations (self, filename, file):
+        num_variations = 0
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'could not parse variation list')
+            if line == '\n':
+                break
+            self.known_variations.add (line.strip())
+            num_variations += 1
+        return num_variations
+
+    # Parse from the first line after 'Running target ...' to the end
+    # of the run's summary.
+    def parse_run (self, filename, file, tool, variation, num_variations):
+        header = None
+        harness = None
+        segment = None
+        final_using = 0
+
+        # If this is the first run for this variation, add any text before
+        # the first harness to the header.
+        if not variation.header:
+            segment = Segment (filename, file.tell())
+            variation.header = segment
+
+        # Parse up until the first line of the summary.
+        if num_variations == 1:
+            end = '\t\t=== ' + tool.name + ' Summary ===\n'
+        else:
+            end = ('\t\t=== ' + tool.name + ' Summary for '
+                   + variation.name + ' ===\n')
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'no recognised summary line')
+            if line == end:
+                break
+
+            # Look for the start of a new harness.
+            if line.startswith ('Running ') and line.endswith (' ...\n'):
+                # Close off the current harness segment, if any.
+                if harness:
+                    segment.lines -= final_using
+                    harness.add_segment (first_key, segment)
+                name = line[len ('Running '):-len(' ...\n')]
+                harness = variation.get_harness (name)
+                segment = Segment (filename, file.tell())
+                first_key = None
+                final_using = 0
+                continue
+
+            # Record test results.  Associate the first test result with
+            # the harness segment, so that if a run for a particular harness
+            # has been split up, we can reassemble the individual segments
+            # in a sensible order.
+            match = self.result_re.match (line)
+            if match:
+                if not harness:
+                    self.fatal (filename, 'saw test result before harness name')
+                name = match.group (2)
+                # Ugly hack to get the right order for gfortran.
+                if name.startswith ('gfortran.dg/g77/'):
+                    name = 'h' + name
+                key = (name, len (harness.results))
+                harness.results.append ((key, line))
+                if not first_key and sort_logs:
+                    first_key = key
+
+            # 'Using ...' lines are only interesting in a header.  Splitting
+            # the test up into parallel runs leads to more 'Using ...' lines
+            # than there would be in a single log.
+            if line.startswith ('Using '):
+                final_using += 1
+            else:
+                final_using = 0
+
+            # Add other text to the current segment, if any.
+            if segment:
+                segment.lines += 1
+
+        # Close off the final harness segment, if any.
+        if harness:
+            segment.lines -= final_using
+            harness.add_segment (first_key, segment)
+
+        # Parse the rest of the summary (the '# of ' lines).
+        if len (variation.counts) == 0:
+            variation.counts = self.zero_counts()
+        while True:
+            before = file.tell()
+            line = file.readline()
+            if line == '':
+                break
+            if line == '\n':
+                continue
+            if not line.startswith ('# '):
+                file.seek (before)
+                break
+            found = False
+            for i in range (len (self.count_names)):
+                if line.startswith (self.count_names[i]):
+                    count = line[len (self.count_names[i]):-1].strip()
+                    variation.counts[i] += self.parse_int (filename, count)
+                    found = True
+                    break
+            if not found:
+                self.fatal (filename, 'unknown test result: ' + line[:-1])
+
+    # Parse an acats run, which uses a different format from dejagnu.
+    # We have just skipped over '=== acats configuration ==='.
+    def parse_acats_run (self, filename, file):
+        # Parse the preamble, which describes the configuration and logs
+        # the creation of support files.
+        record = (self.acats_premable == '')
+        if record:
+            self.acats_premable = '\t\t=== acats configuration ===\n'
+        while True:
+            line = file.readline()
+            if line == '':
+                self.fatal (filename, 'could not parse acats preamble')
+            if line == '\t\t=== acats tests ===\n':
+                break
+            if record:
+                self.acats_premable += line
+
+        # Parse the test results themselves, using a dummy variation name.
+        tool = self.get_tool ('acats')
+        variation = tool.get_variation ('none')
+        self.parse_run (filename, file, tool, variation, 1)
+
+        # Parse the failure list.
+        while True:
+            before = file.tell()
+            line = file.readline()
+            if line.startswith ('*** FAILURES: '):
+                self.acats_failures.append (line[len ('*** FAILURES: '):-1])
+                continue
+            file.seek (before)
+            break
+
+    # Parse the final summary at the end of a log in order to capture
+    # the version output that follows it.
+    def parse_final_summary (self, filename, file):
+        record = (self.version_output == '')
+        while True:
+            line = file.readline()
+            if line == '':
+                break
+            if line.startswith ('# of '):
+                continue
+            if record:
+                self.version_output += line
+            if line == '\n':
+                break
+
+    # Parse a .log or .sum file.
+    def parse_file (self, filename, file):
+        tool = None
+        target = None
+        num_variations = 1
+        while True:
+            line = file.readline()
+            if line == '':
+                return
+
+            # Parse the list of variations, which comes before the test
+            # runs themselves.
+            if line.startswith ('Schedule of variations:'):
+                num_variations = self.parse_variations (filename, file)
+                continue
+
+            # Parse a testsuite run for one tool/variation combination.
+            if line.startswith ('Running target '):
+                name = line[len ('Running target '):-1]
+                if not tool:
+                    self.fatal (filename, 'could not parse tool name')
+                if name not in self.known_variations:
+                    self.fatal (filename, 'unknown target: ' + name)
+                self.parse_run (filename, file, tool,
+                                tool.get_variation (name),
+                                num_variations)
+                # If there is only one variation then there is no separate
+                # summary for it.  Record any following version output.
+                if num_variations == 1:
+                    self.parse_final_summary (filename, file)
+                continue
+
+            # Parse the start line.  In the case where several files are being
+            # parsed, pick the one with the earliest time.
+            match = self.test_run_re.match (line)
+            if match:
+                time = self.parse_time (match.group (2))
+                if not self.start_line or self.start_line[0] > time:
+                    self.start_line = (time, line)
+                continue
+
+            # Parse the form used for native testing.
+            if line.startswith ('Native configuration is '):
+                self.native_line = line
+                continue
+
+            # Parse the target triplet.
+            if line.startswith ('Target is '):
+                self.target_line = line
+                continue
+
+            # Parse the host triplet.
+            if line.startswith ('Host   is '):
+                self.host_line = line
+                continue
+
+            # Parse the acats premable.
+            if line == '\t\t=== acats configuration ===\n':
+                self.parse_acats_run (filename, file)
+                continue
+
+            # Parse the tool name.
+            match = self.tool_re.match (line)
+            if match:
+                tool = self.get_tool (match.group (1))
+                continue
+
+            # Skip over the final summary (which we instead create from
+            # individual runs) and parse the version output.
+            if tool and line == '\t\t=== ' + tool.name + ' Summary ===\n':
+                if file.readline() != '\n':
+                    self.fatal (filename, 'expected blank line after summary')
+                self.parse_final_summary (filename, file)
+                continue
+
+            # Parse the completion line.  In the case where several files
+            # are being parsed, pick the one with the latest time.
+            match = self.completed_re.match (line)
+            if match:
+                time = self.parse_time (match.group (1))
+                if not self.end_line or self.end_line[0] < time:
+                    self.end_line = (time, line)
+                continue
+
+            # Sanity check to make sure that important text doesn't get
+            # dropped accidentally.
+            if strict and line.strip() != '':
+                self.fatal (filename, 'unrecognised line: ' + line[:-1])
+
+    # Output a segment of text.
+    def output_segment (self, segment):
+        with open (segment.filename, 'r') as file:
+            file.seek (segment.start)
+            for i in range (segment.lines):
+                sys.stdout.write (file.readline())
+
+    # Output a summary giving the number of times each type of result has
+    # been seen.
+    def output_summary (self, tool, counts):
+        for i in range (len (self.count_names)):
+            name = self.count_names[i]
+            # dejagnu only prints result types that were seen at least once,
+            # but acats always prints a number of unexpected failures.
+            if (counts[i] > 0
+                or (tool.name == 'acats'
+                    and name.startswith ('# of unexpected failures'))):
+                sys.stdout.write ('%s%d\n' % (name, counts[i]))
+
+    # Output unified .log or .sum information for a particular variation,
+    # with a summary at the end.
+    def output_variation (self, tool, variation):
+        self.output_segment (variation.header)
+        for harness in sorted (variation.harnesses.values()):
+            sys.stdout.write ('Running ' + harness.name + ' ...\n')
+            if self.do_sum:
+                # Keep the original test result order if there was only
+                # one segment for this harness.  This is needed for
+                # unsorted.exp, which has unusual test names.  Otherwise
+                # sort the tests by test filename.  If there are several
+                # subtests for the same test filename (such as 'compilation',
+                # 'test for excess errors', etc.) then keep the subtests
+                # in the original order.
+                if len (harness.segments) > 1:
+                    harness.results.sort()
+                for (key, line) in harness.results:
+                    sys.stdout.write (line)
+            else:
+                # Rearrange the log segments into test order (but without
+                # rearranging text within those segments).
+                for key in sorted (harness.segments.keys()):
+                    self.output_segment (harness.segments[key])
+                for segment in harness.empty:
+                    self.output_segment (segment)
+        if len (self.variations) > 1:
+            sys.stdout.write ('\t\t=== ' + tool.name + ' Summary for '
+                              + variation.name + ' ===\n\n')
+            self.output_summary (tool, variation.counts)
+
+    # Output unified .log or .sum information for a particular tool,
+    # with a summary at the end.
+    def output_tool (self, tool):
+        counts = self.zero_counts()
+        if tool.name == 'acats':
+            # acats doesn't use variations, so just output everything.
+            # It also has a different approach to whitespace.
+            sys.stdout.write ('\t\t=== ' + tool.name + ' tests ===\n')
+            for variation in tool.variations.values():
+                self.output_variation (tool, variation)
+                self.accumulate_counts (counts, variation.counts)
+            sys.stdout.write ('\t\t=== ' + tool.name + ' Summary ===\n')
+        else:
+            # Output the results in the usual dejagnu runtest format.
+            sys.stdout.write ('\n\t\t=== ' + tool.name + ' tests ===\n\n'
+                              'Schedule of variations:\n')
+            for name in self.variations:
+                if name in tool.variations:
+                    sys.stdout.write ('    ' + name + '\n')
+            sys.stdout.write ('\n')
+            for name in self.variations:
+                if name in tool.variations:
+                    variation = tool.variations[name]
+                    sys.stdout.write ('Running target '
+                                      + variation.name + '\n')
+                    self.output_variation (tool, variation)
+                    self.accumulate_counts (counts, variation.counts)
+            sys.stdout.write ('\n\t\t=== ' + tool.name + ' Summary ===\n\n')
+        self.output_summary (tool, counts)
+
+    def main (self):
+        self.parse_cmdline()
+        try:
+            # Parse the input files.
+            for filename in self.files:
+                with open (filename, 'r') as file:
+                    self.parse_file (filename, file)
+
+            # Decide what to output.
+            if len (self.variations) == 0:
+                self.variations = sorted (self.known_variations)
+            else:
+                for name in self.variations:
+                    if name not in self.known_variations:
+                        self.fatal (None, 'no results for ' + name)
+            if len (self.tools) == 0:
+                self.tools = sorted (self.runs.keys())
+
+            # Output the header.
+            if self.start_line:
+                sys.stdout.write (self.start_line[1])
+            sys.stdout.write (self.native_line)
+            sys.stdout.write (self.target_line)
+            sys.stdout.write (self.host_line)
+            sys.stdout.write (self.acats_premable)
+
+            # Output the main body.
+            for name in self.tools:
+                if name not in self.runs:
+                    self.fatal (None, 'no results for ' + name)
+                self.output_tool (self.runs[name])
+
+            # Output the footer.
+            if len (self.acats_failures) > 0:
+                sys.stdout.write ('*** FAILURES: '
+                                  + ' '.join (self.acats_failures) + '\n')
+            sys.stdout.write (self.version_output)
+            if self.end_line:
+                sys.stdout.write (self.end_line[1])
+        except IOError as e:
+            self.fatal (e.filename, e.strerror)
+
+Prog().main()
Index: contrib/dg-extract-results.sh
===================================================================
--- contrib/dg-extract-results.sh	2014-02-13 09:18:48.270283298 +0000
+++ contrib/dg-extract-results.sh	2014-02-13 10:07:24.846979564 +0000
@@ -28,6 +28,15 @@
 
 PROGNAME=dg-extract-results.sh
 
+# Try to use the python version if possible, since it tends to be faster.
+PYTHON_VER=`echo "$0" | sed 's/sh$/py/'`
+if test "$PYTHON_VER" != "$0" &&
+   test -f "$PYTHON_VER" &&
+   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) else 1)' \
+     > /dev/null 2> /dev/null; then
+  exec python $PYTHON_VER "$@"
+fi
+
 usage() {
   cat <<EOF >&2
 Usage: $PROGNAME [-t tool] [-l variant-list] [-L] sum-file ...

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-02-13  9:18 RFA: speeding up dg-extract-results.sh Richard Sandiford
  2014-05-03  7:34 ` Ping: " Richard Sandiford
@ 2014-05-03 20:07 ` Mike Stump
  2014-05-05 16:08 ` Jeff Law
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 18+ messages in thread
From: Mike Stump @ 2014-05-03 20:07 UTC (permalink / raw)
  To: Richard Sandiford; +Cc: gcc-patches@gcc.gnu.org Patches, gcc

On Feb 13, 2014, at 1:18 AM, Richard Sandiford <rdsandiford@googlemail.com> wrote:
> This patch tries to reduce that by providing an alternative single-script
> version.

> Python isn't yet required and I'm pretty sure this script needs 2.6
> or later.

> I'm also worried that the seek/tell stuff might not work on
> Windows.

> OK to install?

So, my intent is to approve this, but, I do want to give people a chance to argue against python if they care to.  I think python is at least as fine as tcl, or better, and this script is only used during test suite running, so the impact on the minimum requirements is only for those that run the test suite.  Additionally, falling back I think is nice in the short term (a year or two), if someone finds something disastrous, they can just flip it back to the old way.  If the new way is maintained and works well, I’d like to remove the old version.  This gives people a bit of time try it on windows and other platforms and get it working.  Seems like that should be sufficient.  If all goes well, after that, I’d like to remove the old way entirely.

Let’s give people 4 or 5 days to weigh in.  Absent objections, Ok.  If there are some, we’ll consider all the points raised and then approve or not.  Thanks for the work.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-02-13  9:18 RFA: speeding up dg-extract-results.sh Richard Sandiford
  2014-05-03  7:34 ` Ping: " Richard Sandiford
  2014-05-03 20:07 ` Mike Stump
@ 2014-05-05 16:08 ` Jeff Law
  2014-05-05 18:15   ` Mike Stump
       [not found] ` <CADnVucCA0ozSz=x3z8vexBOkWRj3pquKVuWsFq4xZre0CfxFyQ@mail.gmail.com>
  2014-05-24 11:17 ` Bernd Schmidt
  4 siblings, 1 reply; 18+ messages in thread
From: Jeff Law @ 2014-05-05 16:08 UTC (permalink / raw)
  To: gcc-patches, rdsandiford

On 02/13/14 02:18, Richard Sandiford wrote:
>
>      http://gcc.gnu.org/ml/gcc-testresults/2014-02/msg00025.html
>
> the script takes just over 5 hours to produce the gcc.log file.
Good grief...

>
> This patch tries to reduce that by providing an alternative single-script
> version.  I was torn between Python and Tcl, but given how most people
> tend to react to Tcl, I thought I'd better go for Python.  I wouldn't
> mind rewriting it in Tcl if that seems better though, not least because
> expect is already a prerequisite.
Can't argue with that reasoning.  There's been talk of rewriting the 
testsuite drivers to use python rather than tcl under the hood.  I'd be 
surprised if anyone in our community really prefers tcl over python.

It would mean a bit of pain for initial system bootstraps by the 
distributors, but those are relatively rare occurrences and they can 
always seed that process by building without testing to get the initial 
tools, when are then used to build python, then rebuild gcc to get the 
testing done.  I don't see this as being a major issue.


>
> Python isn't yet required and I'm pretty sure this script needs 2.6
> or later.  I'm also worried that the seek/tell stuff might not work on
> Windows.  The patch therefore gets dg-extract-results.sh to check the
> environment first and call into the python version if possible,
> otherwise it falls back on the current approach.  This also means
> that the patch is contained entirely within contrib/.  If this does
> indeed not work on Windows then we should either fix the python code
> (obviously preferred) or get dg-extract-results.sh to skip it on
> Windows for now.
Kai can probably test an answer questions about the Windows space.

> I checked that the outputs were otherwise identical for a set of
> mips64-linux-gnu, mipsisa64-sde-elf and x86_64-linux-gnu runs.  I also
> reran the acats tests with some nobbled testcases in order to test the
> failure paths there.
>
> Also bootstrapped & regression-tested on x86_64-linux-gnu.  OK to install?

>
> Thanks,
> Richard
>
>
> contrib/
> 	* dg-extract-results.py: New file.
> 	* dg-extract-results.sh: Use it if the environment seems suitable.
OK by me.

jeff

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-05 16:08 ` Jeff Law
@ 2014-05-05 18:15   ` Mike Stump
  2014-05-05 18:16     ` Jeff Law
  0 siblings, 1 reply; 18+ messages in thread
From: Mike Stump @ 2014-05-05 18:15 UTC (permalink / raw)
  To: Jeff Law; +Cc: gcc-patches@gcc.gnu.org Patches, Richard Sandiford

On May 5, 2014, at 9:08 AM, Jeff Law <law@redhat.com> wrote:
> It would mean a bit of pain for initial system bootstraps by the distributors,

I don’t expect any pain there.  System distributors usually have a distribution for the last release that includes binaries for all, including python, tcl and gcc.  They merely use these to then build the new software.  I don’t know of any that don’t start with binaries someplace.  :-)

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-05 18:15   ` Mike Stump
@ 2014-05-05 18:16     ` Jeff Law
  0 siblings, 0 replies; 18+ messages in thread
From: Jeff Law @ 2014-05-05 18:16 UTC (permalink / raw)
  To: Mike Stump; +Cc: gcc-patches@gcc.gnu.org Patches, Richard Sandiford

On 05/05/14 12:14, Mike Stump wrote:
> On May 5, 2014, at 9:08 AM, Jeff Law <law@redhat.com> wrote:
>> It would mean a bit of pain for initial system bootstraps by the
>> distributors,
>
> I donÂ’t expect any pain there.  System distributors usually have a
> distribution for the last release that includes binaries for all,
> including python, tcl and gcc.  They merely use these to then build
> the new software.  I donÂ’t know of any that donÂ’t start with binaries
> someplace.  :-)
I'm referring to new system bringups -- ie, there is no prior release to 
build from.  There's a couple of those in progress right now.  Package 
interdependencies are a real problem, but as I stated, this in case 
python is limited to the testsuite, which can be disabled during the 
initial building phases.

jeff

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Ping: RFA: speeding up dg-extract-results.sh
  2014-05-03  7:34 ` Ping: " Richard Sandiford
@ 2014-05-05 19:17   ` Jakub Jelinek
  2014-05-05 20:36     ` Richard Sandiford
  0 siblings, 1 reply; 18+ messages in thread
From: Jakub Jelinek @ 2014-05-05 19:17 UTC (permalink / raw)
  To: gcc-patches, rdsandiford

On Sat, May 03, 2014 at 08:34:07AM +0100, Richard Sandiford wrote:
> Ping for this old patch.  I didn't ping it before because I can imagine
> it wasn't a good idea at that stage in 4.8.  But I've been using it locally
> since and it really does make a big difference when testing lots of
> multilibs.

Have you tested that it creates bitwise identical output given the same
input from the old script on a couple of regtests (e.g. your 5 hour mips
case with many multilibs, and say common x86_64 one)?

	Jakub

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Ping: RFA: speeding up dg-extract-results.sh
  2014-05-05 19:17   ` Jakub Jelinek
@ 2014-05-05 20:36     ` Richard Sandiford
  0 siblings, 0 replies; 18+ messages in thread
From: Richard Sandiford @ 2014-05-05 20:36 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches

Jakub Jelinek <jakub@redhat.com> writes:
> On Sat, May 03, 2014 at 08:34:07AM +0100, Richard Sandiford wrote:
>> Ping for this old patch.  I didn't ping it before because I can imagine
>> it wasn't a good idea at that stage in 4.8.  But I've been using it locally
>> since and it really does make a big difference when testing lots of
>> multilibs.
>
> Have you tested that it creates bitwise identical output given the same
> input from the old script on a couple of regtests (e.g. your 5 hour mips
> case with many multilibs, and say common x86_64 one)?

Not bitwise, because there were some deliberate differences (see the
list in the quoted email).  But I did a diff and the only differences
were the expected ones.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
       [not found] ` <CADnVucCA0ozSz=x3z8vexBOkWRj3pquKVuWsFq4xZre0CfxFyQ@mail.gmail.com>
@ 2014-05-19 18:07   ` Richard Sandiford
  2014-05-19 18:49     ` Charles Baylis
  0 siblings, 1 reply; 18+ messages in thread
From: Richard Sandiford @ 2014-05-19 18:07 UTC (permalink / raw)
  To: Charles Baylis; +Cc: GCC Patches

Charles Baylis <charles.baylis@linaro.org> writes:
> On 13 February 2014 09:18, Richard Sandiford <rdsandiford@googlemail.com> wrote:
>> This patch tries to reduce that by providing an alternative single-script
>> version.  I was torn between Python and Tcl, but given how most people
>> tend to react to Tcl, I thought I'd better go for Python.  I wouldn't
>> mind rewriting it in Tcl if that seems better though, not least because
>> expect is already a prerequisite.
>
> This seems to have broken parallel "make check". I normally use "make
> -j8 check" but now that results in empty *.sum and *.log files.
>
> This is caused because the python script aborts with a fatal error
> ('saw test result before harness name') when it encounters this
> warning:
> WARNING: Assuming target board is the local machine (which is probably wrong).
> You may need to set your DEJAGNU environment variable.
>
> In my configuration (arm-unknown-linux-gnueabihf target,
> x86_64-linux-gnu host, running tests using qemu and binfmt_misc) the
> warning is bogus, so I think that make check should succeed despite
> it.
>
> I've attached a sample file which triggers the error with this command
> $ <gcc_path>/contrib/dg-extract-results.sh gcc.sum.sep
> gcc.sum.sep: saw test result before harness name
>
> The attached patch is a hack, intended for demonstration purposes. It
> isn't ideal as the 'You may need to set...' line is lost, but it
> allows my testing to work.

Sorry for the breakage.  I wanted to make the script as picky as I could
get away with though, so that results aren't lost accidentally.

Could you try the attached?

Thanks,
Richard

Index: contrib/dg-extract-results.py
===================================================================
--- contrib/dg-extract-results.py	(revision 210601)
+++ contrib/dg-extract-results.py	(working copy)
@@ -264,8 +264,12 @@
             # the harness segment, so that if a run for a particular harness
             # has been split up, we can reassemble the individual segments
             # in a sensible order.
+            #
+            # dejagnu sometimes issues warnings about the testing environment
+            # before running any tests.  Treat these as part of the header
+            # rather than as a test result.
             match = self.result_re.match (line)
-            if match:
+            if match and (harness or not line.startswith ('WARNING:')):
                 if not harness:
                     self.fatal (filename, 'saw test result before harness name')
                 name = match.group (2)

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-19 18:07   ` Richard Sandiford
@ 2014-05-19 18:49     ` Charles Baylis
  2014-05-20 10:09       ` Richard Sandiford
  0 siblings, 1 reply; 18+ messages in thread
From: Charles Baylis @ 2014-05-19 18:49 UTC (permalink / raw)
  To: Charles Baylis, GCC Patches, rdsandiford

On 19 May 2014 19:07, Richard Sandiford <rdsandiford@googlemail.com> wrote:
>
> Sorry for the breakage.  I wanted to make the script as picky as I could
> get away with though, so that results aren't lost accidentally.
>
> Could you try the attached?

That works for me.

Thanks for looking into it.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-19 18:49     ` Charles Baylis
@ 2014-05-20 10:09       ` Richard Sandiford
  0 siblings, 0 replies; 18+ messages in thread
From: Richard Sandiford @ 2014-05-20 10:09 UTC (permalink / raw)
  To: Charles Baylis; +Cc: GCC Patches

Charles Baylis <charles.baylis@linaro.org> writes:
> On 19 May 2014 19:07, Richard Sandiford <rdsandiford@googlemail.com> wrote:
>>
>> Sorry for the breakage.  I wanted to make the script as picky as I could
>> get away with though, so that results aren't lost accidentally.
>>
>> Could you try the attached?
>
> That works for me.

Thanks.  I also tested it on x86_64-linux-gnu.  Applied as obvious (I hope).

Richard

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-02-13  9:18 RFA: speeding up dg-extract-results.sh Richard Sandiford
                   ` (3 preceding siblings ...)
       [not found] ` <CADnVucCA0ozSz=x3z8vexBOkWRj3pquKVuWsFq4xZre0CfxFyQ@mail.gmail.com>
@ 2014-05-24 11:17 ` Bernd Schmidt
  2014-05-24 17:15   ` Mike Stump
  2014-05-25  9:35   ` Richard Sandiford
  4 siblings, 2 replies; 18+ messages in thread
From: Bernd Schmidt @ 2014-05-24 11:17 UTC (permalink / raw)
  To: gcc-patches, rdsandiford

On 02/13/2014 10:18 AM, Richard Sandiford wrote:
> contrib/
> 	* dg-extract-results.py: New file.
> 	* dg-extract-results.sh: Use it if the environment seems suitable.

I'm now seeing the following:

Traceback (most recent call last):
   File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in 
<module>
     Prog().main()
   File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
     self.output_tool (self.runs[name])
   File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in 
output_tool
     self.output_variation (tool, variation)
   File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in 
output_variation
     for harness in sorted (variation.harnesses.values()):
TypeError: unorderable types: HarnessRun() < HarnessRun()

$ /usr/bin/python --version
Python 3.3.3


Bernd

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-24 11:17 ` Bernd Schmidt
@ 2014-05-24 17:15   ` Mike Stump
  2014-05-24 17:53     ` Trevor Saunders
  2014-05-25  9:35   ` Richard Sandiford
  1 sibling, 1 reply; 18+ messages in thread
From: Mike Stump @ 2014-05-24 17:15 UTC (permalink / raw)
  To: Bernd Schmidt; +Cc: gcc-patches, Richard Sandiford

On May 24, 2014, at 4:17 AM, Bernd Schmidt <bernds_cb1@t-online.de> wrote:
> On 02/13/2014 10:18 AM, Richard Sandiford wrote:
>> contrib/
>> 	* dg-extract-results.py: New file.
>> 	* dg-extract-results.sh: Use it if the environment seems suitable.
> 
> I'm now seeing the following:
> 
> Traceback (most recent call last):
>  File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in <module>
>    Prog().main()
>  File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
>    self.output_tool (self.runs[name])
>  File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in output_tool
>    self.output_variation (tool, variation)
>  File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in output_variation
>    for harness in sorted (variation.harnesses.values()):
> TypeError: unorderable types: HarnessRun() < HarnessRun()
> 
> $ /usr/bin/python --version
> Python 3.3.3

Seems unfortunate…  I’d put in a, if on 3.3 or later, don’t use line until a python person can address it.

Can you try something like the below and see if it works better for you…  If it does, I’d approve it, if you would like to put it in.

Index: dg-extract-results.sh
===================================================================
--- dg-extract-results.sh	(revision 210894)
+++ dg-extract-results.sh	(working copy)
@@ -32,7 +32,7 @@ PROGNAME=dg-extract-results.sh
 PYTHON_VER=`echo "$0" | sed 's/sh$/py/'`
 if test "$PYTHON_VER" != "$0" &&
    test -f "$PYTHON_VER" &&
-   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) else 1)' \
+   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) and sys.version_info < (3, 3) else 1)' \
      > /dev/null 2> /dev/null; then
   exec python $PYTHON_VER "$@"
 fi

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-24 17:15   ` Mike Stump
@ 2014-05-24 17:53     ` Trevor Saunders
  0 siblings, 0 replies; 18+ messages in thread
From: Trevor Saunders @ 2014-05-24 17:53 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 2192 bytes --]

On Sat, May 24, 2014 at 10:14:54AM -0700, Mike Stump wrote:
> On May 24, 2014, at 4:17 AM, Bernd Schmidt <bernds_cb1@t-online.de> wrote:
> > On 02/13/2014 10:18 AM, Richard Sandiford wrote:
> >> contrib/
> >> 	* dg-extract-results.py: New file.
> >> 	* dg-extract-results.sh: Use it if the environment seems suitable.
> > 
> > I'm now seeing the following:
> > 
> > Traceback (most recent call last):
> >  File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in <module>
> >    Prog().main()
> >  File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
> >    self.output_tool (self.runs[name])
> >  File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in output_tool
> >    self.output_variation (tool, variation)
> >  File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in output_variation
> >    for harness in sorted (variation.harnesses.values()):
> > TypeError: unorderable types: HarnessRun() < HarnessRun()
> > 
> > $ /usr/bin/python --version
> > Python 3.3.3
> 
> Seems unfortunate…  I’d put in a, if on 3.3 or later, don’t use line until a python person can address it.

it looks like __cmp__ which the named class implements doesn't work in
python3, and instead you get to implement __eq__, __ne__, __gt__,
__ge__, __lt__, and __le__ or whatever subset of those is enough to make
sorted happy.

Trev

> 
> Can you try something like the below and see if it works better for you…  If it does, I’d approve it, if you would like to put it in.
> 
> Index: dg-extract-results.sh
> ===================================================================
> --- dg-extract-results.sh	(revision 210894)
> +++ dg-extract-results.sh	(working copy)
> @@ -32,7 +32,7 @@ PROGNAME=dg-extract-results.sh
>  PYTHON_VER=`echo "$0" | sed 's/sh$/py/'`
>  if test "$PYTHON_VER" != "$0" &&
>     test -f "$PYTHON_VER" &&
> -   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) else 1)' \
> +   python -c 'import sys; sys.exit (0 if sys.version_info >= (2, 6) and sys.version_info < (3, 3) else 1)' \
>       > /dev/null 2> /dev/null; then
>    exec python $PYTHON_VER "$@"
>  fi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-24 11:17 ` Bernd Schmidt
  2014-05-24 17:15   ` Mike Stump
@ 2014-05-25  9:35   ` Richard Sandiford
  2014-06-12 15:53     ` Bernd Schmidt
  1 sibling, 1 reply; 18+ messages in thread
From: Richard Sandiford @ 2014-05-25  9:35 UTC (permalink / raw)
  To: Bernd Schmidt; +Cc: gcc-patches

Bernd Schmidt <bernds_cb1@t-online.de> writes:
> On 02/13/2014 10:18 AM, Richard Sandiford wrote:
>> contrib/
>> 	* dg-extract-results.py: New file.
>> 	* dg-extract-results.sh: Use it if the environment seems suitable.
>
> I'm now seeing the following:
>
> Traceback (most recent call last):
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in 
> <module>
>      Prog().main()
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
>      self.output_tool (self.runs[name])
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in 
> output_tool
>      self.output_variation (tool, variation)
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in 
> output_variation
>      for harness in sorted (variation.harnesses.values()):
> TypeError: unorderable types: HarnessRun() < HarnessRun()
>
> $ /usr/bin/python --version
> Python 3.3.3

Sorry, thought I'd tested it with python3, but obviously not.
I've applied the fix below after testing that it didn't change the
output for python 2.6 and python 2.7.

Thanks,
Richard


contrib/
	* dg-extract-results.py (Named): Remove __cmp__ method.
	(output_variation): Use a key to sort variation.harnesses.

Index: contrib/dg-extract-results.py
===================================================================
--- contrib/dg-extract-results.py	2014-05-25 10:32:31.822202922 +0100
+++ contrib/dg-extract-results.py	2014-05-25 10:33:30.811758127 +0100
@@ -11,6 +11,7 @@ import sys
 import getopt
 import re
 from datetime import datetime
+from operator import attrgetter
 
 # True if unrecognised lines should cause a fatal error.  Might want to turn
 # this on by default later.
@@ -24,9 +25,6 @@ class Named:
     def __init__ (self, name):
         self.name = name
 
-    def __cmp__ (self, other):
-        return cmp (self.name, other.name)
-
 class ToolRun (Named):
     def __init__ (self, name):
         Named.__init__ (self, name)
@@ -480,7 +478,8 @@ class Prog:
     # with a summary at the end.
     def output_variation (self, tool, variation):
         self.output_segment (variation.header)
-        for harness in sorted (variation.harnesses.values()):
+        for harness in sorted (variation.harnesses.values(),
+                               key = attrgetter ('name')):
             sys.stdout.write ('Running ' + harness.name + ' ...\n')
             if self.do_sum:
                 # Keep the original test result order if there was only

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-05-25  9:35   ` Richard Sandiford
@ 2014-06-12 15:53     ` Bernd Schmidt
  2014-06-12 16:32       ` Mike Stump
  2014-06-14  9:50       ` Richard Sandiford
  0 siblings, 2 replies; 18+ messages in thread
From: Bernd Schmidt @ 2014-06-12 15:53 UTC (permalink / raw)
  To: gcc-patches, rdsandiford

On 05/25/2014 11:35 AM, Richard Sandiford wrote:
> Bernd Schmidt <bernds_cb1@t-online.de> writes:
>> On 02/13/2014 10:18 AM, Richard Sandiford wrote:
>>> contrib/
>>> 	* dg-extract-results.py: New file.
>>> 	* dg-extract-results.sh: Use it if the environment seems suitable.
>>
>> I'm now seeing the following:
>>
>> Traceback (most recent call last):
>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in
>> <module>
>>       Prog().main()
>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
>>       self.output_tool (self.runs[name])
>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in
>> output_tool
>>       self.output_variation (tool, variation)
>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in
>> output_variation
>>       for harness in sorted (variation.harnesses.values()):
>> TypeError: unorderable types: HarnessRun() < HarnessRun()
>>
>> $ /usr/bin/python --version
>> Python 3.3.3
>
> Sorry, thought I'd tested it with python3, but obviously not.
> I've applied the fix below after testing that it didn't change the
> output for python 2.6 and python 2.7.

I've recently been trying to add ada to my set of tested languages, and 
I now encounter the following:

Traceback (most recent call last):
   File "../../git/gcc/../contrib/dg-extract-results.py", line 580, in 
<module>
     Prog().main()
   File "../../git/gcc/../contrib/dg-extract-results.py", line 544, in main
     self.parse_file (filename, file)
   File "../../git/gcc/../contrib/dg-extract-results.py", line 427, in 
parse_file
     self.parse_acats_run (filename, file)
   File "../../git/gcc/../contrib/dg-extract-results.py", line 342, in 
parse_acats_run
     self.parse_run (filename, file, tool, variation, 1)
   File "../../git/gcc/../contrib/dg-extract-results.py", line 242, in 
parse_run
     line = file.readline()
   File "/usr/lib64/python3.3/codecs.py", line 301, in decode
     (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe1 in position 
5227: invalid continuation byte


Bernd

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-06-12 15:53     ` Bernd Schmidt
@ 2014-06-12 16:32       ` Mike Stump
  2014-06-14  9:50       ` Richard Sandiford
  1 sibling, 0 replies; 18+ messages in thread
From: Mike Stump @ 2014-06-12 16:32 UTC (permalink / raw)
  To: Bernd Schmidt; +Cc: gcc-patches, Richard Sandiford

On Jun 12, 2014, at 8:53 AM, Bernd Schmidt <bernds@codesourcery.com> wrote:
> I've recently been trying to add ada to my set of tested languages, and I now encounter the following:
> 
>  File "../../git/gcc/../contrib/dg-extract-results.py", line 242, in parse_run
>    line = file.readline()
>  File "/usr/lib64/python3.3/codecs.py", line 301, in decode
>    (result, consumed) = self._buffer_decode(data, self.errors, final)
> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe1 in position 5227: invalid continuation byte

In the old skool world, these are byte sequences that end in ‘\n’…  no decoding errors are possible…  well, maybe one, if you tried to put ‘\0’ in the stream.  :-(  Maybe a LANG/LC type person can suggest an environment variable to set that would make things happier, else we’re down to a python person to solve from that side.  My knee jerk would be LANG=c for the entire test suite run...

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: RFA: speeding up dg-extract-results.sh
  2014-06-12 15:53     ` Bernd Schmidt
  2014-06-12 16:32       ` Mike Stump
@ 2014-06-14  9:50       ` Richard Sandiford
  1 sibling, 0 replies; 18+ messages in thread
From: Richard Sandiford @ 2014-06-14  9:50 UTC (permalink / raw)
  To: Bernd Schmidt; +Cc: gcc-patches

Bernd Schmidt <bernds@codesourcery.com> writes:
> On 05/25/2014 11:35 AM, Richard Sandiford wrote:
>> Bernd Schmidt <bernds_cb1@t-online.de> writes:
>>> On 02/13/2014 10:18 AM, Richard Sandiford wrote:
>>>> contrib/
>>>> 	* dg-extract-results.py: New file.
>>>> 	* dg-extract-results.sh: Use it if the environment seems suitable.
>>>
>>> I'm now seeing the following:
>>>
>>> Traceback (most recent call last):
>>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 581, in
>>> <module>
>>>       Prog().main()
>>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 569, in main
>>>       self.output_tool (self.runs[name])
>>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 534, in
>>> output_tool
>>>       self.output_variation (tool, variation)
>>>     File "../../git/gcc/../contrib/dg-extract-results.py", line 483, in
>>> output_variation
>>>       for harness in sorted (variation.harnesses.values()):
>>> TypeError: unorderable types: HarnessRun() < HarnessRun()
>>>
>>> $ /usr/bin/python --version
>>> Python 3.3.3
>>
>> Sorry, thought I'd tested it with python3, but obviously not.
>> I've applied the fix below after testing that it didn't change the
>> output for python 2.6 and python 2.7.
>
> I've recently been trying to add ada to my set of tested languages, and 
> I now encounter the following:
>
> Traceback (most recent call last):
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 580, in 
> <module>
>      Prog().main()
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 544, in main
>      self.parse_file (filename, file)
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 427, in 
> parse_file
>      self.parse_acats_run (filename, file)
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 342, in 
> parse_acats_run
>      self.parse_run (filename, file, tool, variation, 1)
>    File "../../git/gcc/../contrib/dg-extract-results.py", line 242, in 
> parse_run
>      line = file.readline()
>    File "/usr/lib64/python3.3/codecs.py", line 301, in decode
>      (result, consumed) = self._buffer_decode(data, self.errors, final)
> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe1 in position 
> 5227: invalid continuation byte

Bah.  I'm seriously beginning to regret choosing Python for this.
Getting code to work with both Python 2 and Python 3 is like the bad
old days of getting stuff to work with both K&R and ANSI C.

I see the weird character is coming from C250002, which is specifically
testing that some arbitrary byte above 127 can be used in identifier names.
The actual choice of byte or its meaning in the locale encoding doesn't
seem to be relevant.

I committed the fix below after checking it against an Ada log for
both python2 and python3.

Thanks,
Richard


contrib/
	* dg-extract-results.py: For Python 3, force sys.stdout to handle
	surrogate escape sequences.
	(safe_open): New function.
	(output_segment, main): Use it.

Index: contrib/dg-extract-results.py
===================================================================
--- contrib/dg-extract-results.py	2014-06-14 10:17:41.698438403 +0100
+++ contrib/dg-extract-results.py	2014-06-14 10:45:12.586546139 +0100
@@ -10,6 +10,7 @@
 import sys
 import getopt
 import re
+import io
 from datetime import datetime
 from operator import attrgetter
 
@@ -21,6 +22,18 @@ strict = False
 # they should keep the original order.
 sort_logs = True
 
+# A version of open() that is safe against whatever binary output
+# might be added to the log.
+def safe_open (self, filename):
+    if sys.version_info >= (3, 0):
+        return open (filename, 'r', errors = 'surrogateescape')
+    return open (filename, 'r')
+
+# Force stdout to handle escape sequences from a safe_open file.
+if sys.version_info >= (3, 0):
+    sys.stdout = io.TextIOWrapper (sys.stdout.buffer,
+                                   errors = 'surrogateescape')
+
 class Named:
     def __init__ (self, name):
         self.name = name
@@ -457,7 +470,7 @@ class Prog:
 
     # Output a segment of text.
     def output_segment (self, segment):
-        with open (segment.filename, 'r') as file:
+        with safe_open (segment.filename) as file:
             file.seek (segment.start)
             for i in range (segment.lines):
                 sys.stdout.write (file.readline())
@@ -540,7 +553,7 @@ class Prog:
         try:
             # Parse the input files.
             for filename in self.files:
-                with open (filename, 'r') as file:
+                with safe_open (filename) as file:
                     self.parse_file (filename, file)
 
             # Decide what to output.

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2014-06-14  9:50 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-02-13  9:18 RFA: speeding up dg-extract-results.sh Richard Sandiford
2014-05-03  7:34 ` Ping: " Richard Sandiford
2014-05-05 19:17   ` Jakub Jelinek
2014-05-05 20:36     ` Richard Sandiford
2014-05-03 20:07 ` Mike Stump
2014-05-05 16:08 ` Jeff Law
2014-05-05 18:15   ` Mike Stump
2014-05-05 18:16     ` Jeff Law
     [not found] ` <CADnVucCA0ozSz=x3z8vexBOkWRj3pquKVuWsFq4xZre0CfxFyQ@mail.gmail.com>
2014-05-19 18:07   ` Richard Sandiford
2014-05-19 18:49     ` Charles Baylis
2014-05-20 10:09       ` Richard Sandiford
2014-05-24 11:17 ` Bernd Schmidt
2014-05-24 17:15   ` Mike Stump
2014-05-24 17:53     ` Trevor Saunders
2014-05-25  9:35   ` Richard Sandiford
2014-06-12 15:53     ` Bernd Schmidt
2014-06-12 16:32       ` Mike Stump
2014-06-14  9:50       ` Richard Sandiford

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).