[Checkins] SVN: zope.exceptions/trunk/ Merge tseaver-no_2to3 branch.

Tres Seaver cvs-admin at zope.org
Tue Apr 17 15:45:10 UTC 2012


Log message for revision 125160:
  Merge tseaver-no_2to3 branch.

Changed:
  _U  zope.exceptions/trunk/
  U   zope.exceptions/trunk/.bzrignore
  U   zope.exceptions/trunk/CHANGES.txt
  A   zope.exceptions/trunk/docs/
  U   zope.exceptions/trunk/docs/Makefile
  U   zope.exceptions/trunk/docs/api.rst
  U   zope.exceptions/trunk/docs/conf.py
  U   zope.exceptions/trunk/docs/index.rst
  U   zope.exceptions/trunk/docs/make.bat
  U   zope.exceptions/trunk/docs/narr.rst
  A   zope.exceptions/trunk/setup.cfg
  U   zope.exceptions/trunk/setup.py
  U   zope.exceptions/trunk/src/zope/exceptions/__init__.py
  U   zope.exceptions/trunk/src/zope/exceptions/exceptionformatter.py
  U   zope.exceptions/trunk/src/zope/exceptions/interfaces.py
  U   zope.exceptions/trunk/src/zope/exceptions/log.py
  U   zope.exceptions/trunk/src/zope/exceptions/tests/test_exceptionformatter.py
  A   zope.exceptions/trunk/src/zope/exceptions/tests/test_log.py
  A   zope.exceptions/trunk/tox.ini

-=-

Property changes on: zope.exceptions/trunk
___________________________________________________________________
Added: svn:mergeinfo
   + /zope.exceptions/branches/tseaver-no_2to3:125005,125012-125039

Added: svk:merge
   + 62d5b8a3-27da-0310-9561-8e5933582275:/zope.exceptions/branches/tseaver-no_2to3:125039


Modified: zope.exceptions/trunk/.bzrignore
===================================================================
--- zope.exceptions/trunk/.bzrignore	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/.bzrignore	2012-04-17 15:45:06 UTC (rev 125160)
@@ -4,3 +4,6 @@
 ./eggs
 ./parts
 *.egg-info
+.coverage
+__pycache__
+docs/_build

Modified: zope.exceptions/trunk/CHANGES.txt
===================================================================
--- zope.exceptions/trunk/CHANGES.txt	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/CHANGES.txt	2012-04-17 15:45:06 UTC (rev 125160)
@@ -2,12 +2,28 @@
 Changes
 =======
 
-3.7.2 (unreleased)
+4.0.0 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Added Sphinx documentation.
 
+- Added support for continuous integration using ``tox`` and ``jenkins``.
 
+- Removed use of '2to3' and associated fixers when installing under Py3k.
+  The code is now in a "compatible subset" which supports Python 2.6, 2.7,
+  and 3.2, including PyPy 1.8 (the version compatible with the 2.7 language
+  spec).
+
+- 100% unit test coverage.
+
+- Dropped explicit support for Python 2.4 / 2.5 / 3.1.
+
+- Added 'setup.py dev' alias (runs ``setup.py develop`` plus installs
+  ``nose`` and ``coverage``).
+
+- Added 'setup.py docs' alias (installs ``Sphinx`` and dependencies).
+
+
 3.7.1 (2012-03-28)
 ------------------
 

Copied: zope.exceptions/trunk/setup.cfg (from rev 125039, zope.exceptions/branches/tseaver-no_2to3/setup.cfg)
===================================================================
--- zope.exceptions/trunk/setup.cfg	                        (rev 0)
+++ zope.exceptions/trunk/setup.cfg	2012-04-17 15:45:06 UTC (rev 125160)
@@ -0,0 +1,10 @@
+[nosetests]
+nocapture=1
+cover-package=zope.exceptions
+cover-erase=1
+with-doctest=0
+where=src
+
+[aliases]
+dev = develop easy_install zope.exceptions[testing]
+docs = easy_install zope.exceptions[docs]

Modified: zope.exceptions/trunk/setup.py
===================================================================
--- zope.exceptions/trunk/setup.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/setup.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -21,23 +21,20 @@
 import os
 from setuptools import setup, find_packages
 import sys
-if sys.version_info < (3, ):
-    extra = {}
-else:
-    # Python 3 support:
-    extra = dict(
-        use_2to3=True,
-        setup_requires=['zope.fixers'],
-        use_2to3_fixers = ['zope.fixers'],
-    )
 
+extra = {
+    'extras_require': {'docs': ['Sphinx', 'repoze.sphinx.autointerface'],
+                       'testing': ['nose', 'coverage'],
+                      }
+}
 
