[Zope3-checkins] SVN: zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/ - Moved the actual formatter definitions to its own file.

Christian Theune ct at gocept.com
Sat May 3 10:06:19 EDT 2008


Log message for revision 86214:
  - Moved the actual formatter definitions to its own file.
  - Fix the way the test runner calls itself: don't use __file__ but use the
    original sys.argv[0].
  

Changed:
  U   zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/__init__.py
  A   zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/formatter.py
  U   zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/testrunner-debugging-layer-setup.test

-=-
Modified: zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/__init__.py
===================================================================
--- zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/__init__.py	2008-05-03 13:58:31 UTC (rev 86213)
+++ zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/__init__.py	2008-05-03 14:06:18 UTC (rev 86214)
@@ -38,6 +38,8 @@
 import types
 import unittest
 
+from zope.testing.testrunner.formatter import OutputFormatter, ColorfulOutputFormatter
+from zope.testing.testrunner.formatter import terminal_has_colors
 
 available_profilers = {}
 
@@ -250,612 +252,6 @@
     return False
 
 
-doctest_template = """
-File "%s", line %s, in %s
-
-%s
-Want:
-%s
-Got:
-%s
-"""
-
-
-def tigetnum(attr, default=None):
-    """Return a value from the terminfo database.
-
-    Terminfo is used on Unix-like systems to report various terminal attributes
-    (such as width, height or the number of supported colors).
-
-    Returns ``default`` when the ``curses`` module is not available, or when
-    sys.stdout is not a terminal.
-    """
-    try:
-        import curses
-    except ImportError:
-        # avoid reimporting a broken module in python 2.3
-        sys.modules['curses'] = None
-    else:
-        try:
-            curses.setupterm()
-        except (curses.error, TypeError):
-            # You get curses.error when $TERM is set to an unknown name
-            # You get TypeError when sys.stdout is not a real file object
-            # (e.g. in unit tests that use various wrappers).
-            pass
-        else:
-            return curses.tigetnum(attr)
-    return default
-
-
-class OutputFormatter(object):
-    """Test runner output formatter."""
-
-    # Implementation note: be careful about printing stuff to sys.stderr.
-    # It is used for interprocess communication between the parent and the
-    # child test runner, when you run some test layers in a subprocess.
-    # resume_layer() reasigns sys.stderr for this reason, but be careful
-    # and don't store the original one in __init__ or something.
-
-    max_width = 80
-
-    def __init__(self, options):
-        self.options = options
-        self.last_width = 0
-        self.compute_max_width()
-
-    progress = property(lambda self: self.options.progress)
-    verbose = property(lambda self: self.options.verbose)
-
-    def compute_max_width(self):
-        """Try to determine the terminal width."""
-        # Note that doing this every time is more test friendly.
-        self.max_width = tigetnum('cols', self.max_width)
-
-    def getShortDescription(self, test, room):
-        """Return a description of a test that fits in ``room`` characters."""
-        room -= 1
-        s = str(test)
-        if len(s) > room:
-            pos = s.find(" (")
-            if pos >= 0:
-                w = room - (pos + 5)
-                if w < 1:
-                    # first portion (test method name) is too long
-                    s = s[:room-3] + "..."
-                else:
-                    pre = s[:pos+2]
-                    post = s[-w:]
-                    s = "%s...%s" % (pre, post)
-            else:
-                w = room - 4
-                s = '... ' + s[-w:]
-
-        return ' ' + s[:room]
-
-    def info(self, message):
-        """Print an informative message."""
-        print message
-
-    def info_suboptimal(self, message):
-        """Print an informative message about losing some of the features.
-
-        For example, when you run some tests in a subprocess, you lose the
-        ability to use the debugger.
-        """
-        print message
-
-    def error(self, message):
-        """Report an error."""
-        print message
-
-    def error_with_banner(self, message):
-        """Report an error with a big ASCII banner."""
-        print
-        print '*'*70
-        self.error(message)
-        print '*'*70
-        print
-
-    def profiler_stats(self, stats):
-        """Report profiler stats."""
-        stats.print_stats(50)
-
-    def import_errors(self, import_errors):
-        """Report test-module import errors (if any)."""
-        if import_errors:
-            print "Test-module import failures:"
-            for error in import_errors:
-                self.print_traceback("Module: %s\n" % error.module,
-                                     error.exc_info),
-            print
-
-    def tests_with_errors(self, errors):
-        """Report names of tests with errors (if any)."""
-        if errors:
-            print
-            print "Tests with errors:"
-            for test, exc_info in errors:
-                print "  ", test
-
-    def tests_with_failures(self, failures):
-        """Report names of tests with failures (if any)."""
-        if failures:
-            print
-            print "Tests with failures:"
-            for test, exc_info in failures:
-                print "  ", test
-
-    def modules_with_import_problems(self, import_errors):
-        """Report names of modules with import problems (if any)."""
-        if import_errors:
-            print
-            print "Test-modules with import problems:"
-            for test in import_errors:
-                print "  " + test.module
-
-    def format_seconds(self, n_seconds):
-        """Format a time in seconds."""
-        if n_seconds >= 60:
-            n_minutes, n_seconds = divmod(n_seconds, 60)
-            return "%d minutes %.3f seconds" % (n_minutes, n_seconds)
-        else:
-            return "%.3f seconds" % n_seconds
-
-    def format_seconds_short(self, n_seconds):
-        """Format a time in seconds (short version)."""
-        return "%.3f s" % n_seconds
-
-    def summary(self, n_tests, n_failures, n_errors, n_seconds):
-        """Summarize the results of a single test layer."""
-        print ("  Ran %s tests with %s failures and %s errors in %s."
-               % (n_tests, n_failures, n_errors,
-                  self.format_seconds(n_seconds)))
-
-    def totals(self, n_tests, n_failures, n_errors, n_seconds):
-        """Summarize the results of all layers."""
-        print ("Total: %s tests, %s failures, %s errors in %s."
-               % (n_tests, n_failures, n_errors,
-                  self.format_seconds(n_seconds)))
-
-    def list_of_tests(self, tests, layer_name):
-        """Report a list of test names."""
-        print "Listing %s tests:" % layer_name
-        for test in tests:
-            print ' ', test
-
-    def garbage(self, garbage):
-        """Report garbage generated by tests."""
-        if garbage:
-            print "Tests generated new (%d) garbage:" % len(garbage)
-            print garbage
-
-    def test_garbage(self, test, garbage):
-        """Report garbage generated by a test."""
-        if garbage:
-            print "The following test left garbage:"
-            print test
-            print garbage
-
-    def test_threads(self, test, new_threads):
-        """Report threads left behind by a test."""
-        if new_threads:
-            print "The following test left new threads behind:"
-            print test
-            print "New thread(s):", new_threads
-
-    def refcounts(self, rc, prev):
-        """Report a change in reference counts."""
-        print "  sys refcount=%-8d change=%-6d" % (rc, rc - prev)
-
-    def detailed_refcounts(self, track, rc, prev):
-        """Report a change in reference counts, with extra detail."""
-        print ("  sum detail refcount=%-8d"
-               " sys refcount=%-8d"
-               " change=%-6d"
-               % (track.n, rc, rc - prev))
-        track.output()
-
-    def start_set_up(self, layer_name):
-        """Report that we're setting up a layer.
-
-        The next output operation should be stop_set_up().
-        """
-        print "  Set up %s" % layer_name,
-        sys.stdout.flush()
-
-    def stop_set_up(self, seconds):
-        """Report that we've set up a layer.
-
-        Should be called right after start_set_up().
-        """
-        print "in %s." % self.format_seconds(seconds)
-
-    def start_tear_down(self, layer_name):
-        """Report that we're tearing down a layer.
-
-        The next output operation should be stop_tear_down() or
-        tear_down_not_supported().
-        """
-        print "  Tear down %s" % layer_name,
-        sys.stdout.flush()
-
-    def stop_tear_down(self, seconds):
-        """Report that we've tore down a layer.
-
-        Should be called right after start_tear_down().
-        """
-        print "in %s." % self.format_seconds(seconds)
-
-    def tear_down_not_supported(self):
-        """Report that we could not tear down a layer.
-
-        Should be called right after start_tear_down().
-        """
-        print "... not supported"
-
-    def start_test(self, test, tests_run, total_tests):
-        """Report that we're about to run a test.
-
-        The next output operation should be test_success(), test_error(), or
-        test_failure().
-        """
-        self.test_width = 0
-        if self.progress:
-            if self.last_width:
-                sys.stdout.write('\r' + (' ' * self.last_width) + '\r')
-
-            s = "    %d/%d (%.1f%%)" % (tests_run, total_tests,
-                                        tests_run * 100.0 / total_tests)
-            sys.stdout.write(s)
-            self.test_width += len(s)
-            if self.verbose == 1:
-                room = self.max_width - self.test_width - 1
-                s = self.getShortDescription(test, room)
-                sys.stdout.write(s)
-                self.test_width += len(s)
-
-        elif self.verbose == 1:
-            sys.stdout.write('.' * test.countTestCases())
-
-        if self.verbose > 1:
-            s = str(test)
-            sys.stdout.write(' ')
-            sys.stdout.write(s)
-            self.test_width += len(s) + 1
-
-        sys.stdout.flush()
-
-    def test_success(self, test, seconds):
-        """Report that a test was successful.
-
-        Should be called right after start_test().
-
-        The next output operation should be stop_test().
-        """
-        if self.verbose > 2:
-            s = " (%s)" % self.format_seconds_short(seconds)
-            sys.stdout.write(s)
-            self.test_width += len(s) + 1
-
-    def test_error(self, test, seconds, exc_info):
-        """Report that an error occurred while running a test.
-
-        Should be called right after start_test().
-
-        The next output operation should be stop_test().
-        """
-        if self.verbose > 2:
-            print " (%s)" % self.format_seconds_short(seconds)
-        print
-        self.print_traceback("Error in test %s" % test, exc_info)
-        self.test_width = self.last_width = 0
-
-    def test_failure(self, test, seconds, exc_info):
-        """Report that a test failed.
-
-        Should be called right after start_test().
-
-        The next output operation should be stop_test().
-        """
-        if self.verbose > 2:
-            print " (%s)" % self.format_seconds_short(seconds)
-        print
-        self.print_traceback("Failure in test %s" % test, exc_info)
-        self.test_width = self.last_width = 0
-
-    def print_traceback(self, msg, exc_info):
-        """Report an error with a traceback."""
-        print
-        print msg
-        print self.format_traceback(exc_info)
-
-    def format_traceback(self, exc_info):
-        """Format the traceback."""
-        v = exc_info[1]
-        if isinstance(v, doctest.DocTestFailureException):
-            tb = v.args[0]
-        elif isinstance(v, doctest.DocTestFailure):
-            tb = doctest_template % (
-                v.test.filename,
-                v.test.lineno + v.example.lineno + 1,
-                v.test.name,
-                v.example.source,
-                v.example.want,
-                v.got,
-                )
-        else:
-            tb = "".join(traceback.format_exception(*exc_info))
-        return tb
-
-    def stop_test(self, test):
-        """Clean up the output state after a test."""
-        if self.progress:
-            self.last_width = self.test_width
-        elif self.verbose > 1:
-            print
-        sys.stdout.flush()
-
-    def stop_tests(self):
-        """Clean up the output state after a collection of tests."""
-        if self.progress and self.last_width:
-            sys.stdout.write('\r' + (' ' * self.last_width) + '\r')
-        if self.verbose == 1 or self.progress:
-            print
-
-
-class ColorfulOutputFormatter(OutputFormatter):
-    """Output formatter that uses ANSI color codes.
-
-    Like syntax highlighting in your text editor, colorizing
-    test failures helps the developer.
-    """
-
-    # These colors are carefully chosen to have enough contrast
-    # on terminals with both black and white background.
-    colorscheme = {'normal': 'normal',
-                   'default': 'default',
-                   'info': 'normal',
-                   'suboptimal-behaviour': 'magenta',
-                   'error': 'brightred',
-                   'number': 'green',
-                   'slow-test': 'brightmagenta',
-                   'ok-number': 'green',
-                   'error-number': 'brightred',
-                   'filename': 'lightblue',
-                   'lineno': 'lightred',
-                   'testname': 'lightcyan',
-                   'failed-example': 'cyan',
-                   'expected-output': 'green',
-                   'actual-output': 'red',
-                   'character-diffs': 'magenta',
-                   'diff-chunk': 'magenta',
-                   'exception': 'red'}
-
-    # Map prefix character to color in diff output.  This handles ndiff and
-    # udiff correctly, but not cdiff.  In cdiff we ought to highlight '!' as
-    # expected-output until we see a '-', then highlight '!' as actual-output,
-    # until we see a '*', then switch back to highlighting '!' as
-    # expected-output.  Nevertheless, coloried cdiffs are reasonably readable,
-    # so I'm not going to fix this.
-    #   -- mgedmin
-    diff_color = {'-': 'expected-output',
-                  '+': 'actual-output',
-                  '?': 'character-diffs',
-                  '@': 'diff-chunk',
-                  '*': 'diff-chunk',
-                  '!': 'actual-output',}
-
-    prefixes = [('dark', '0;'),
-                ('light', '1;'),
-                ('bright', '1;'),
-                ('bold', '1;'),]
-
-    colorcodes = {'default': 0, 'normal': 0,
-                  'black': 30,
-                  'red': 31,
-                  'green': 32,
-                  'brown': 33, 'yellow': 33,
-                  'blue': 34,
-                  'magenta': 35,
-                  'cyan': 36,
-                  'grey': 37, 'gray': 37, 'white': 37}
-
-    slow_test_threshold = 10.0 # seconds
-
-    def color_code(self, color):
-        """Convert a color description (e.g. 'lightgray') to a terminal code."""
-        prefix_code = ''
-        for prefix, code in self.prefixes:
-            if color.startswith(prefix):
-                color = color[len(prefix):]
-                prefix_code = code
-                break
-        color_code = self.colorcodes[color]
-        return '\033[%s%sm' % (prefix_code, color_code)
-
-    def color(self, what):
-        """Pick a named color from the color scheme"""
-        return self.color_code(self.colorscheme[what])
-
-    def colorize(self, what, message, normal='normal'):
-        """Wrap message in color."""
-        return self.color(what) + message + self.color(normal)
-
-    def error_count_color(self, n):
-        """Choose a color for the number of errors."""
-        if n:
-            return self.color('error-number')
-        else:
-            return self.color('ok-number')
-
-    def info(self, message):
-        """Print an informative message."""
-        print self.colorize('info', message)
-
-    def info_suboptimal(self, message):
-        """Print an informative message about losing some of the features.
-
-        For example, when you run some tests in a subprocess, you lose the
-        ability to use the debugger.
-        """
-        print self.colorize('suboptimal-behaviour', message)
-
-    def error(self, message):
-        """Report an error."""
-        print self.colorize('error', message)
-
-    def error_with_banner(self, message):
-        """Report an error with a big ASCII banner."""
-        print
-        print self.colorize('error', '*'*70)
-        self.error(message)
-        print self.colorize('error', '*'*70)
-        print
-
-    def tear_down_not_supported(self):
-        """Report that we could not tear down a layer.
-
-        Should be called right after start_tear_down().
-        """
-        print "...", self.colorize('suboptimal-behaviour', "not supported")
-
-    def format_seconds(self, n_seconds, normal='normal'):
-        """Format a time in seconds."""
-        if n_seconds >= 60:
-            n_minutes, n_seconds = divmod(n_seconds, 60)
-            return "%s minutes %s seconds" % (
-                        self.colorize('number', '%d' % n_minutes, normal),
-                        self.colorize('number', '%.3f' % n_seconds, normal))
-        else:
-            return "%s seconds" % (
-                        self.colorize('number', '%.3f' % n_seconds, normal))
-
-    def format_seconds_short(self, n_seconds):
-        """Format a time in seconds (short version)."""
-        if n_seconds >= self.slow_test_threshold:
-            color = 'slow-test'
-        else:
-            color = 'number'
-        return self.colorize(color, "%.3f s" % n_seconds)
-
-    def summary(self, n_tests, n_failures, n_errors, n_seconds):
-        """Summarize the results."""
-        sys.stdout.writelines([
-            self.color('info'), '  Ran ',
-            self.color('number'), str(n_tests),
-            self.color('info'), ' tests with ',
-            self.error_count_color(n_failures), str(n_failures),
-            self.color('info'), ' failures and ',
-            self.error_count_color(n_errors), str(n_errors),
-            self.color('info'), ' errors in ',
-            self.format_seconds(n_seconds, 'info'), '.',
-            self.color('normal'), '\n'])
-
-    def totals(self, n_tests, n_failures, n_errors, n_seconds):
-        """Report totals (number of tests, failures, and errors)."""
-        sys.stdout.writelines([
-            self.color('info'), 'Total: ',
-            self.color('number'), str(n_tests),
-            self.color('info'), ' tests, ',
-            self.error_count_color(n_failures), str(n_failures),
-            self.color('info'), ' failures, ',
-            self.error_count_color(n_errors), str(n_errors),
-            self.color('info'), ' errors in ',
-            self.format_seconds(n_seconds, 'info'), '.',
-            self.color('normal'), '\n'])
-
-    def print_traceback(self, msg, exc_info):
-        """Report an error with a traceback."""
-        print
-        print self.colorize('error', msg)
-        v = exc_info[1]
-        if isinstance(v, doctest.DocTestFailureException):
-            self.print_doctest_failure(v.args[0])
-        elif isinstance(v, doctest.DocTestFailure):
-            # I don't think these are ever used... -- mgedmin
-            tb = self.format_traceback(exc_info)
-            print tb
-        else:
-            tb = self.format_traceback(exc_info)
-            self.print_colorized_traceback(tb)
-
-    def print_doctest_failure(self, formatted_failure):
-        """Report a doctest failure.
-
-        ``formatted_failure`` is a string -- that's what
-        DocTestSuite/DocFileSuite gives us.
-        """
-        color_of_indented_text = 'normal'
-        colorize_diff = False
-        for line in formatted_failure.splitlines():
-            if line.startswith('File '):
-                m = re.match(r'File "(.*)", line (\d*), in (.*)$', line)
-                if m:
-                    filename, lineno, test = m.groups()
-                    sys.stdout.writelines([
-                        self.color('normal'), 'File "',
-                        self.color('filename'), filename,
-                        self.color('normal'), '", line ',
-                        self.color('lineno'), lineno,
-                        self.color('normal'), ', in ',
-                        self.color('testname'), test,
-                        self.color('normal'), '\n'])
-                else:
-                    print line
-            elif line.startswith('    '):
-                if colorize_diff and len(line) > 4:
-                    color = self.diff_color.get(line[4], color_of_indented_text)
-                    print self.colorize(color, line)
-                else:
-                    print self.colorize(color_of_indented_text, line)
-            else:
-                colorize_diff = False
-                if line.startswith('Failed example'):
-                    color_of_indented_text = 'failed-example'
-                elif line.startswith('Expected:'):
-                    color_of_indented_text = 'expected-output'
-                elif line.startswith('Got:'):
-                    color_of_indented_text = 'actual-output'
-                elif line.startswith('Exception raised:'):
-                    color_of_indented_text = 'exception'
-                elif line.startswith('Differences '):
-                    color_of_indented_text = 'normal'
-                    colorize_diff = True
-                else:
-                    color_of_indented_text = 'normal'
-                print line
-        print
-
-    def print_colorized_traceback(self, formatted_traceback):
-        """Report a test failure.
-
-        ``formatted_traceback`` is a string.
-        """
-        for line in formatted_traceback.splitlines():
-            if line.startswith('  File'):
-                m = re.match(r'  File "(.*)", line (\d*), in (.*)$', line)
-                if m:
-                    filename, lineno, test = m.groups()
-                    sys.stdout.writelines([
-                        self.color('normal'), '  File "',
-                        self.color('filename'), filename,
-                        self.color('normal'), '", line ',
-                        self.color('lineno'), lineno,
-                        self.color('normal'), ', in ',
-                        self.color('testname'), test,
-                        self.color('normal'), '\n'])
-                else:
-                    print line
-            elif line.startswith('    '):
-                print self.colorize('failed-example', line)
-            elif line.startswith('Traceback (most recent call last)'):
-                print line
-            else:
-                print self.colorize('exception', line)
-        print
-
-
 def run(defaults=None, args=None):
     if args is None:
         args = sys.argv[:]
