Merge pull request #1102 from nicoddemus/doctest-fixtures-fix
Fix autouse fixtures and doctest modules
This commit is contained in:
commit
db077555f6
|
@ -20,6 +20,10 @@
|
|||
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
|
||||
for contributing and Bruno Oliveira for the PR.
|
||||
|
||||
- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
|
||||
Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
|
||||
Oliveira for the PR.
|
||||
|
||||
2.8.1
|
||||
-----
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import pytest, py
|
|||
from _pytest.python import FixtureRequest
|
||||
from py._code.code import TerminalRepr, ReprFileLocation
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addini('doctest_optionflags', 'option flags for doctests',
|
||||
type="args", default=["ELLIPSIS"])
|
||||
|
@ -22,6 +23,7 @@ def pytest_addoption(parser):
|
|||
help="ignore doctest ImportErrors",
|
||||
dest="doctest_ignore_import_errors")
|
||||
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
config = parent.config
|
||||
if path.ext == ".py":
|
||||
|
@ -31,20 +33,33 @@ def pytest_collect_file(path, parent):
|
|||
path.check(fnmatch=config.getvalue("doctestglob")):
|
||||
return DoctestTextfile(path, parent)
|
||||
|
||||
|
||||
class ReprFailDoctest(TerminalRepr):
|
||||
|
||||
def __init__(self, reprlocation, lines):
|
||||
self.reprlocation = reprlocation
|
||||
self.lines = lines
|
||||
|
||||
def toterminal(self, tw):
|
||||
for line in self.lines:
|
||||
tw.line(line)
|
||||
self.reprlocation.toterminal(tw)
|
||||
|
||||
|
||||
class DoctestItem(pytest.Item):
|
||||
|
||||
def __init__(self, name, parent, runner=None, dtest=None):
|
||||
super(DoctestItem, self).__init__(name, parent)
|
||||
self.runner = runner
|
||||
self.dtest = dtest
|
||||
self.obj = None
|
||||
self.fixture_request = None
|
||||
|
||||
def setup(self):
|
||||
if self.dtest is not None:
|
||||
self.fixture_request = _setup_fixtures(self)
|
||||
globs = dict(getfixture=self.fixture_request.getfuncargvalue)
|
||||
self.dtest.globs.update(globs)
|
||||
|
||||
def runtest(self):
|
||||
_check_all_skipped(self.dtest)
|
||||
|
@ -94,6 +109,7 @@ class DoctestItem(pytest.Item):
|
|||
def reportinfo(self):
|
||||
return self.fspath, None, "[doctest] %s" % self.name
|
||||
|
||||
|
||||
def _get_flag_lookup():
|
||||
import doctest
|
||||
return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
|
||||
|
@ -104,6 +120,7 @@ def _get_flag_lookup():
|
|||
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
|
||||
ALLOW_UNICODE=_get_allow_unicode_flag())
|
||||
|
||||
|
||||
def get_optionflags(parent):
|
||||
optionflags_str = parent.config.getini("doctest_optionflags")
|
||||
flag_lookup_table = _get_flag_lookup()
|
||||
|
@ -113,7 +130,7 @@ def get_optionflags(parent):
|
|||
return flag_acc
|
||||
|
||||
|
||||
class DoctestTextfile(DoctestItem, pytest.File):
|
||||
class DoctestTextfile(DoctestItem, pytest.Module):
|
||||
|
||||
def runtest(self):
|
||||
import doctest
|
||||
|
@ -148,7 +165,7 @@ def _check_all_skipped(test):
|
|||
pytest.skip('all tests skipped by +SKIP option')
|
||||
|
||||
|
||||
class DoctestModule(pytest.File):
|
||||
class DoctestModule(pytest.Module):
|
||||
def collect(self):
|
||||
import doctest
|
||||
if self.fspath.basename == "conftest.py":
|
||||
|
@ -161,23 +178,19 @@ class DoctestModule(pytest.File):
|
|||
pytest.skip('unable to import module %r' % self.fspath)
|
||||
else:
|
||||
raise
|
||||
# satisfy `FixtureRequest` constructor...
|
||||
fixture_request = _setup_fixtures(self)
|
||||
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
|
||||
# uses internal doctest module parsing mechanism
|
||||
finder = doctest.DocTestFinder()
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
for test in finder.find(module, module.__name__,
|
||||
extraglobs=doctest_globals):
|
||||
for test in finder.find(module, module.__name__):
|
||||
if test.examples: # skip empty doctests
|
||||
yield DoctestItem(test.name, self, runner, test)
|
||||
|
||||
|
||||
def _setup_fixtures(doctest_item):
|
||||
"""
|
||||
Used by DoctestTextfile and DoctestModule to setup fixture information.
|
||||
Used by DoctestTextfile and DoctestItem to setup fixture information.
|
||||
"""
|
||||
def func():
|
||||
pass
|
||||
|
|
|
@ -371,38 +371,6 @@ class TestDoctests:
|
|||
"--junit-xml=junit.xml")
|
||||
reprec.assertoutcome(failed=1)
|
||||
|
||||
def test_doctest_module_session_fixture(self, testdir):
|
||||
"""Test that session fixtures are initialized for doctest modules (#768)
|
||||
"""
|
||||
# session fixture which changes some global data, which will
|
||||
# be accessed by doctests in a module
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
@pytest.yield_fixture(autouse=True, scope='session')
|
||||
def myfixture():
|
||||
assert not hasattr(sys, 'pytest_session_data')
|
||||
sys.pytest_session_data = 1
|
||||
yield
|
||||
del sys.pytest_session_data
|
||||
""")
|
||||
testdir.makepyfile(foo="""
|
||||
import sys
|
||||
|
||||
def foo():
|
||||
'''
|
||||
>>> assert sys.pytest_session_data == 1
|
||||
'''
|
||||
|
||||
def bar():
|
||||
'''
|
||||
>>> assert sys.pytest_session_data == 1
|
||||
'''
|
||||
""")
|
||||
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
|
||||
|
@ -446,7 +414,7 @@ class TestDoctests:
|
|||
reprec.assertoutcome(passed=passed, failed=int(not passed))
|
||||
|
||||
|
||||
class TestDocTestSkips:
|
||||
class TestDoctestSkips:
|
||||
"""
|
||||
If all examples in a doctest are skipped due to the SKIP option, then
|
||||
the tests should be SKIPPED rather than PASSED. (#957)
|
||||
|
@ -493,3 +461,122 @@ class TestDocTestSkips:
|
|||
""")
|
||||
reprec = testdir.inline_run("--doctest-modules")
|
||||
reprec.assertoutcome(skipped=1)
|
||||
|
||||
|
||||
class TestDoctestAutoUseFixtures:
|
||||
|
||||
SCOPES = ['module', 'session', 'class', 'function']
|
||||
|
||||
def test_doctest_module_session_fixture(self, testdir):
|
||||
"""Test that session fixtures are initialized for doctest modules (#768)
|
||||
"""
|
||||
# session fixture which changes some global data, which will
|
||||
# be accessed by doctests in a module
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
@pytest.yield_fixture(autouse=True, scope='session')
|
||||
def myfixture():
|
||||
assert not hasattr(sys, 'pytest_session_data')
|
||||
sys.pytest_session_data = 1
|
||||
yield
|
||||
del sys.pytest_session_data
|
||||
""")
|
||||
testdir.makepyfile(foo="""
|
||||
import sys
|
||||
|
||||
def foo():
|
||||
'''
|
||||
>>> assert sys.pytest_session_data == 1
|
||||
'''
|
||||
|
||||
def bar():
|
||||
'''
|
||||
>>> assert sys.pytest_session_data == 1
|
||||
'''
|
||||
""")
|
||||
result = testdir.runpytest("--doctest-modules")
|
||||
result.stdout.fnmatch_lines('*2 passed*')
|
||||
|
||||
@pytest.mark.parametrize('scope', SCOPES)
|
||||
@pytest.mark.parametrize('enable_doctest', [True, False])
|
||||
def test_fixture_scopes(self, testdir, scope, enable_doctest):
|
||||
"""Test that auto-use fixtures work properly with doctest modules.
|
||||
See #1057 and #1100.
|
||||
"""
|
||||
testdir.makeconftest('''
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(autouse=True, scope="{scope}")
|
||||
def auto(request):
|
||||
return 99
|
||||
'''.format(scope=scope))
|
||||
testdir.makepyfile(test_1='''
|
||||
def test_foo():
|
||||
"""
|
||||
>>> getfixture('auto') + 1
|
||||
100
|
||||
"""
|
||||
def test_bar():
|
||||
assert 1
|
||||
''')
|
||||
params = ('--doctest-modules',) if enable_doctest else ()
|
||||
passes = 3 if enable_doctest else 2
|
||||
result = testdir.runpytest(*params)
|
||||
result.stdout.fnmatch_lines(['*=== %d passed in *' % passes])
|
||||
|
||||
@pytest.mark.parametrize('scope', SCOPES)
|
||||
@pytest.mark.parametrize('autouse', [True, False])
|
||||
@pytest.mark.parametrize('use_fixture_in_doctest', [True, False])
|
||||
def test_fixture_module_doctest_scopes(self, testdir, scope, autouse,
|
||||
use_fixture_in_doctest):
|
||||
"""Test that auto-use fixtures work properly with doctest files.
|
||||
See #1057 and #1100.
|
||||
"""
|
||||
testdir.makeconftest('''
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(autouse={autouse}, scope="{scope}")
|
||||
def auto(request):
|
||||
return 99
|
||||
'''.format(scope=scope, autouse=autouse))
|
||||
if use_fixture_in_doctest:
|
||||
testdir.maketxtfile(test_doc="""
|
||||
>>> getfixture('auto')
|
||||
99
|
||||
""")
|
||||
else:
|
||||
testdir.maketxtfile(test_doc="""
|
||||
>>> 1 + 1
|
||||
2
|
||||
""")
|
||||
result = testdir.runpytest('--doctest-modules')
|
||||
assert 'FAILURES' not in str(result.stdout.str())
|
||||
result.stdout.fnmatch_lines(['*=== 1 passed in *'])
|
||||
|
||||
@pytest.mark.parametrize('scope', SCOPES)
|
||||
def test_auto_use_request_attributes(self, testdir, scope):
|
||||
"""Check that all attributes of a request in an autouse fixture
|
||||
behave as expected when requested for a doctest item.
|
||||
"""
|
||||
testdir.makeconftest('''
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(autouse=True, scope="{scope}")
|
||||
def auto(request):
|
||||
if "{scope}" == 'module':
|
||||
assert request.module is None
|
||||
if "{scope}" == 'class':
|
||||
assert request.cls is None
|
||||
if "{scope}" == 'function':
|
||||
assert request.function is None
|
||||
return 99
|
||||
'''.format(scope=scope))
|
||||
testdir.maketxtfile(test_doc="""
|
||||
>>> 1 + 1
|
||||
2
|
||||
""")
|
||||
result = testdir.runpytest('--doctest-modules')
|
||||
assert 'FAILURES' not in str(result.stdout.str())
|
||||
result.stdout.fnmatch_lines(['*=== 1 passed in *'])
|
Loading…
Reference in New Issue