Merge pull request #4212 from RonnyPfannschmidt/doctest-testmod-has-call
Doctest: hack in handling mock style objects
This commit is contained in:
commit
5f16ff3acc
|
@ -0,0 +1 @@
|
||||||
|
Extend Doctest-modules to ignore mock objects.
|
|
@ -3,17 +3,19 @@ from __future__ import absolute_import
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import inspect
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import ReprFileLocation
|
from _pytest._code.code import ReprFileLocation
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
|
|
||||||
|
|
||||||
DOCTEST_REPORT_CHOICE_NONE = "none"
|
DOCTEST_REPORT_CHOICE_NONE = "none"
|
||||||
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
|
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
|
||||||
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
|
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
|
||||||
|
@ -346,10 +348,61 @@ def _check_all_skipped(test):
|
||||||
pytest.skip("all tests skipped by +SKIP option")
|
pytest.skip("all tests skipped by +SKIP option")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_mocked(obj):
|
||||||
|
"""
|
||||||
|
returns if a object is possibly a mock object by checking the existence of a highly improbable attribute
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
|
||||||
|
is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _patch_unwrap_mock_aware():
|
||||||
|
"""
|
||||||
|
contextmanager which replaces ``inspect.unwrap`` with a version
|
||||||
|
that's aware of mock objects and doesn't recurse on them
|
||||||
|
"""
|
||||||
|
real_unwrap = getattr(inspect, "unwrap", None)
|
||||||
|
if real_unwrap is None:
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
|
||||||
|
def _mock_aware_unwrap(obj, stop=None):
|
||||||
|
if stop is None:
|
||||||
|
return real_unwrap(obj, stop=_is_mocked)
|
||||||
|
else:
|
||||||
|
return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj))
|
||||||
|
|
||||||
|
inspect.unwrap = _mock_aware_unwrap
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
inspect.unwrap = real_unwrap
|
||||||
|
|
||||||
|
|
||||||
class DoctestModule(pytest.Module):
|
class DoctestModule(pytest.Module):
|
||||||
def collect(self):
|
def collect(self):
|
||||||
import doctest
|
import doctest
|
||||||
|
|
||||||
|
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
||||||
|
"""
|
||||||
|
a hackish doctest finder that overrides stdlib internals to fix a stdlib bug
|
||||||
|
|
||||||
|
https://github.com/pytest-dev/pytest/issues/3456
|
||||||
|
https://bugs.python.org/issue25532
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
||||||
|
if _is_mocked(obj):
|
||||||
|
return
|
||||||
|
with _patch_unwrap_mock_aware():
|
||||||
|
|
||||||
|
doctest.DocTestFinder._find(
|
||||||
|
self, tests, obj, name, module, source_lines, globs, seen
|
||||||
|
)
|
||||||
|
|
||||||
if self.fspath.basename == "conftest.py":
|
if self.fspath.basename == "conftest.py":
|
||||||
module = self.config.pluginmanager._importconftest(self.fspath)
|
module = self.config.pluginmanager._importconftest(self.fspath)
|
||||||
else:
|
else:
|
||||||
|
@ -361,7 +414,7 @@ class DoctestModule(pytest.Module):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
# uses internal doctest module parsing mechanism
|
# uses internal doctest module parsing mechanism
|
||||||
finder = doctest.DocTestFinder()
|
finder = MockAwareDocTestFinder()
|
||||||
optionflags = get_optionflags(self)
|
optionflags = get_optionflags(self)
|
||||||
runner = _get_runner(
|
runner = _get_runner(
|
||||||
verbose=0,
|
verbose=0,
|
||||||
|
|
|
@ -1206,3 +1206,22 @@ class TestDoctestReportingOption(object):
|
||||||
"*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*"
|
"*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_module", ["mock", "unittest.mock"])
|
||||||
|
def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, testdir):
|
||||||
|
pytest.importorskip(mock_module)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
from {mock_module} import call
|
||||||
|
class Example(object):
|
||||||
|
'''
|
||||||
|
>>> 1 + 1
|
||||||
|
2
|
||||||
|
'''
|
||||||
|
""".format(
|
||||||
|
mock_module=mock_module
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--doctest-modules")
|
||||||
|
result.stdout.fnmatch_lines(["* 1 passed *"])
|
||||||
|
|
Loading…
Reference in New Issue