Merge pull request #938 from nicoddemus/doctest-unicode

New ALLOW_UNICODE doctest option
This commit is contained in:
Ronny Pfannschmidt 2015-08-16 11:35:10 +02:00
commit 37ed391cc2
4 changed files with 146 additions and 8 deletions

View File

@ -7,6 +7,11 @@
with parametrization markers.
Thanks to Markus Unterwaditzer for the PR.
- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
``u`` prefix is stripped from unicode strings in expected doctest output. This
allows doctests which use unicode to run in Python 2 and 3 unchanged.
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
- parametrize now also generates meaningful test IDs for enum, regex and class
objects (as opposed to class instances).
Thanks to Florian Bruhin for the PR.

View File

@ -63,7 +63,7 @@ class DoctestItem(pytest.Item):
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = doctest.OutputChecker()
checker = _get_unicode_checker()
REPORT_UDIFF = doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
lines = []
@ -100,7 +100,8 @@ def _get_flag_lookup():
NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
ELLIPSIS=doctest.ELLIPSIS,
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS)
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
ALLOW_UNICODE=_get_allow_unicode_flag())
def get_optionflags(parent):
optionflags_str = parent.config.getini("doctest_optionflags")
@ -110,15 +111,30 @@ def get_optionflags(parent):
flag_acc |= flag_lookup_table[flag]
return flag_acc
class DoctestTextfile(DoctestItem, pytest.File):
def runtest(self):
import doctest
fixture_request = _setup_fixtures(self)
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=get_optionflags(self),
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=0)
# inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker
text = self.fspath.read()
filename = str(self.fspath)
name = self.fspath.basename
globs = dict(getfixture=fixture_request.getfuncargvalue)
if '__name__' not in globs:
globs['__name__'] = '__main__'
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
runner.run(test)
class DoctestModule(pytest.File):
def collect(self):
@ -139,7 +155,8 @@ class DoctestModule(pytest.File):
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
for test in finder.find(module, module.__name__,
extraglobs=doctest_globals):
if test.examples: # skip empty doctests
@ -160,3 +177,59 @@ def _setup_fixtures(doctest_item):
fixture_request = FixtureRequest(doctest_item)
fixture_request._fillfixtures()
return fixture_request
def _get_unicode_checker():
"""
Returns a doctest.OutputChecker subclass that takes in account the
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
when the same doctest should run in Python 2 and Python 3.
An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
return _get_unicode_checker.UnicodeOutputChecker()
import doctest
import re
class UnicodeOutputChecker(doctest.OutputChecker):
"""
Copied from doctest_nose_plugin.py from the nltk project:
https://github.com/nltk/nltk
"""
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
def check_output(self, want, got, optionflags):
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
if res:
return True
if not (optionflags & _get_allow_unicode_flag()):
return False
else: # pragma: no cover
# the code below will end up executed only in Python 2 in
# our tests, and our coverage check runs in Python 3 only
def remove_u_prefixes(txt):
return re.sub(self._literal_re, r'\1\2', txt)
want = remove_u_prefixes(want)
got = remove_u_prefixes(got)
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
return res
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
return _get_unicode_checker.UnicodeOutputChecker()
def _get_allow_unicode_flag():
"""
Registers and returns the ALLOW_UNICODE flag.
"""
import doctest
return doctest.register_optionflag('ALLOW_UNICODE')

View File

@ -72,3 +72,18 @@ ignore lengthy exception stack traces you can just write::
# content of pytest.ini
[pytest]
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
py.test also introduces a new ``ALLOW_UNICODE`` option flag: when enabled, the
``u`` prefix is stripped from unicode strings in expected doctest output. This
allows doctests which use unicode to run in Python 2 and 3 unchanged.
As with any other option flag, this flag can be enabled in ``pytest.ini`` using
the ``doctest_optionflags`` ini option or by an inline comment in the doc test
itself::
# content of example.rst
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
'Hello'

View File

@ -1,5 +1,7 @@
import sys
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
import py
import pytest
class TestDoctests:
@ -401,3 +403,46 @@ class TestDoctests:
result = testdir.runpytest("--doctest-modules")
result.stdout.fnmatch_lines('*2 passed*')
@pytest.mark.parametrize('config_mode', ['ini', 'comment'])
def test_allow_unicode(self, testdir, config_mode):
"""Test that doctests which output unicode work in all python versions
tested by pytest when the ALLOW_UNICODE option is used (either in
the ini file or by an inline comment).
"""
if config_mode == 'ini':
testdir.makeini('''
[pytest]
doctest_optionflags = ALLOW_UNICODE
''')
comment = ''
else:
comment = '#doctest: +ALLOW_UNICODE'
testdir.maketxtfile(test_doc="""
>>> b'12'.decode('ascii') {comment}
'12'
""".format(comment=comment))
testdir.makepyfile(foo="""
def foo():
'''
>>> b'12'.decode('ascii') {comment}
'12'
'''
""".format(comment=comment))
reprec = testdir.inline_run("--doctest-modules")
reprec.assertoutcome(passed=2)
def test_unicode_string(self, testdir):
"""Test that doctests which output unicode fail in Python 2 when
the ALLOW_UNICODE option is not used. The same test should pass
in Python 3.
"""
testdir.maketxtfile(test_doc="""
>>> b'12'.decode('ascii')
'12'
""")
reprec = testdir.inline_run()
passed = int(sys.version_info[0] >= 3)
reprec.assertoutcome(passed=passed, failed=int(not passed))