summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc7
-rw-r--r--.travis.yml6
-rw-r--r--CHANGES.rst8
-rw-r--r--setup.py4
-rw-r--r--src/zope/exceptions/exceptionformatter.py42
-rw-r--r--src/zope/exceptions/log.py8
-rw-r--r--src/zope/exceptions/tests/test_exceptionformatter.py189
-rw-r--r--src/zope/exceptions/tests/test_log.py31
-rw-r--r--tox.ini15
9 files changed, 191 insertions, 119 deletions
diff --git a/.coveragerc b/.coveragerc
index af40312..3b86679 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,6 +1,11 @@
[run]
-source = src
+source = zope.exceptions
[report]
+precision = 2
exclude_lines =
pragma: no cover
+ if __name__ == '__main__':
+ raise NotImplementedError
+ self.fail
+ raise AssertionError
diff --git a/.travis.yml b/.travis.yml
index 19a4c83..830860d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,17 +2,17 @@ language: python
sudo: false
python:
- 2.7
- - 3.3
- 3.4
- 3.5
- 3.6
- - pypy-5.4.1
+ - pypy
install:
- pip install -U pip setuptools
- pip install -U coverage coveralls
- - pip install -U -e .[test]
+ - pip install -U -e .[test,docs]
script:
- coverage run -m zope.testrunner --test-path=src
+ - coverage run -a -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest
notifications:
email: false
cache: pip
diff --git a/CHANGES.rst b/CHANGES.rst
index d919f5a..5d60d0f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,12 @@
- Add support for Python 3.6.
+- Drop support for Python 3.3.
+
+- Fix handling of unicode supplemental traceback information on
+ Python 2. Now such values are always encoded to UTF-8; previously
+ the results were undefined and depended on system encodings and the
+ values themselves. See `issue 1 <https://github.com/zopefoundation/zope.exceptions/issues/1>`_.
4.1.0 (2017-04-12)
==================
@@ -76,7 +82,7 @@
4.0.1 (2012-08-20)
==================
-- Fixed optional dependency code for `zope.security` to work under Python 3.3.
+- Fixed optional dependency code for `'zope.security`` to work under Python 3.3.
4.0.0.1 (2012-05-16)
diff --git a/setup.py b/setup.py
index bc2e2d0..bdfdafa 100644
--- a/setup.py
+++ b/setup.py
@@ -61,7 +61,6 @@ setup(name='zope.exceptions',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
@@ -72,7 +71,7 @@ setup(name='zope.exceptions',
'Topic :: Internet :: WWW/HTTP',
'Framework :: Zope3',
],
- url='http://cheeseshop.python.org/pypi/zope.exceptions',
+ url='https://github.com/zopefoundation/zope.exceptions',
license='ZPL 2.1',
packages=find_packages('src'),
package_dir={'': 'src'},
@@ -89,7 +88,6 @@ setup(name='zope.exceptions',
zip_safe=False,
extras_require={
'docs': ['Sphinx', 'repoze.sphinx.autointerface'],
- 'testing': ['nose', 'coverage'],
'test': tests_require,
},
)
diff --git a/src/zope/exceptions/exceptionformatter.py b/src/zope/exceptions/exceptionformatter.py
index fabea9f..47e0f41 100644
--- a/src/zope/exceptions/exceptionformatter.py
+++ b/src/zope/exceptions/exceptionformatter.py
@@ -46,7 +46,12 @@ class TextExceptionFormatter(object):
return limit
def formatSupplementLine(self, line):
- return ' - %s' % line
+ result = ' - %s' % line
+ if not isinstance(result, str):
+ # Must be an Python 2, and must be a unicode `line`
+ # and we upconverted the result to a unicode
+ result = result.encode('utf-8')
+ return result
def formatSourceURL(self, url):
return [self.formatSupplementLine(url)]
@@ -110,13 +115,13 @@ class TextExceptionFormatter(object):
co = f.f_code
filename = co.co_filename
name = co.co_name
- locals = f.f_locals # XXX shadowing normal builtins deliberately?
- globals = f.f_globals # XXX shadowing normal builtins deliberately?
+ f_locals = f.f_locals
+ f_globals = f.f_globals
if self.with_filenames:
s = ' File "%s", line %d' % (filename, lineno)
else:
- modname = globals.get('__name__', filename)
+ modname = f_globals.get('__name__', filename)
s = ' Module %s, line %d' % (modname, lineno)
s = s + ', in %s' % name
@@ -130,13 +135,13 @@ class TextExceptionFormatter(object):
result.append(" " + self.escape(line.strip()))
# Output a traceback supplement, if any.
- if '__traceback_supplement__' in locals:
+ if '__traceback_supplement__' in f_locals:
# Use the supplement defined in the function.
- tbs = locals['__traceback_supplement__']
- elif '__traceback_supplement__' in globals:
+ tbs = f_locals['__traceback_supplement__']
+ elif '__traceback_supplement__' in f_globals:
# Use the supplement defined in the module.
# This is used by Scripts (Python).
- tbs = globals['__traceback_supplement__']
+ tbs = f_globals['__traceback_supplement__']
else:
tbs = None
if tbs is not None:
@@ -151,7 +156,7 @@ class TextExceptionFormatter(object):
# else just swallow the exception.
try:
- tbi = locals.get('__traceback_info__', None)
+ tbi = f_locals.get('__traceback_info__', None)
if tbi is not None:
result.append(self.formatTracebackInfo(tbi))
except: #pragma: no cover
@@ -240,13 +245,23 @@ class HTMLExceptionFormatter(TextExceptionFormatter):
line_sep = '<br />\r\n'
def escape(self, s):
+ if not isinstance(s, str):
+ try:
+ s = str(s)
+ except UnicodeError:
+ if hasattr(s, 'encode'):
+ # We probably got a unicode string on
+ # Python 2.
+ s = s.encode('utf-8')
+ else: # pragma: no cover
+ raise
return escape(s, quote=False)
def getPrefix(self):
return '<p>Traceback (most recent call last):</p>\r\n<ul>'
def formatSupplementLine(self, line):
- return '<b>%s</b>' % self.escape(str(line))
+ return '<b>%s</b>' % self.escape(line)
def formatSupplementInfo(self, info):
info = self.escape(info)
@@ -255,7 +270,7 @@ class HTMLExceptionFormatter(TextExceptionFormatter):
return info
def formatTracebackInfo(self, tbi):
- s = self.escape(str(tbi))
+ s = self.escape(tbi)
s = s.replace('\n', self.line_sep)
return '__traceback_info__: %s' % (s, )
@@ -275,6 +290,9 @@ def format_exception(t, v, tb, limit=None, as_html=False,
Similar to 'traceback.format_exception', but adds supplemental
information to the traceback and accepts two options, 'as_html'
and 'with_filenames'.
+
+ The result is a list of native strings; on Python 2 they are UTF-8
+ encoded if need be.
"""
if as_html:
fmt = HTMLExceptionFormatter(limit, with_filenames)
@@ -291,7 +309,7 @@ def print_exception(t, v, tb, limit=None, file=None, as_html=False,
information to the traceback and accepts two options, 'as_html'
and 'with_filenames'.
"""
- if file is None: #pragma: no cover
+ 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:
diff --git a/src/zope/exceptions/log.py b/src/zope/exceptions/log.py
index 735429a..574ccd3 100644
--- a/src/zope/exceptions/log.py
+++ b/src/zope/exceptions/log.py
@@ -15,13 +15,11 @@
"""
import logging
-try:
- from StringIO import StringIO
-except ImportError: #pragma: no cover Python3
- from io import StringIO
+import io
from zope.exceptions.exceptionformatter import print_exception
+Buffer = io.StringIO if bytes is not str else io.BytesIO
class Formatter(logging.Formatter):
@@ -30,7 +28,7 @@ class Formatter(logging.Formatter):
Uses zope.exceptions.exceptionformatter to generate the traceback.
"""
- sio = StringIO()
+ sio = Buffer()
print_exception(ei[0], ei[1], ei[2], file=sio, with_filenames=True)
s = sio.getvalue()
if s.endswith("\n"):
diff --git a/src/zope/exceptions/tests/test_exceptionformatter.py b/src/zope/exceptions/tests/test_exceptionformatter.py
index 5856583..9046950 100644
--- a/src/zope/exceptions/tests/test_exceptionformatter.py
+++ b/src/zope/exceptions/tests/test_exceptionformatter.py
@@ -14,7 +14,6 @@
"""ExceptionFormatter tests.
"""
import unittest
-import doctest
import sys
@@ -53,7 +52,6 @@ class TextExceptionFormatterTests(unittest.TestCase):
self.assertEqual(fmt.getLimit(), 200)
def test_getLimit_sys_has_limit(self):
- import sys
fmt = self._makeOne()
with _Monkey(sys, tracebacklimit=15):
self.assertEqual(fmt.getLimit(), 15)
@@ -152,6 +150,17 @@ class TextExceptionFormatterTests(unittest.TestCase):
self.assertEqual(fmt.formatTracebackInfo('XYZZY'),
' - __traceback_info__: XYZZY')
+ def test_formatTracebackInfo_unicode(self):
+ __traceback_info__ = u"Have a Snowman: \u2603"
+ fmt = self._makeOne()
+
+ result = fmt.formatTracebackInfo(__traceback_info__)
+ expected = ' - __traceback_info__: Have a Snowman: '
+ # utf-8 encoded on Python 2, unicode on Python 3
+ expected += '\xe2\x98\x83' if bytes is str else u'\u2603'
+ self.assertIsInstance(result, str)
+ self.assertEqual(result, expected)
+
def test_formatLine_no_tb_no_f(self):
fmt = self._makeOne()
self.assertRaises(ValueError, fmt.formatLine, None, None)
@@ -168,24 +177,26 @@ class TextExceptionFormatterTests(unittest.TestCase):
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,
- ))
+ 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,
- ))
+ 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)
@@ -194,25 +205,27 @@ class TextExceptionFormatterTests(unittest.TestCase):
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,
- ))
+ 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
+ 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[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)')
@@ -257,7 +270,7 @@ class TextExceptionFormatterTests(unittest.TestCase):
err = ValueError('testing')
self.assertEqual(fmt.formatExceptionOnly(ValueError, err),
''.join(
- traceback.format_exception_only(ValueError, err)))
+ traceback.format_exception_only(ValueError, err)))
def test_formatLastLine(self):
fmt = self._makeOne()
@@ -272,7 +285,7 @@ class TextExceptionFormatterTests(unittest.TestCase):
self.assertEqual(lines[0], 'Traceback (most recent call last):\n')
self.assertEqual(lines[1],
''.join(
- traceback.format_exception_only(ValueError, err)))
+ traceback.format_exception_only(ValueError, err)))
def test_formatException_non_empty_tb_stack(self):
import traceback
@@ -287,7 +300,7 @@ class TextExceptionFormatterTests(unittest.TestCase):
'in dummy_function\n')
self.assertEqual(lines[2],
''.join(
- traceback.format_exception_only(ValueError, err)))
+ traceback.format_exception_only(ValueError, err)))
def test_formatException_deep_tb_stack_with_limit(self):
import traceback
@@ -336,12 +349,12 @@ class TextExceptionFormatterTests(unittest.TestCase):
'in dummy_function\n')
self.assertEqual(lines[4],
''.join(
- traceback.format_exception_only(ValueError, err)))
+ traceback.format_exception_only(ValueError, err)))
def test_extractStack_wo_frame(self):
- import sys
fmt = self._makeOne()
- f = sys._getframe(); lineno = f.f_lineno
+ f = sys._getframe()
+ lineno = f.f_lineno
lines = fmt.extractStack()
# rather don't assert this here
# self.assertEqual(len(lines), 10)
@@ -351,9 +364,9 @@ class TextExceptionFormatterTests(unittest.TestCase):
' lines = fmt.extractStack()\n' % (lineno + 1))
def test_extractStack_wo_frame_w_limit(self):
- import sys
fmt = self._makeOne(limit=2)
- f = sys._getframe(); lineno = f.f_lineno
+ f = sys._getframe()
+ lineno = f.f_lineno
lines = fmt.extractStack()
self.assertEqual(len(lines), 3)
self.assertEqual(lines[-1], ' Module '
@@ -417,7 +430,7 @@ class TextExceptionFormatterTests(unittest.TestCase):
def _makeTBs(self, count):
prev = None
- for i in range(count):
+ for _i in range(count):
tb = DummyTB()
tb.tb_lineno = 14
tb.tb_frame = DummyFrame()
@@ -429,7 +442,7 @@ class TextExceptionFormatterTests(unittest.TestCase):
def _makeFrames(self, count):
prev = None
- for i in range(count):
+ for _i in range(count):
f = DummyFrame()
f.f_lineno = 17
if prev is not None:
@@ -467,7 +480,7 @@ class HTMLExceptionFormatterTests(unittest.TestCase):
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;')
+ '&lt;span&gt;XXX &amp; YYY&lt;span&gt;')
def test_getPrefix(self):
fmt = self._makeOne()
@@ -506,12 +519,13 @@ class HTMLExceptionFormatterTests(unittest.TestCase):
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,
- ))
+ 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()
@@ -521,7 +535,6 @@ class HTMLExceptionFormatterTests(unittest.TestCase):
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:
@@ -633,23 +646,33 @@ class Test_format_exception(unittest.TestCase):
pass
try:
raise TypeError(C())
- except:
+ except TypeError:
s = self._callFUT(True)
- self.assertTrue(s.find('&lt;') >= 0, s)
- self.assertTrue(s.find('&gt;') >= 0, s)
+ self.assertIn('&lt;', s)
+ self.assertIn('&gt;', s)
def test_multiline_exception(self):
try:
exec('syntax error\n')
- except Exception:
+ except SyntaxError:
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 test_traceback_info_non_ascii(self):
+ __traceback_info__ = u"Have a Snowman: \u2603"
+ try:
+ raise TypeError()
+ except TypeError:
+ s = self._callFUT(True)
+
+ self.assertIsInstance(s, str)
+ self.assertIn('Have a Snowman', s)
+
+
def test_recursion_failure(self):
- import sys
from zope.exceptions.exceptionformatter import TextExceptionFormatter
class FormatterException(Exception):
@@ -676,16 +699,38 @@ class Test_format_exception(unittest.TestCase):
self.assertTrue('FormatterException: Formatter failed'
in s.splitlines()[-1])
+ def test_format_exception_as_html(self):
+ # Test for format_exception (as_html=True)
+ from zope.exceptions.exceptionformatter import format_exception
+ from textwrap import dedent
+ import re
+ try:
+ exec('import')
+ except SyntaxError:
+ result = ''.join(format_exception(*sys.exc_info(), as_html=True))
+ expected = dedent("""\
+ <p>Traceback (most recent call last):</p>
+ <ul>
+ <li> Module zope.exceptions.tests.test_exceptionformatter, line ABC, in test_format_exception_as_html<br />
+ exec('import')</li>
+ </ul><p> File "&lt;string&gt;", line 1<br />
+ import<br />
+ ^<br />
+ SyntaxError: invalid syntax<br />
+ </p>""")
+ # HTML formatter uses Windows line endings for some reason.
+ result = result.replace('\r\n', '\n')
+ result = re.sub(r'line \d\d\d,', 'line ABC,', result)
+ self.maxDiff = None
+ self.assertEqual(expected, result)
+
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
+ import io
+ buf = io.StringIO() if bytes is not str else io.BytesIO()
+
from zope.exceptions.exceptionformatter import print_exception
t, v, b = sys.exc_info()
try:
@@ -718,7 +763,6 @@ class Test_print_exception(unittest.TestCase):
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:
@@ -752,7 +796,7 @@ class Test_extract_stack(unittest.TestCase):
def test_traceback_info_html(self):
try:
- __traceback_info__ = "Adam & Eve"
+ __traceback_info__ = u"Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(True)
@@ -761,7 +805,7 @@ class Test_extract_stack(unittest.TestCase):
def test_traceback_supplement_text(self):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
- "You're one in a million")
+ u"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = self._callFUT(False)
@@ -803,7 +847,7 @@ class Test_extract_stack(unittest.TestCase):
self.assertTrue(s.find('test_noinput') >= 0)
-class ExceptionForTesting (Exception):
+class ExceptionForTesting(Exception):
pass
@@ -860,30 +904,7 @@ class _Monkey(object):
delattr(self.module, key)
-def doctest_format_exception_as_html():
- """Test for format_exception (as_html=True)
-
- >>> from zope.exceptions.exceptionformatter import format_exception
- >>> try:
- ... exec('import 2 + 2')
- ... except:
- ... print(''.join(format_exception(*sys.exc_info(), as_html=True)))
- <p>Traceback (most recent call last):</p>
- <ul>
- <li> Module zope.exceptions.tests.test_exceptionformatter, line 2, in &lt;module&gt;<br />
- exec('import 2 + 2')</li>
- </ul><p> File "&lt;string&gt;", line 1<br />
- import 2 + 2<br />
- ^<br />
- SyntaxError: invalid syntax<br />
- </p>
-
- """
def test_suite():
- return unittest.TestSuite([
- unittest.defaultTestLoader.loadTestsFromName(__name__),
- doctest.DocTestSuite(
- optionflags=doctest.NORMALIZE_WHITESPACE),
- ])
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/src/zope/exceptions/tests/test_log.py b/src/zope/exceptions/tests/test_log.py
index d63385a..9e01eea 100644
--- a/src/zope/exceptions/tests/test_log.py
+++ b/src/zope/exceptions/tests/test_log.py
@@ -28,7 +28,6 @@ class FormatterTests(unittest.TestCase):
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))
@@ -38,14 +37,40 @@ class FormatterTests(unittest.TestCase):
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
+ traceback.format_exception_only(
+ ValueError, exc)[0][:-1]) #trailing \n
+
+ def test_unicode_traceback_info(self):
+ import traceback
+ __traceback_info__ = u"Have a Snowman: \u2603"
+ tb = DummyTB()
+ tb.tb_frame.f_locals['__traceback_info__'] = __traceback_info__
+ exc = ValueError('testing')
+ fmt = self._makeOne()
+ result = fmt.formatException((ValueError, exc, tb))
+ self.assertIsInstance(result, str)
+ lines = result.splitlines()
+ self.assertEqual(len(lines), 4)
+ self.assertEqual(lines[0], 'Traceback (most recent call last):')
+ self.assertEqual(lines[1], ' File "dummy/filename.py", line 14, '
+ 'in dummy_function')
+ expected = ' - __traceback_info__: Have a Snowman: '
+ # utf-8 encoded on Python 2, unicode on Python 3
+ expected += '\xe2\x98\x83' if bytes is str else u'\u2603'
+
+ self.assertEqual(lines[2],
+ expected)
+ self.assertEqual(lines[3],
+ traceback.format_exception_only(
+ ValueError, exc)[0][:-1]) #trailing \n
class DummyTB(object):
tb_lineno = 14
tb_next = None
+ def __init__(self):
+ self.tb_frame = DummyFrame()
class DummyFrame(object):
f_lineno = 137
diff --git a/tox.ini b/tox.ini
index ce87117..c9423d8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,22 +1,25 @@
[tox]
envlist =
- py27,py33,py34,py35,py36,pypy,pypy3,coverage,docs
+ py27,py34,py35,py36,pypy,pypy3,coverage,docs
[testenv]
commands =
zope-testrunner --test-path=src []
+ sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest
deps =
- .[test]
+ .[test,docs]
[testenv:coverage]
usedevelop = true
basepython =
python2.7
commands =
- nosetests --with-xunit --with-xcoverage
+ coverage run -m zope.testrunner --test-path=src []
+ coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest
+ coverage report --fail-under=100
deps =
- .[test,testing]
- nosexcover
+ {[testenv]deps}
+ coverage
[testenv:docs]
basepython =
@@ -24,5 +27,3 @@ basepython =
commands =
sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest
-deps =
- .[test,docs]