@@ -1273,7 +669,7 @@
     resume_number = 0
     for layer_name in layers:
         args = [sys.executable,
-                options.original_testrunner_args[0],
+                sys.argv[0],
                 '--resume-layer', layer_name, str(resume_number),
                 ]
         resume_number += 1
@@ -2400,14 +1796,6 @@
     ]
 
 
-def terminal_has_colors():
-    """Determine whether the terminal supports colors.
-
-    Some terminals (e.g. the emacs built-in one) don't.
-    """
-    return tigetnum('colors', -1) >= 8
-
-
 def get_options(args=None, defaults=None):
     # Because we want to inspect stdout and decide to colorize or not, we
     # replace the --auto-color option with the appropriate --color or
@@ -2662,7 +2050,7 @@
             gc.get_threshold(),
             )
         test.globs['this_directory'] = os.path.split(__file__)[0]
-        test.globs['testrunner_script'] = __file__
+        test.globs['testrunner_script'] = sys.argv[0]
 
     def tearDown(test):
         sys.path[:], sys.argv[:] = test.globs['saved-sys-info'][:2]

Added: zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/formatter.py
===================================================================
--- zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/formatter.py	                        (rev 0)
+++ zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/formatter.py	2008-05-03 14:06:18 UTC (rev 86214)
@@ -0,0 +1,637 @@
+##############################################################################
+#
+# Copyright (c) 2004-2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Output formatting.
+
+$Id: __init__.py 86207 2008-05-03 13:25:02Z ctheune $
+"""
+
+import sys
+import re
+import traceback
+
+from zope.testing import doctest
+
+
+doctest_template = """
+File "%s", line %s, in %s
+
+%s
+Want:
+%s
+Got:
+%s
+"""
+
+
+class OutputFormatter(object):
+    """Test runner output formatter."""
+
+    # Implementation note: be careful about printing stuff to sys.stderr.
+    # It is used for interprocess communication between the parent and the
+    # child test runner, when you run some test layers in a subprocess.
+    # resume_layer() reasigns sys.stderr for this reason, but be careful
+    # and don't store the original one in __init__ or something.
+
+    max_width = 80
+
+    def __init__(self, options):
+        self.options = options
+        self.last_width = 0
+        self.compute_max_width()
+
+    progress = property(lambda self: self.options.progress)
+    verbose = property(lambda self: self.options.verbose)
+
+    def compute_max_width(self):
+        """Try to determine the terminal width."""
+        # Note that doing this every time is more test friendly.
+        self.max_width = tigetnum('cols', self.max_width)
+
+    def getShortDescription(self, test, room):
+        """Return a description of a test that fits in ``room`` characters."""
+        room -= 1
+        s = str(test)
+        if len(s) > room:
+            pos = s.find(" (")
+            if pos >= 0:
+                w = room - (pos + 5)
+                if w < 1:
+                    # first portion (test method name) is too long
+                    s = s[:room-3] + "..."
+                else:
+                    pre = s[:pos+2]
+                    post = s[-w:]
+                    s = "%s...%s" % (pre, post)
+            else:
+                w = room - 4
+                s = '... ' + s[-w:]
+
+        return ' ' + s[:room]
+
+    def info(self, message):
+        """Print an informative message."""
+        print message
+
+    def info_suboptimal(self, message):
+        """Print an informative message about losing some of the features.
+
+        For example, when you run some tests in a subprocess, you lose the
+        ability to use the debugger.
+        """
+        print message
+
+    def error(self, message):
+        """Report an error."""
+        print message
+
+    def error_with_banner(self, message):
+        """Report an error with a big ASCII banner."""
+        print
+        print '*'*70
+        self.error(message)
+        print '*'*70
+        print
+
+    def profiler_stats(self, stats):
+        """Report profiler stats."""
+        stats.print_stats(50)
+
+    def import_errors(self, import_errors):
+        """Report test-module import errors (if any)."""
+        if import_errors:
+            print "Test-module import failures:"
+            for error in import_errors:
+                self.print_traceback("Module: %s\n" % error.module,
+                                     error.exc_info),
+            print
+
+    def tests_with_errors(self, errors):
+        """Report names of tests with errors (if any)."""
+        if errors:
+            print
+            print "Tests with errors:"
+            for test, exc_info in errors:
+                print "  ", test
+
+    def tests_with_failures(self, failures):
+        """Report names of tests with failures (if any)."""
+        if failures:
+            print
+            print "Tests with failures:"
+            for test, exc_info in failures:
+                print "  ", test
+
+    def modules_with_import_problems(self, import_errors):
+        """Report names of modules with import problems (if any)."""
+        if import_errors:
+            print
+            print "Test-modules with import problems:"
+            for test in import_errors:
+                print "  " + test.module
+
+    def format_seconds(self, n_seconds):
+        """Format a time in seconds."""
+        if n_seconds >= 60:
+            n_minutes, n_seconds = divmod(n_seconds, 60)
+            return "%d minutes %.3f seconds" % (n_minutes, n_seconds)
+        else:
+            return "%.3f seconds" % n_seconds
+
+    def format_seconds_short(self, n_seconds):
+        """Format a time in seconds (short version)."""
+        return "%.3f s" % n_seconds
+
+    def summary(self, n_tests, n_failures, n_errors, n_seconds):
+        """Summarize the results of a single test layer."""
+        print ("  Ran %s tests with %s failures and %s errors in %s."
+               % (n_tests, n_failures, n_errors,
+                  self.format_seconds(n_seconds)))
+
+    def totals(self, n_tests, n_failures, n_errors, n_seconds):
+        """Summarize the results of all layers."""
+        print ("Total: %s tests, %s failures, %s errors in %s."
+               % (n_tests, n_failures, n_errors,
+                  self.format_seconds(n_seconds)))
+
+    def list_of_tests(self, tests, layer_name):
+        """Report a list of test names."""
+        print "Listing %s tests:" % layer_name
+        for test in tests:
+            print ' ', test
+
+    def garbage(self, garbage):
+        """Report garbage generated by tests."""
+        if garbage:
+            print "Tests generated new (%d) garbage:" % len(garbage)
+            print garbage
+
+    def test_garbage(self, test, garbage):
+        """Report garbage generated by a test."""
+        if garbage:
+            print "The following test left garbage:"
+            print test
+            print garbage
+
+    def test_threads(self, test, new_threads):
+        """Report threads left behind by a test."""
+        if new_threads:
+            print "The following test left new threads behind:"
+            print test
+            print "New thread(s):", new_threads
+
+    def refcounts(self, rc, prev):
+        """Report a change in reference counts."""
+        print "  sys refcount=%-8d change=%-6d" % (rc, rc - prev)
+
+    def detailed_refcounts(self, track, rc, prev):
+        """Report a change in reference counts, with extra detail."""
+        print ("  sum detail refcount=%-8d"
+               " sys refcount=%-8d"
+               " change=%-6d"
+               % (track.n, rc, rc - prev))
+        track.output()
+
+    def start_set_up(self, layer_name):
+        """Report that we're setting up a layer.
+
+        The next output operation should be stop_set_up().
+        """
+        print "  Set up %s" % layer_name,
+        sys.stdout.flush()
+
+    def stop_set_up(self, seconds):
+        """Report that we've set up a layer.
+
+        Should be called right after start_set_up().
+        """
+        print "in %s." % self.format_seconds(seconds)
+
+    def start_tear_down(self, layer_name):
+        """Report that we're tearing down a layer.
+
+        The next output operation should be stop_tear_down() or
+        tear_down_not_supported().
+        """
+        print "  Tear down %s" % layer_name,
+        sys.stdout.flush()
+
+    def stop_tear_down(self, seconds):
+        """Report that we've tore down a layer.
+
+        Should be called right after start_tear_down().
+        """
+        print "in %s." % self.format_seconds(seconds)
+
+    def tear_down_not_supported(self):
+        """Report that we could not tear down a layer.
+
+        Should be called right after start_tear_down().
+        """
+        print "... not supported"
+
+    def start_test(self, test, tests_run, total_tests):
+        """Report that we're about to run a test.
+
+        The next output operation should be test_success(), test_error(), or
+        test_failure().
+        """
+        self.test_width = 0
+        if self.progress:
+            if self.last_width:
+                sys.stdout.write('\r' + (' ' * self.last_width) + '\r')
+
+            s = "    %d/%d (%.1f%%)" % (tests_run, total_tests,
+                                        tests_run * 100.0 / total_tests)
+            sys.stdout.write(s)
+            self.test_width += len(s)
+            if self.verbose == 1:
+                room = self.max_width - self.test_width - 1
+                s = self.getShortDescription(test, room)
+                sys.stdout.write(s)
+                self.test_width += len(s)
+
+        elif self.verbose == 1:
+            sys.stdout.write('.' * test.countTestCases())
+
+        if self.verbose > 1:
+            s = str(test)
+            sys.stdout.write(' ')
+            sys.stdout.write(s)
+            self.test_width += len(s) + 1
+
+        sys.stdout.flush()
+
+    def test_success(self, test, seconds):
+        """Report that a test was successful.
+
+        Should be called right after start_test().
+
+        The next output operation should be stop_test().
+        """
+        if self.verbose > 2:
+            s = " (%s)" % self.format_seconds_short(seconds)
+            sys.stdout.write(s)
+            self.test_width += len(s) + 1
+
+    def test_error(self, test, seconds, exc_info):
+        """Report that an error occurred while running a test.
+
+        Should be called right after start_test().
+
+        The next output operation should be stop_test().
+        """
+        if self.verbose > 2:
+            print " (%s)" % self.format_seconds_short(seconds)
+        print
+        self.print_traceback("Error in test %s" % test, exc_info)
+        self.test_width = self.last_width = 0
+
+    def test_failure(self, test, seconds, exc_info):
+        """Report that a test failed.
+
+        Should be called right after start_test().
+
+        The next output operation should be stop_test().
+        """
+        if self.verbose > 2:
+            print " (%s)" % self.format_seconds_short(seconds)
+        print
+        self.print_traceback("Failure in test %s" % test, exc_info)
+        self.test_width = self.last_width = 0
+
+    def print_traceback(self, msg, exc_info):
+        """Report an error with a traceback."""
+        print
+        print msg
+        print self.format_traceback(exc_info)
+
+    def format_traceback(self, exc_info):
+        """Format the traceback."""
+        v = exc_info[1]
+        if isinstance(v, doctest.DocTestFailureException):
+            tb = v.args[0]
+        elif isinstance(v, doctest.DocTestFailure):
+            tb = doctest_template % (
+                v.test.filename,
+                v.test.lineno + v.example.lineno + 1,
+                v.test.name,
+                v.example.source,
+                v.example.want,
+                v.got,
+                )
+        else:
+            tb = "".join(traceback.format_exception(*exc_info))
+        return tb
+
+    def stop_test(self, test):
+        """Clean up the output state after a test."""
+        if self.progress:
+            self.last_width = self.test_width
+        elif self.verbose > 1:
+            print
+        sys.stdout.flush()
+
+    def stop_tests(self):
+        """Clean up the output state after a collection of tests."""
+        if self.progress and self.last_width:
+            sys.stdout.write('\r' + (' ' * self.last_width) + '\r')
+        if self.verbose == 1 or self.progress:
+            print
+
+
+def tigetnum(attr, default=None):
+    """Return a value from the terminfo database.
+
+    Terminfo is used on Unix-like systems to report various terminal attributes
+    (such as width, height or the number of supported colors).
+
+    Returns ``default`` when the ``curses`` module is not available, or when
+    sys.stdout is not a terminal.
+    """
+    try:
+        import curses
+    except ImportError:
+        # avoid reimporting a broken module in python 2.3
+        sys.modules['curses'] = None
+    else:
+        try:
+            curses.setupterm()
+        except (curses.error, TypeError):
+            # You get curses.error when $TERM is set to an unknown name
+            # You get TypeError when sys.stdout is not a real file object
+            # (e.g. in unit tests that use various wrappers).
+            pass
+        else:
+            return curses.tigetnum(attr)
+    return default
+
+
+def terminal_has_colors():
+    """Determine whether the terminal supports colors.
+
+    Some terminals (e.g. the emacs built-in one) don't.
+    """
+    return tigetnum('colors', -1) >= 8
+
+
+class ColorfulOutputFormatter(OutputFormatter):
+    """Output formatter that uses ANSI color codes.
+
+    Like syntax highlighting in your text editor, colorizing
+    test failures helps the developer.
+    """
+
+    # These colors are carefully chosen to have enough contrast
+    # on terminals with both black and white background.
+    colorscheme = {'normal': 'normal',
+                   'default': 'default',
+                   'info': 'normal',
+                   'suboptimal-behaviour': 'magenta',
+                   'error': 'brightred',
+                   'number': 'green',
+                   'slow-test': 'brightmagenta',
+                   'ok-number': 'green',
+                   'error-number': 'brightred',
+                   'filename': 'lightblue',
+                   'lineno': 'lightred',
+                   'testname': 'lightcyan',
+                   'failed-example': 'cyan',
+                   'expected-output': 'green',
+                   'actual-output': 'red',
+                   'character-diffs': 'magenta',
+                   'diff-chunk': 'magenta',
+                   'exception': 'red'}
+
+    # Map prefix character to color in diff output.  This handles ndiff and
+    # udiff correctly, but not cdiff.  In cdiff we ought to highlight '!' as
+    # expected-output until we see a '-', then highlight '!' as actual-output,
+    # until we see a '*', then switch back to highlighting '!' as
+    # expected-output.  Nevertheless, coloried cdiffs are reasonably readable,
+    # so I'm not going to fix this.
+    #   -- mgedmin
+    diff_color = {'-': 'expected-output',
+                  '+': 'actual-output',
+                  '?': 'character-diffs',
+                  '@': 'diff-chunk',
+                  '*': 'diff-chunk',
+                  '!': 'actual-output',}
+
+    prefixes = [('dark', '0;'),
+                ('light', '1;'),
+                ('bright', '1;'),
+                ('bold', '1;'),]
+
+    colorcodes = {'default': 0, 'normal': 0,
+                  'black': 30,
+                  'red': 31,
+                  'green': 32,
+                  'brown': 33, 'yellow': 33,
+                  'blue': 34,
+                  'magenta': 35,
+                  'cyan': 36,
+                  'grey': 37, 'gray': 37, 'white': 37}
+
+    slow_test_threshold = 10.0 # seconds
+
+    def color_code(self, color):
+        """Convert a color description (e.g. 'lightgray') to a terminal code."""
+        prefix_code = ''
+        for prefix, code in self.prefixes:
+            if color.startswith(prefix):
+                color = color[len(prefix):]
+                prefix_code = code
+                break
+        color_code = self.colorcodes[color]
+        return '\033[%s%sm' % (prefix_code, color_code)
+
+    def color(self, what):
+        """Pick a named color from the color scheme"""
+        return self.color_code(self.colorscheme[what])
+
+    def colorize(self, what, message, normal='normal'):
+        """Wrap message in color."""
+        return self.color(what) + message + self.color(normal)
+
+    def error_count_color(self, n):
+        """Choose a color for the number of errors."""
+        if n:
+            return self.color('error-number')
+        else:
+            return self.color('ok-number')
+
+    def info(self, message):
+        """Print an informative message."""
+        print self.colorize('info', message)
+
+    def info_suboptimal(self, message):
+        """Print an informative message about losing some of the features.
+
+        For example, when you run some tests in a subprocess, you lose the
+        ability to use the debugger.
+        """
+        print self.colorize('suboptimal-behaviour', message)
+
+    def error(self, message):
+        """Report an error."""
+        print self.colorize('error', message)
+
+    def error_with_banner(self, message):
+        """Report an error with a big ASCII banner."""
+        print
+        print self.colorize('error', '*'*70)
+        self.error(message)
+        print self.colorize('error', '*'*70)
+        print
+
+    def tear_down_not_supported(self):
+        """Report that we could not tear down a layer.
+
+        Should be called right after start_tear_down().
+        """
+        print "...", self.colorize('suboptimal-behaviour', "not supported")
+
+    def format_seconds(self, n_seconds, normal='normal'):
+        """Format a time in seconds."""
+        if n_seconds >= 60:
+            n_minutes, n_seconds = divmod(n_seconds, 60)
+            return "%s minutes %s seconds" % (
+                        self.colorize('number', '%d' % n_minutes, normal),
+                        self.colorize('number', '%.3f' % n_seconds, normal))
+        else:
+            return "%s seconds" % (
+                        self.colorize('number', '%.3f' % n_seconds, normal))
+
+    def format_seconds_short(self, n_seconds):
+        """Format a time in seconds (short version)."""
+        if n_seconds >= self.slow_test_threshold:
+            color = 'slow-test'
+        else:
+            color = 'number'
+        return self.colorize(color, "%.3f s" % n_seconds)
+
+    def summary(self, n_tests, n_failures, n_errors, n_seconds):
+        """Summarize the results."""
+        sys.stdout.writelines([
+            self.color('info'), '  Ran ',
+            self.color('number'), str(n_tests),
+            self.color('info'), ' tests with ',
+            self.error_count_color(n_failures), str(n_failures),
+            self.color('info'), ' failures and ',
+            self.error_count_color(n_errors), str(n_errors),
+            self.color('info'), ' errors in ',
+            self.format_seconds(n_seconds, 'info'), '.',
+            self.color('normal'), '\n'])
+
+    def totals(self, n_tests, n_failures, n_errors, n_seconds):
+        """Report totals (number of tests, failures, and errors)."""
+        sys.stdout.writelines([
+            self.color('info'), 'Total: ',
+            self.color('number'), str(n_tests),
+            self.color('info'), ' tests, ',
+            self.error_count_color(n_failures), str(n_failures),
+            self.color('info'), ' failures, ',
+            self.error_count_color(n_errors), str(n_errors),
+            self.color('info'), ' errors in ',
+            self.format_seconds(n_seconds, 'info'), '.',
+            self.color('normal'), '\n'])
+
+    def print_traceback(self, msg, exc_info):
+        """Report an error with a traceback."""
+        print
+        print self.colorize('error', msg)
+        v = exc_info[1]
+        if isinstance(v, doctest.DocTestFailureException):
+            self.print_doctest_failure(v.args[0])
+        elif isinstance(v, doctest.DocTestFailure):
+            # I don't think these are ever used... -- mgedmin
+            tb = self.format_traceback(exc_info)
+            print tb
+        else:
+            tb = self.format_traceback(exc_info)
+            self.print_colorized_traceback(tb)
+
+    def print_doctest_failure(self, formatted_failure):
+        """Report a doctest failure.
+
+        ``formatted_failure`` is a string -- that's what
+        DocTestSuite/DocFileSuite gives us.
+        """
+        color_of_indented_text = 'normal'
+        colorize_diff = False
+        for line in formatted_failure.splitlines():
+            if line.startswith('File '):
+                m = re.match(r'File "(.*)", line (\d*), in (.*)$', line)
+                if m:
+                    filename, lineno, test = m.groups()
+                    sys.stdout.writelines([
+                        self.color('normal'), 'File "',
+                        self.color('filename'), filename,
+                        self.color('normal'), '", line ',
+                        self.color('lineno'), lineno,
+                        self.color('normal'), ', in ',
+                        self.color('testname'), test,
+                        self.color('normal'), '\n'])
+                else:
+                    print line
+            elif line.startswith('    '):
+                if colorize_diff and len(line) > 4:
+                    color = self.diff_color.get(line[4], color_of_indented_text)
+                    print self.colorize(color, line)
+                else:
+                    print self.colorize(color_of_indented_text, line)
+            else:
+                colorize_diff = False
+                if line.startswith('Failed example'):
+                    color_of_indented_text = 'failed-example'
+                elif line.startswith('Expected:'):
+                    color_of_indented_text = 'expected-output'
+                elif line.startswith('Got:'):
+                    color_of_indented_text = 'actual-output'
+                elif line.startswith('Exception raised:'):
+                    color_of_indented_text = 'exception'
+                elif line.startswith('Differences '):
+                    color_of_indented_text = 'normal'
+                    colorize_diff = True
+                else:
+                    color_of_indented_text = 'normal'
+                print line
+        print
+
+    def print_colorized_traceback(self, formatted_traceback):
+        """Report a test failure.
+
+        ``formatted_traceback`` is a string.
+        """
+        for line in formatted_traceback.splitlines():
+            if line.startswith('  File'):
+                m = re.match(r'  File "(.*)", line (\d*), in (.*)$', line)
+                if m:
+                    filename, lineno, test = m.groups()
+                    sys.stdout.writelines([
+                        self.color('normal'), '  File "',
+                        self.color('filename'), filename,
+                        self.color('normal'), '", line ',
+                        self.color('lineno'), lineno,
+                        self.color('normal'), ', in ',
+                        self.color('testname'), test,
+                        self.color('normal'), '\n'])
+                else:
+                    print line
+            elif line.startswith('    '):
+                print self.colorize('failed-example', line)
+            elif line.startswith('Traceback (most recent call last)'):
+                print line
+            else:
+                print self.colorize('exception', line)
+        print


Property changes on: zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/formatter.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/testrunner-debugging-layer-setup.test
===================================================================
--- zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/testrunner-debugging-layer-setup.test	2008-05-03 13:58:31 UTC (rev 86213)
+++ zope.testing/branches/ctheune-cleanup/src/zope/testing/testrunner/testrunner-debugging-layer-setup.test	2008-05-03 14:06:18 UTC (rev 86214)
@@ -96,11 +96,12 @@
     ... 
     ... ''')
 
+    >>> import sys
     >>> try:
     ...     zope.testing.testrunner.run(
     ...       ['--path', dir, '-Dvv', '--tests-pattern', 'tests2'])
     ... finally: sys.stdin = real_stdin
-    ... # doctest: +ELLIPSIS
+    ... # doctest: +ELLIPSIS +REPORT_NDIFF
     Running tests at level 1
     Running tests2.Layer1 tests:
       Set up tests2.Layer1 in 0.000 seconds.
@@ -125,7 +126,7 @@
     <BLANKLINE>
     <BLANKLINE>
     Tests with errors:
-       runTest (__main__.SetUpLayerFailure)
+       runTest (zope.testing.testrunner.SetUpLayerFailure)
     Total: 1 tests, 0 failures, 1 errors in 0.210 seconds.
     True
 



More information about the Zope3-Checkins mailing list