+
 def read(*rnames):
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
 
 setup(name='zope.exceptions',
-      version='3.7.2.dev0',
+      version='4.0dev',
       author='Zope Foundation and Contributors',
       author_email='zope-dev at zope.org',
       description='Zope Exceptions',
@@ -50,13 +47,12 @@
           'Intended Audience :: Developers',
           'License :: OSI Approved :: Zope Public License',
           'Programming Language :: Python',
-          'Programming Language :: Python :: 2.4',
-          'Programming Language :: Python :: 2.5',
           'Programming Language :: Python :: 2.6',
           'Programming Language :: Python :: 2.7',
           'Programming Language :: Python :: 3',
-          'Programming Language :: Python :: 3.1',
           'Programming Language :: Python :: 3.2',
+          "Programming Language :: Python :: Implementation :: CPython",
+          "Programming Language :: Python :: Implementation :: PyPy",
           'Natural Language :: English',
           'Operating System :: OS Independent',
           'Topic :: Internet :: WWW/HTTP',

Modified: zope.exceptions/trunk/src/zope/exceptions/__init__.py
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/__init__.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/src/zope/exceptions/__init__.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -16,17 +16,26 @@
 These exceptions are so general purpose that they don't belong in Zope
 application-specific packages.
 """
-from zope.exceptions.interfaces import DuplicationError, IDuplicationError
-from zope.exceptions.interfaces import UserError, IUserError
+from zope.exceptions.interfaces import DuplicationError
+from zope.exceptions.interfaces import IDuplicationError
+from zope.exceptions.interfaces import UserError
+from zope.exceptions.interfaces import IUserError
 
+from zope.exceptions.exceptionformatter import format_exception
+from zope.exceptions.exceptionformatter import print_exception
+from zope.exceptions.exceptionformatter import extract_stack
+
 # avoid dependency on zope.security:
 try:
     import zope.security
-except ImportError, v:
+except ImportError as v: #pragma NO COVER
     # "ImportError: No module named security"
     if not str(v).endswith('security'):
         raise
-else:
-    from zope.security.interfaces import IUnauthorized, Unauthorized
-    from zope.security.interfaces import IForbidden, IForbiddenAttribute
-    from zope.security.interfaces import Forbidden, ForbiddenAttribute
+else: #pragma NO COVER
+    from zope.security.interfaces import IUnauthorized
+    from zope.security.interfaces import Unauthorized
+    from zope.security.interfaces import IForbidden
+    from zope.security.interfaces import IForbiddenAttribute
+    from zope.security.interfaces import Forbidden
+    from zope.security.interfaces import ForbiddenAttribute

Modified: zope.exceptions/trunk/src/zope/exceptions/exceptionformatter.py
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/exceptionformatter.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/src/zope/exceptions/exceptionformatter.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -25,7 +25,6 @@
 class TextExceptionFormatter(object):
 
     line_sep = '\n'
-    show_revisions = 0
 
     def __init__(self, limit=None, with_filenames=False):
         self.limit = limit
@@ -84,17 +83,16 @@
             try:
                 extra = getInfo()
                 if extra:
-                    extra = self.escape(extra)
-                    if self.line_sep != "\n":
-                        extra = extra.replace(" ", "&nbsp;")
-                        extra = extra.replace("\n", self.line_sep)
-                    result.append(extra)
-            except:
+                    result.append(self.formatSupplementInfo(extra))
+            except: #pragma NO COVER
                 if DEBUG_EXCEPTION_FORMATTER:
                     traceback.print_exc()
                 # else just swallow the exception.
         return result
 
+    def formatSupplementInfo(self, info):
+        return self.escape(info)
+
     def formatTracebackInfo(self, tbi):
         return self.formatSupplementLine('__traceback_info__: %s' % (tbi, ))
 
@@ -105,13 +103,14 @@
         elif not tb and f:
             lineno = f.f_lineno
         else:
-            raise ValueError("tb or f needs to be passed")
+            raise ValueError("Pass exactly one of tb or f")
         co = f.f_code
         filename = co.co_filename
         name = co.co_name
-        locals = f.f_locals
-        globals = f.f_globals
+        locals = f.f_locals    # XXX shadowing normal builtins deliberately?
+        globals = f.f_globals  # XXX shadowing normal builtins deliberately?
 
+
         if self.with_filenames:
             s = '  File "%s", line %d' % (filename, lineno)
         else:
@@ -144,7 +143,7 @@
             try:
                 supp = factory(*args)
                 result.extend(self.formatSupplement(supp, tb))
-            except:
+            except: #pragma NO COVER
                 if DEBUG_EXCEPTION_FORMATTER:
                     traceback.print_exc()
                 # else just swallow the exception.
@@ -153,7 +152,7 @@
             tbi = locals.get('__traceback_info__', None)
             if tbi is not None:
                 result.append(self.formatTracebackInfo(tbi))
-        except:
+        except: #pragma NO COVER
             if DEBUG_EXCEPTION_FORMATTER:
                 traceback.print_exc()
             # else just swallow the exception.
@@ -176,7 +175,8 @@
         while tb is not None and (limit is None or n < limit):
             if tb.tb_frame.f_locals.get('__exception_formatter__'):
                 # Stop recursion.
-                result.append('(Recursive formatException() stopped, trying traceback.format_tb)\n')
+                result.append('(Recursive formatException() stopped, '
+                              'trying traceback.format_tb)\n')
                 result.extend(traceback.format_tb(tb))
                 break
             line = self.formatLine(tb=tb)
@@ -216,11 +216,17 @@
         return cgi.escape(s)
 
     def getPrefix(self):
-        return '<p>Traceback (most recent call last):\r\n<ul>'
+        return '<p>Traceback (most recent call last):</p>\r\n<ul>'
 
     def formatSupplementLine(self, line):
         return '<b>%s</b>' % self.escape(str(line))
 
+    def formatSupplementInfo(self, info):
+        info = self.escape(info)
+        info = info.replace(" ", "&nbsp;")
+        info = info.replace("\n", self.line_sep)
+        return info
+
     def formatTracebackInfo(self, tbi):
         s = self.escape(str(tbi))
         s = s.replace('\n', self.line_sep)
@@ -231,7 +237,7 @@
         return '<li>%s</li>' % line
 
     def formatLastLine(self, exc_line):
-        return '</ul>%s</p>' % self.escape(exc_line)
+        return '</ul><p>%s</p>' % self.escape(exc_line)
 
 
 def format_exception(t, v, tb, limit=None, as_html=False,
@@ -257,7 +263,7 @@
     information to the traceback and accepts two options, 'as_html'
     and 'with_filenames'.
     """
-    if file is None:
+    if file is None: #pragma NO COVER
         file = sys.stderr
     lines = format_exception(t, v, tb, limit, as_html, with_filenames)
     for line in lines:

Modified: zope.exceptions/trunk/src/zope/exceptions/interfaces.py
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/interfaces.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/src/zope/exceptions/interfaces.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -28,16 +28,18 @@
 effort to clearly present the information provided by the
 ITracebackSupplement.
 """
-from zope.interface import Interface, Attribute, implements
+from zope.interface import Interface
+from zope.interface import Attribute
+from zope.interface import implementer
 
 
 class IDuplicationError(Interface):
     pass
 
 
+ at implementer(IDuplicationError)
 class DuplicationError(Exception):
     """A duplicate registration was attempted"""
-    implements(IDuplicationError)
 
 
 class IUserError(Interface):
@@ -45,13 +47,13 @@
     """
 
 
+ at implementer(IUserError)
 class UserError(Exception):
     """User errors
 
     These exceptions should generally be displayed to users unless
     they are handled.
     """
-    implements(IUserError)
 
 
 class ITracebackSupplement(Interface):
@@ -102,10 +104,6 @@
         itself provides enough information.
         """)
 
-    def getInfo(as_html=0):
+    def getInfo():
         """Optional.  Returns a string containing any other useful info.
-
-        If as_html is set, the implementation must HTML-quote the result
-        (normally using cgi.escape()).  Returns None to provide no
-        extra info.
         """

Modified: zope.exceptions/trunk/src/zope/exceptions/log.py
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/log.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/src/zope/exceptions/log.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -15,7 +15,10 @@
 """
 
 import logging
-import cStringIO
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
 
 from zope.exceptions.exceptionformatter import print_exception
 
@@ -27,7 +30,7 @@
 
         Uses zope.exceptions.exceptionformatter to generate the traceback.
         """
-        sio = cStringIO.StringIO()
+        sio = StringIO()
         print_exception(ei[0], ei[1], ei[2], file=sio, with_filenames=True)
         s = sio.getvalue()
         if s.endswith("\n"):

Modified: zope.exceptions/trunk/src/zope/exceptions/tests/test_exceptionformatter.py
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/tests/test_exceptionformatter.py	2012-04-17 15:39:44 UTC (rev 125159)
+++ zope.exceptions/trunk/src/zope/exceptions/tests/test_exceptionformatter.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -13,166 +13,539 @@
 ##############################################################################
 """ExceptionFormatter tests.
 """
+import unittest
 
-import sys
-from unittest import TestCase, makeSuite
 
-from zope.exceptions.exceptionformatter import format_exception
-from zope.exceptions.exceptionformatter import extract_stack
+class TextExceptionFormatterTests(unittest.TestCase):
 
+    def _getTargetClass(self):
+        from zope.exceptions.exceptionformatter import TextExceptionFormatter
+        return TextExceptionFormatter
 
-def tb(as_html=0):
-    t, v, b = sys.exc_info()
-    try:
-        return ''.join(format_exception(t, v, b, as_html=as_html))
-    finally:
-        del b
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
 
-def st(as_html=0):
-    f = sys.exc_info()[2].tb_frame
-    try:
-        return ''.join(extract_stack(f, as_html=as_html))
-    finally:
-        del f
+    def test_ctor_defaults(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.line_sep, '\n')
+        self.assertEqual(fmt.limit, None)
+        self.assertEqual(fmt.with_filenames, False)
 
-class ExceptionForTesting (Exception):
-    pass
+    def test_ctor_explicit(self):
+        fmt = self._makeOne(limit=20, with_filenames=True)
+        self.assertEqual(fmt.line_sep, '\n')
+        self.assertEqual(fmt.limit, 20)
+        self.assertEqual(fmt.with_filenames, True)
 
+    def test_escape(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.escape('XXX'), 'XXX')
 
-class TestingTracebackSupplement(object):
+    def test_getPrefix(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.getPrefix(),
+                         'Traceback (most recent call last):')
 
-    source_url = '/somepath'
-    line = 634
-    column = 57
-    warnings = ['Repent, for the end is nigh']
+    def test_getLimit_default(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.getLimit(), 200)
 
-    def __init__(self, expression):
-        self.expression = expression
+    def test_getLimit_sys_has_limit(self):
+        import sys
+        fmt = self._makeOne()
+        with _Monkey(sys, tracebacklimit=15):
+            self.assertEqual(fmt.getLimit(), 15)
 
+    def test_getLimit_explicit(self):
+        fmt = self._makeOne(limit=10)
+        self.assertEqual(fmt.getLimit(), 10)
 
-class Test(TestCase):
+    def test_formatSupplementLine(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementLine('XXX'), '   - XXX')
 
-    def testBasicNamesText(self, as_html=0):
+    def test_formatSourceURL(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSourceURL('http://example.com/'),
+                         ['   - http://example.com/'])
+
+    def test_formatSupplement_no_info(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None), [])
+
+    def test_formatSupplement_w_source_url(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.source_url = 'http://example.com/'
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - http://example.com/'])
+
+    def test_formatSupplement_w_line_as_marker(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.line = -1
+        tb = DummyTB()
+        self.assertEqual(fmt.formatSupplement(supplement, tb=tb),
+                         ['   - Line 14'])
+
+    def test_formatSupplement_w_line_no_column(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.line = 23
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - Line 23'])
+
+    def test_formatSupplement_w_column_no_line(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.column = 47
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - Column 47'])
+
+    def test_formatSupplement_w_line_and_column(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.line = 23
+        supplement.column = 47
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - Line 23, Column 47'])
+
+    def test_formatSupplement_w_expression(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.expression = 'a*x^2 + b*x + c'
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - Expression: a*x^2 + b*x + c'])
+
+    def test_formatSupplement_w_warnings(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        supplement.warnings = ['Beware the ides of March!',
+                               'You\'re gonna get wasted.',
+                              ]
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None),
+                         ['   - Warning: Beware the ides of March!',
+                          '   - Warning: You\'re gonna get wasted.',
+                         ])
+
+    def test_formatSupplement_w_getInfo_empty(self):
+        fmt = self._makeOne()
+        supplement = DummySupplement()
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None), [])
+
+    def test_formatSupplement_w_getInfo_text(self):
+        INFO = 'Some days\nI wish I had stayed in bed.'
+        fmt = self._makeOne()
+        supplement = DummySupplement(INFO)
+        self.assertEqual(fmt.formatSupplement(supplement, tb=None), [INFO])
+
+    def test_formatSupplementInfo(self):
+        INFO = 'Some days\nI wish I had stayed in bed.'
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementInfo(INFO), INFO)
+
+    def test_formatTracebackInfo(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatTracebackInfo('XYZZY'),
+                         '   - __traceback_info__: XYZZY')
+
+    def test_formatLine_no_tb_no_f(self):
+        fmt = self._makeOne()
+        self.assertRaises(ValueError, fmt.formatLine, None, None)
+
+    def test_formatLine_w_tb_and_f(self):
+        fmt = self._makeOne()
+        tb = DummyTB()
+        f = DummyFrame()
+        self.assertRaises(ValueError, fmt.formatLine, tb, f)
+
+    def test_formatLine_w_tb_bogus_linecache_w_filenames(self):
+        fmt = self._makeOne(with_filenames=True)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        lines = fmt.formatLine(tb).splitlines()
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0],
+                         '  File "%s", line %d, in %s'
+                          % (f.f_code.co_filename,
+                             tb.tb_lineno,
+                             f.f_code.co_name,
+                            ))
+
+    def test_formatLine_w_f_bogus_linecache_w_filenames(self):
+        fmt = self._makeOne(with_filenames=True)
+        f = DummyFrame()
+        lines = fmt.formatLine(f=f).splitlines()
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0],
+                         '  File "%s", line %d, in %s'
+                          % (f.f_code.co_filename,
+                             f.f_lineno,
+                             f.f_code.co_name,
+                            ))
+
+    def test_formatLine_w_tb_bogus_linecache_wo_filenames(self):
+        fmt = self._makeOne(with_filenames=False)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        f.f_globals['__name__'] = 'dummy.filename'
+        lines = fmt.formatLine(tb).splitlines()
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0],
+                         '  Module dummy.filename, line %d, in %s'
+                          % (tb.tb_lineno,
+                             f.f_code.co_name,
+                            ))
+
+    def test_formatLine_w_f_real_linecache_w_filenames(self):
+        import sys
+        fmt = self._makeOne(with_filenames=True)
+        f = sys._getframe(); lineno = f.f_lineno
+        result = fmt.formatLine(f=f)
+        lines = result.splitlines()
+        self.assertEqual(len(lines), 2)
+        self.assertEqual(lines[0],
+                         '  File "%s", line %d, in %s'
+                          % (f.f_code.co_filename,
+                             lineno + 1,
+                             f.f_code.co_name,
+                            ))
+        self.assertEqual(lines[1],
+                         '    result = fmt.formatLine(f=f)')
+
+    def test_formatLine_w_supplement_in_locals(self):
+        INFO_L = 'I wish I had stayed in bed.'
+        INFO_G = 'I would rather soak my head.'
+        fmt = self._makeOne(with_filenames=False)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        f.f_globals['__name__'] = 'dummy.filename'
+        f.f_locals['__traceback_supplement__'] = (DummySupplement, INFO_L)
+        f.f_globals['__traceback_supplement__'] = (DummySupplement, INFO_G)
+        lines = fmt.formatLine(tb).splitlines()
+        self.assertEqual(len(lines), 2)
+        self.assertEqual(lines[1], INFO_L)
+
+    def test_formatLine_w_supplement_in_globals(self):
+        INFO_G = 'I would rather soak my head.'
+        fmt = self._makeOne(with_filenames=False)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        f.f_globals['__name__'] = 'dummy.filename'
+        f.f_globals['__traceback_supplement__'] = (DummySupplement, INFO_G)
+        lines = fmt.formatLine(tb).splitlines()
+        self.assertEqual(len(lines), 2)
+        self.assertEqual(lines[1], INFO_G)
+
+    def test_formatLine_w_traceback_info(self):
+        INFO_T = 'I would rather soak my head.'
+        fmt = self._makeOne(with_filenames=False)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        f.f_globals['__name__'] = 'dummy.filename'
+        f.f_locals['__traceback_info__'] = INFO_T
+        lines = fmt.formatLine(tb).splitlines()
+        self.assertEqual(len(lines), 2)
+        self.assertEqual(lines[1], '   - __traceback_info__: %s' % INFO_T)
+
+    def test_formatExceptionOnly(self):
+        import traceback
+        fmt = self._makeOne()
+        err = ValueError('testing')
+        self.assertEqual(fmt.formatExceptionOnly(ValueError, err),
+                         ''.join(
+                            traceback.format_exception_only(ValueError, err)))
+
+    def test_formatLastLine(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatLastLine('XXX'), 'XXX')
+
+    def test_formatException_empty_tb_stack(self):
+        import traceback
+        fmt = self._makeOne()
+        err = ValueError('testing')
+        lines = fmt.formatException(ValueError, err, None)
+        self.assertEqual(len(lines), 2)
+        self.assertEqual(lines[0], 'Traceback (most recent call last):\n')
+        self.assertEqual(lines[1],
+                         ''.join(
+                            traceback.format_exception_only(ValueError, err)))
+
+    def test_formatException_non_empty_tb_stack(self):
+        import traceback
+        fmt = self._makeOne()
+        err = ValueError('testing')
+        tb = DummyTB()
+        tb.tb_frame = DummyFrame()
+        lines = fmt.formatException(ValueError, err, tb)
+        self.assertEqual(len(lines), 3)
+        self.assertEqual(lines[0], 'Traceback (most recent call last):\n')
+        self.assertEqual(lines[1], '  Module dummy/filename.py, line 14, '
+                                   'in dummy_function\n')
+        self.assertEqual(lines[2],
+                         ''.join(
+                            traceback.format_exception_only(ValueError, err)))
+
+    def test_formatException_deep_tb_stack_with_limit(self):
+        import traceback
+        fmt = self._makeOne(limit=1)
+        err = ValueError('testing')
+        tb0 = DummyTB()
+        tb0.tb_lineno = 27
+        tb0.tb_frame = DummyFrame()
+        tb = DummyTB()
+        tb.tb_frame = DummyFrame()
+        tb.tb_next = tb0
+        lines = fmt.formatException(ValueError, err, tb)
+        self.assertEqual(len(lines), 3)
+        self.assertEqual(lines[0], 'Traceback (most recent call last):\n')
+        self.assertEqual(lines[1], '  Module dummy/filename.py, line 14, '
+                                   'in dummy_function\n')
+        self.assertEqual(lines[2],
+                         ''.join(
+                            traceback.format_exception_only(ValueError, err)))
+
+    def test_formatException_recursion_in_tb_stack(self):
+        import traceback
+        fmt = self._makeOne()
+        err = ValueError('testing')
+        tb_recurse = DummyTB()
+        tb_recurse.tb_lineno = 27
+        r_f = tb_recurse.tb_frame = DummyFrame()
+        r_f.f_lineno = 27
+        r_f.f_locals['__exception_formatter__'] = 1
+        tb = DummyTB()
+        tb.tb_frame = DummyFrame()
+        tb.tb_next = tb_recurse
+        lines = fmt.formatException(ValueError, err, tb)
+        self.assertEqual(len(lines), 5)
+        self.assertEqual(lines[0], 'Traceback (most recent call last):\n')
+        self.assertEqual(lines[1], '  Module dummy/filename.py, line 14, '
+                                   'in dummy_function\n')
+        self.assertEqual(lines[2], '(Recursive formatException() stopped, '
+                                   'trying traceback.format_tb)\n')
+        self.assertEqual(lines[3], '  File "dummy/filename.py", line 27, '
+                                   'in dummy_function\n')
+        self.assertEqual(lines[4],
+                         ''.join(
+                            traceback.format_exception_only(ValueError, err)))
+
+    def test_extractStack_wo_frame(self):
+        import sys
+        fmt = self._makeOne(limit=1)
+        f = sys._getframe(); lineno = f.f_lineno
+        lines = fmt.extractStack()
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0], '  Module '
+                         'zope.exceptions.tests.test_exceptionformatter, '
+                         'line %d, in test_extractStack_wo_frame\n'
+                         '    lines = fmt.extractStack()\n' % (lineno + 1))
+
+    def test_extractStack_w_single_frame(self):
+        fmt = self._makeOne()
+        f = DummyFrame()
+        lines = fmt.extractStack(f)
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0], '  Module dummy/filename.py, line 137, '
+                                   'in dummy_function\n')
+
+    def test_extractStack_w_multiple_frames_and_limit(self):
+        fmt = self._makeOne(limit=1)
+        f0 = DummyFrame()
+        f0.f_lineno = 213
+        f = DummyFrame()
+        f.f_back = f0
+        lines = fmt.extractStack(f)
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0], '  Module dummy/filename.py, line 137, '
+                                   'in dummy_function\n')
+
+    def test_extractStack_w_recursive_frames_and_limit(self):
+        fmt = self._makeOne(limit=1)
+        f = DummyFrame()
+        f.f_back = f
+        lines = fmt.extractStack(f)
+        self.assertEqual(len(lines), 1)
+        self.assertEqual(lines[0], '  Module dummy/filename.py, line 137, '
+                                   'in dummy_function\n')
+
+
+class HTMLExceptionFormatterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from zope.exceptions.exceptionformatter import HTMLExceptionFormatter
+        return HTMLExceptionFormatter
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
+    def test_ctor_defaults(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.line_sep, '<br />\r\n')
+        self.assertEqual(fmt.limit, None)
+        self.assertEqual(fmt.with_filenames, False)
+
+    def test_ctor_explicit(self):
+        fmt = self._makeOne(limit=20, with_filenames=True)
+        self.assertEqual(fmt.line_sep, '<br />\r\n')
+        self.assertEqual(fmt.limit, 20)
+        self.assertEqual(fmt.with_filenames, True)
+
+    def test_escape_simple(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.escape('XXX'), 'XXX')
+
+    def test_escape_w_markup(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.escape('<span>XXX & YYY<span>'),
+                                    '&lt;span&gt;XXX &amp; YYY&lt;span&gt;')
+
+    def test_getPrefix(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.getPrefix(),
+                         '<p>Traceback (most recent call last):</p>\r\n<ul>')
+
+    def test_formatSupplementLine(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementLine('XXX'), '<b>XXX</b>')
+
+    def test_formatSupplementLine_w_markup(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementLine('XXX & YYY'),
+                         '<b>XXX &amp; YYY</b>')
+
+    def test_formatSupplementInfo_simple(self):
+        INFO = 'Some days\nI wonder.'
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementInfo(INFO),
+                         'Some&nbsp;days<br />\r\nI&nbsp;wonder.')
+
+    def test_formatSupplementInfo_w_markup(self):
+        INFO = 'Some days\nI wonder, <b>Why?</b>.'
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatSupplementInfo(INFO),
+                         'Some&nbsp;days<br />\r\nI&nbsp;wonder,&nbsp;'
+                         '&lt;b&gt;Why?&lt;/b&gt;.')
+
+    def test_formatTracebackInfo(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatTracebackInfo('XXX & YYY\nZZZ'),
+                         '__traceback_info__: XXX &amp; YYY<br />\r\nZZZ')
+
+    def test_formatLine_simple(self):
+        fmt = self._makeOne(with_filenames=True)
+        tb = DummyTB()
+        tb.tb_frame = f = DummyFrame()
+        result = fmt.formatLine(tb)
+        self.assertEqual(result,
+                         '<li>  File "%s", line %d, in %s</li>'
+                          % (f.f_code.co_filename,
+                             tb.tb_lineno,
+                             f.f_code.co_name,
+                            ))
+
+    def test_formatLastLine(self):
+        fmt = self._makeOne()
+        self.assertEqual(fmt.formatLastLine('XXX'), '</ul><p>XXX</p>')
+
+
+class Test_format_exception(unittest.TestCase):
+
+    def _callFUT(self, as_html=False):
+        import sys
+        from zope.exceptions.exceptionformatter import format_exception
+        t, v, b = sys.exc_info()
         try:
+            return ''.join(format_exception(t, v, b, as_html=as_html))
+        finally:
+            del b
+
+    def test_basic_names_text(self):
+        try:
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = tb(as_html)
-            # The traceback should include the name of this function.
-            self.assertTrue(s.find('testBasicNamesText') >= 0)
-            # The traceback should include the name of the exception.
-            self.assertTrue(s.find('ExceptionForTesting') >= 0)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(False)
+        # The traceback should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_text') >= 0)
+        # The traceback should include the name of the exception.
+        self.assertTrue(s.find('ExceptionForTesting') >= 0)
 
-    def testBasicNamesHTML(self):
-        self.testBasicNamesText(1)
-
-    def testBasicNamesText_stack(self, as_html=0):
+    def test_basic_names_html(self):
         try:
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = st(as_html)
-            # The stack trace should include the name of this function.
-            self.assertTrue(s.find('testBasicNamesText_stack') >= 0)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(True)
+        # The traceback should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_html') >= 0)
+        # The traceback should include the name of the exception.
+        self.assertTrue(s.find('ExceptionForTesting') >= 0)
 
-    def testBasicNamesHTML_stack(self):
-        self.testBasicNamesText_stack(1)
-
-    def testSupplement(self, as_html=0):
+    def test_traceback_info_text(self):
         try:
-            __traceback_supplement__ = (TestingTracebackSupplement,
-                                        "You're one in a million")
+            __traceback_info__ = "Adam & Eve"
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = tb(as_html)
-            # The source URL
-            self.assertTrue(s.find('/somepath') >= 0, s)
-            # The line number
-            self.assertTrue(s.find('634') >= 0, s)
-            # The column number
-            self.assertTrue(s.find('57') >= 0, s)
-            # The expression
-            self.assertTrue(s.find("You're one in a million") >= 0, s)
-            # The warning
-            self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(False)
+        self.assertTrue(s.find('Adam & Eve') >= 0, s)
 
-    def testSupplementHTML(self):
-        self.testSupplement(1)
-
-    def testSupplement_stack(self, as_html=0):
+    def test_traceback_info_html(self):
         try:
-            __traceback_supplement__ = (TestingTracebackSupplement,
-                                        "You're one in a million")
+            __traceback_info__ = "Adam & Eve"
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = st(as_html)
-            # The source URL
-            self.assertTrue(s.find('/somepath') >= 0, s)
-            # The line number
-            self.assertTrue(s.find('634') >= 0, s)
-            # The column number
-            self.assertTrue(s.find('57') >= 0, s)
-            # The expression
-            self.assertTrue(s.find("You're one in a million") >= 0, s)
-            # The warning
-            self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(True)
+        # Be sure quoting is happening.
+        self.assertTrue(s.find('Adam &amp; Eve') >= 0, s)
 
-    def testSupplementHTML_stack(self):
-        self.testSupplement_stack(1)
-
-    def testTracebackInfo(self, as_html=0):
+    def test_traceback_info_is_tuple(self):
         try:
-            __traceback_info__ = "Adam & Eve"
+            __traceback_info__ = ("Adam", "Eve")
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = tb(as_html)
-            if as_html:
-                # Be sure quoting is happening.
-                self.assertTrue(s.find('Adam &amp; Eve') >= 0, s)
-            else:
-                self.assertTrue(s.find('Adam & Eve') >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(False)
+        self.assertTrue(s.find('Adam') >= 0, s)
+        self.assertTrue(s.find('Eve') >= 0, s)
 
-    def testTracebackInfoHTML(self):
-        self.testTracebackInfo(1)
-
-    def testTracebackInfo_stack(self, as_html=0):
+    def test_supplement_text(self, as_html=0):
         try:
-            __traceback_info__ = "Adam & Eve"
+            __traceback_supplement__ = (TestingTracebackSupplement,
+                                        "You're one in a million")
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = st(as_html)
-            if as_html:
-                # Be sure quoting is happening.
-                self.assertTrue(s.find('Adam &amp; Eve') >= 0, s)
-            else:
-                self.assertTrue(s.find('Adam & Eve') >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(as_html)
+        # The source URL
+        self.assertTrue(s.find('/somepath') >= 0, s)
+        # The line number
+        self.assertTrue(s.find('634') >= 0, s)
+        # The column number
+        self.assertTrue(s.find('57') >= 0, s)
+        # The expression
+        self.assertTrue(s.find("You're one in a million") >= 0, s)
+        # The warning
+        self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
 
-    def testTracebackInfoHTML_stack(self):
-        self.testTracebackInfo_stack(1)
-
-    def testTracebackInfoTuple(self):
+    def test_supplement_html(self):
         try:
-            __traceback_info__ = ("Adam", "Eve")
+            __traceback_supplement__ = (TestingTracebackSupplement,
+                                        "You're one in a million")
             raise ExceptionForTesting
         except ExceptionForTesting:
-            s = tb()
-            self.assertTrue(s.find('Adam') >= 0, s)
-            self.assertTrue(s.find('Eve') >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(True)
+        # The source URL
+        self.assertTrue(s.find('/somepath') >= 0, s)
+        # The line number
+        self.assertTrue(s.find('634') >= 0, s)
+        # The column number
+        self.assertTrue(s.find('57') >= 0, s)
+        # The expression
+        self.assertTrue(s.find("You're one in a million") >= 0, s)
+        # The warning
+        self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
 
-    def testMultipleLevels(self):
-        # Makes sure many levels are shown in a traceback.
+    def test_multiple_levels(self):
+        # Ensure many levels are shown in a traceback.
+        HOW_MANY = 10
         def f(n):
             """Produces a (n + 1)-level traceback."""
             __traceback_info__ = 'level%d' % n
@@ -180,39 +553,35 @@
                 f(n - 1)
             else:
                 raise ExceptionForTesting
-
         try:
-            f(10)
+            f(HOW_MANY)
         except ExceptionForTesting:
-            s = tb()
-            for n in range(11):
-                self.assertTrue(s.find('level%d' % n) >= 0, s)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(False)
+        for n in range(HOW_MANY+1):
+            self.assertTrue(s.find('level%d' % n) >= 0, s)
 
-    def testQuoteLastLine(self):
+    def test_quote_last_line(self):
         class C(object):
             pass
         try:
             raise TypeError(C())
         except:
-            s = tb(1)
-        else:
-            self.fail('no exception occurred')
+            s = self._callFUT(True)
         self.assertTrue(s.find('&lt;') >= 0, s)
         self.assertTrue(s.find('&gt;') >= 0, s)
 
-    def testMultilineException(self):
+    def test_multiline_exception(self):
         try:
-            exec 'syntax error\n'
+            exec('syntax error\n')
         except Exception:
-            s = tb()
-        self.assertEqual(s.splitlines()[-3:],
-                         ['    syntax error',
-                          '               ^',
-                          'SyntaxError: invalid syntax'])
+            s = self._callFUT(False)
+        lines = s.splitlines()[-3:]
+        self.assertEqual(lines[0], '    syntax error')
+        self.assertTrue(lines[1].endswith('    ^')) #PyPy has a shorter prefix
+        self.assertEqual(lines[2], 'SyntaxError: invalid syntax')
 
-    def testRecursionFailure(self):
+    def test_recursion_failure(self):
+        import sys
         from zope.exceptions.exceptionformatter import TextExceptionFormatter
 
         class FormatterException(Exception):
@@ -229,13 +598,197 @@
             try:
                 fmt.formatException(*sys.exc_info())
             except FormatterException:
-                s = tb()
+                s = self._callFUT(False)
         # Recursion was detected
-        self.assertTrue('(Recursive formatException() stopped, trying traceback.format_tb)' in s, s)
+        self.assertTrue('(Recursive formatException() stopped, '
+                        'trying traceback.format_tb)' in s, s)
         # and we fellback to the stdlib rather than hid the real error
-        self.assertEqual(s.splitlines()[-2], '    raise FormatterException("Formatter failed")')
-        self.assertTrue('FormatterException: Formatter failed' in s.splitlines()[-1])
+        self.assertEqual(s.splitlines()[-2],
+                         '    raise FormatterException("Formatter failed")')
+        self.assertTrue('FormatterException: Formatter failed'
+                        in s.splitlines()[-1])
 
 
+class Test_print_exception(unittest.TestCase):
+
+    def _callFUT(self, as_html=False):
+        try:
+            from StringIO import StringIO
+        except ImportError:
+            from io import StringIO
+        buf = StringIO()
+        import sys
+        from zope.exceptions.exceptionformatter import print_exception
+        t, v, b = sys.exc_info()
+        try:
+            print_exception(t, v, b, file=buf, as_html=as_html)
+            return buf.getvalue()
+        finally:
+            del b
+
+    def test_basic_names_text(self):
+        try:
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(False)
+        # The traceback should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_text') >= 0)
+        # The traceback should include the name of the exception.
+        self.assertTrue(s.find('ExceptionForTesting') >= 0)
+
+    def test_basic_names_html(self):
+        try:
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(True)
+        # The traceback should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_html') >= 0)
+        # The traceback should include the name of the exception.
+        self.assertTrue(s.find('ExceptionForTesting') >= 0)
+
+
+class Test_extract_stack(unittest.TestCase):
+
+    def _callFUT(self, as_html=False):
+        import sys
+        from zope.exceptions.exceptionformatter import extract_stack
+        f = sys.exc_info()[2].tb_frame
+        try:
+            return ''.join(extract_stack(f, as_html=as_html))
+        finally:
+            del f
+
+    def test_basic_names_as_text(self, as_html=0):
+        try:
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(False)
+        # The stack trace should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_as_text') >= 0)
+
+    def test_basic_names_as_html(self):
+        try:
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(True)
+        # The stack trace should include the name of this function.
+        self.assertTrue(s.find('test_basic_names_as_html') >= 0)
+
+    def test_traceback_info_text(self):
+        try:
+            __traceback_info__ = "Adam & Eve"
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(False)
+        self.assertTrue(s.find('Adam & Eve') >= 0, s)
+
+    def test_traceback_info_html(self):
+        try:
+            __traceback_info__ = "Adam & Eve"
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(True)
+        self.assertTrue(s.find('Adam &amp; Eve') >= 0, s)
+
+    def test_traceback_supplement_text(self):
+        try:
+            __traceback_supplement__ = (TestingTracebackSupplement,
+                                        "You're one in a million")
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(False)
+        # The source URL
+        self.assertTrue(s.find('/somepath') >= 0, s)
+        # The line number
+        self.assertTrue(s.find('634') >= 0, s)
+        # The column number
+        self.assertTrue(s.find('57') >= 0, s)
+        # The expression
+        self.assertTrue(s.find("You're one in a million") >= 0, s)
+        # The warning
+        self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
+
+    def test_traceback_supplement_html(self):
+        try:
+            __traceback_supplement__ = (TestingTracebackSupplement,
+                                        "You're one in a million")
+            raise ExceptionForTesting
+        except ExceptionForTesting:
+            s = self._callFUT(True)
+        # The source URL
+        self.assertTrue(s.find('/somepath') >= 0, s)
+        # The line number
+        self.assertTrue(s.find('634') >= 0, s)
+        # The column number
+        self.assertTrue(s.find('57') >= 0, s)
+        # The expression
+        self.assertTrue(s.find("You're one in a million") >= 0, s)
+        # The warning
+        self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s)
+
+
+class ExceptionForTesting (Exception):
+    pass
+
+
+class TestingTracebackSupplement(object):
+    source_url = '/somepath'
+    line = 634
+    column = 57
+    warnings = ['Repent, for the end is nigh']
+    def __init__(self, expression):
+        self.expression = expression
+
+
+class DummySupplement(object):
+    def __init__(self, info=''):
+        self._info = info
+    def getInfo(self):
+        return self._info
+
+
+class DummyTB(object):
+    tb_lineno = 14
+    tb_next = None
+
+
+class DummyFrame(object):
+    f_lineno = 137
+    f_back = None
+    def __init__(self):
+        self.f_locals = {}
+        self.f_globals = {}
+        self.f_code = DummyCode()
+
+class DummyCode(object):
+    co_filename = 'dummy/filename.py'
+    co_name = 'dummy_function'
+
+class _Monkey(object):
+    # context-manager for replacing module names in the scope of a test.
+    def __init__(self, module, **kw):
+        self.module = module
+        self.to_restore = dict([(key, getattr(module, key, self))
+                                    for key in kw])
+        for key, value in kw.items():
+            setattr(module, key, value)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        for key, value in self.to_restore.items():
+            if value is not self:
+                setattr(self.module, key, value)
+            else:
+                delattr(self.module, key)
+
+
 def test_suite():
-    return makeSuite(Test)
+    return unittest.TestSuite((
+        unittest.makeSuite(TextExceptionFormatterTests),
+        unittest.makeSuite(HTMLExceptionFormatterTests),
+        unittest.makeSuite(Test_format_exception),
+        unittest.makeSuite(Test_print_exception),
+        unittest.makeSuite(Test_extract_stack),
+    ))

Copied: zope.exceptions/trunk/src/zope/exceptions/tests/test_log.py (from rev 125039, zope.exceptions/branches/tseaver-no_2to3/src/zope/exceptions/tests/test_log.py)
===================================================================
--- zope.exceptions/trunk/src/zope/exceptions/tests/test_log.py	                        (rev 0)
+++ zope.exceptions/trunk/src/zope/exceptions/tests/test_log.py	2012-04-17 15:45:06 UTC (rev 125160)
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2012 Zope Foundation 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.
+#
+##############################################################################
+"""log.Formatter tests.
+"""
+import unittest
+
+
+class FormatterTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from zope.exceptions.log import Formatter
+        return Formatter
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
+    def test_simple_exception(self):
+        import traceback
+        tb = DummyTB()
+        tb.tb_frame = DummyFrame()
+        exc = ValueError('testing')
+        fmt = self._makeOne()
+        result = fmt.formatException((ValueError, exc, tb))
+        lines = result.splitlines()
+        self.assertEqual(len(lines), 3)
+        self.assertEqual(lines[0], 'Traceback (most recent call last):')
+        self.assertEqual(lines[1], '  File "dummy/filename.py", line 14, '
+                                   'in dummy_function')
+        self.assertEqual(lines[2],
+                        traceback.format_exception_only(
+                                        ValueError, exc)[0][:-1]) #trailing \n
+
+
+class DummyTB(object):
+    tb_lineno = 14
+    tb_next = None
+
+
+class DummyFrame(object):
+    f_lineno = 137
+    f_back = None
+    def __init__(self):
+        self.f_locals = {}
+        self.f_globals = {}
+        self.f_code = DummyCode()
+
+class DummyCode(object):
+    co_filename = 'dummy/filename.py'
+    co_name = 'dummy_function'

Copied: zope.exceptions/trunk/tox.ini (from rev 125039, zope.exceptions/branches/tseaver-no_2to3/tox.ini)
===================================================================
--- zope.exceptions/trunk/tox.ini	                        (rev 0)
+++ zope.exceptions/trunk/tox.ini	2012-04-17 15:45:06 UTC (rev 125160)
@@ -0,0 +1,31 @@
+[tox]
+envlist = 
+# Jython support pending 2.7 support, due 2012-07-15 or so.  See:
+# http://fwierzbicki.blogspot.com/2012/03/adconion-to-fund-jython-27.html
+#   py26,py27,py32,jython,pypy,coverage
+    py26,py27,py32,pypy,coverage
+
+[testenv]
+commands = 
+    python setup.py test -q
+deps = zope.interface
+
+[testenv:jython]
+commands = 
+   jython setup.py test -q
+
+[testenv:coverage]
+basepython =
+    python2.6
+commands = 
+#   The installed version messes up nose's test discovery / coverage reporting
+#   So, we uninstall that from the environment, and then install the editable
+#   version, before running nosetests.
+    pip uninstall -y zope.exceptions
+    pip install -e .
+    nosetests --with-xunit --with-xcoverage
+deps =
+    zope.interface
+    nose
+    coverage
+    nosexcover



More information about the checkins mailing list