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
|
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
|
||||||
for contributing and Bruno Oliveira for the PR.
|
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
|
2.8.1
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import pytest, py
|
||||||
from _pytest.python import FixtureRequest
|
from _pytest.python import FixtureRequest
|
||||||
from py._code.code import TerminalRepr, ReprFileLocation
|
from py._code.code import TerminalRepr, ReprFileLocation
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addini('doctest_optionflags', 'option flags for doctests',
|
parser.addini('doctest_optionflags', 'option flags for doctests',
|
||||||
type="args", default=["ELLIPSIS"])
|
type="args", default=["ELLIPSIS"])
|
||||||
|
@ -22,6 +23,7 @@ def pytest_addoption(parser):
|
||||||
help="ignore doctest ImportErrors",
|
help="ignore doctest ImportErrors",
|
||||||
dest="doctest_ignore_import_errors")
|
dest="doctest_ignore_import_errors")
|
||||||
|
|
||||||
|
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_collect_file(path, parent):
|
||||||
config = parent.config
|
config = parent.config
|
||||||
if path.ext == ".py":
|
if path.ext == ".py":
|
||||||
|
@ -31,20 +33,33 @@ def pytest_collect_file(path, parent):
|
||||||
path.check(fnmatch=config.getvalue("doctestglob")):
|
path.check(fnmatch=config.getvalue("doctestglob")):
|
||||||
return DoctestTextfile(path, parent)
|
return DoctestTextfile(path, parent)
|
||||||
|
|
||||||
|
|
||||||
class ReprFailDoctest(TerminalRepr):
|
class ReprFailDoctest(TerminalRepr):
|
||||||
|
|
||||||
def __init__(self, reprlocation, lines):
|
def __init__(self, reprlocation, lines):
|
||||||
self.reprlocation = reprlocation
|
self.reprlocation = reprlocation
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
|
|
||||||
def toterminal(self, tw):
|
def toterminal(self, tw):
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
tw.line(line)
|
tw.line(line)
|
||||||
self.reprlocation.toterminal(tw)
|
self.reprlocation.toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
class DoctestItem(pytest.Item):
|
class DoctestItem(pytest.Item):
|
||||||
|
|
||||||
def __init__(self, name, parent, runner=None, dtest=None):
|
def __init__(self, name, parent, runner=None, dtest=None):
|
||||||
super(DoctestItem, self).__init__(name, parent)
|
super(DoctestItem, self).__init__(name, parent)
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
self.dtest = dtest
|
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):
|
def runtest(self):
|
||||||
_check_all_skipped(self.dtest)
|
_check_all_skipped(self.dtest)
|
||||||
|
@ -94,6 +109,7 @@ class DoctestItem(pytest.Item):
|
||||||
def reportinfo(self):
|
def reportinfo(self):
|
||||||
return self.fspath, None, "[doctest] %s" % self.name
|
return self.fspath, None, "[doctest] %s" % self.name
|
||||||
|
|
||||||
|
|
||||||
def _get_flag_lookup():
|
def _get_flag_lookup():
|
||||||
import doctest
|
import doctest
|
||||||
return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
|
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,
|
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
|
||||||
ALLOW_UNICODE=_get_allow_unicode_flag())
|
ALLOW_UNICODE=_get_allow_unicode_flag())
|
||||||
|
|
||||||
|
|
||||||
def get_optionflags(parent):
|
def get_optionflags(parent):
|
||||||
optionflags_str = parent.config.getini("doctest_optionflags")
|
optionflags_str = parent.config.getini("doctest_optionflags")
|
||||||
flag_lookup_table = _get_flag_lookup()
|
flag_lookup_table = _get_flag_lookup()
|
||||||
|
@ -113,7 +130,7 @@ def get_optionflags(parent):
|
||||||
return flag_acc
|
return flag_acc
|
||||||
|
|
||||||
|
|
||||||
class DoctestTextfile(DoctestItem, pytest.File):
|
class DoctestTextfile(DoctestItem, pytest.Module):
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
import doctest
|
import doctest
|
||||||
|
@ -148,7 +165,7 @@ def _check_all_skipped(test):
|
||||||
pytest.skip('all tests skipped by +SKIP option')
|
pytest.skip('all tests skipped by +SKIP option')
|
||||||
|
|
||||||
|
|
||||||
class DoctestModule(pytest.File):
|
class DoctestModule(pytest.Module):
|
||||||
def collect(self):
|
def collect(self):
|
||||||
import doctest
|
import doctest
|
||||||
if self.fspath.basename == "conftest.py":
|
if self.fspath.basename == "conftest.py":
|
||||||
|
@ -161,23 +178,19 @@ class DoctestModule(pytest.File):
|
||||||
pytest.skip('unable to import module %r' % self.fspath)
|
pytest.skip('unable to import module %r' % self.fspath)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
# satisfy `FixtureRequest` constructor...
|
|
||||||
fixture_request = _setup_fixtures(self)
|
|
||||||
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
|
|
||||||
# uses internal doctest module parsing mechanism
|
# uses internal doctest module parsing mechanism
|
||||||
finder = doctest.DocTestFinder()
|
finder = doctest.DocTestFinder()
|
||||||
optionflags = get_optionflags(self)
|
optionflags = get_optionflags(self)
|
||||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||||
checker=_get_unicode_checker())
|
checker=_get_unicode_checker())
|
||||||
for test in finder.find(module, module.__name__,
|
for test in finder.find(module, module.__name__):
|
||||||
extraglobs=doctest_globals):
|
|
||||||
if test.examples: # skip empty doctests
|
if test.examples: # skip empty doctests
|
||||||
yield DoctestItem(test.name, self, runner, test)
|
yield DoctestItem(test.name, self, runner, test)
|
||||||
|
|
||||||
|
|
||||||
def _setup_fixtures(doctest_item):
|
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():
|
def func():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -371,38 +371,6 @@ class TestDoctests:
|
||||||
"--junit-xml=junit.xml")
|
"--junit-xml=junit.xml")
|
||||||
reprec.assertoutcome(failed=1)
|
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'])
|
@pytest.mark.parametrize('config_mode', ['ini', 'comment'])
|
||||||
def test_allow_unicode(self, testdir, config_mode):
|
def test_allow_unicode(self, testdir, config_mode):
|
||||||
"""Test that doctests which output unicode work in all python versions
|
"""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))
|
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
|
If all examples in a doctest are skipped due to the SKIP option, then
|
||||||
the tests should be SKIPPED rather than PASSED. (#957)
|
the tests should be SKIPPED rather than PASSED. (#957)
|
||||||
|
@ -493,3 +461,122 @@ class TestDocTestSkips:
|
||||||
""")
|
""")
|
||||||
reprec = testdir.inline_run("--doctest-modules")
|
reprec = testdir.inline_run("--doctest-modules")
|
||||||
reprec.assertoutcome(skipped=1)
|
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