Merge pull request #4524 from blueyed/merge-master

Merge master into features
This commit is contained in:
Daniel Hahler 2018-12-10 10:17:23 +01:00 committed by GitHub
commit 038f1f94c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 98 additions and 20 deletions

1
changelog/1495.doc.rst Normal file
View File

@ -0,0 +1 @@
Document common doctest fixture directory tree structure pitfalls

View File

@ -0,0 +1 @@
Validate arguments from the ``PYTEST_ADDOPTS`` environment variable and the ``addopts`` ini option separately.

View File

@ -0,0 +1 @@
When a fixture yields and a log call is made after the test runs, and, if the test is interrupted, capture attributes are ``None``.

View File

@ -154,6 +154,9 @@ which can then be used in your doctests directly::
""" """
pass pass
Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in.
Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
Fixtures will not be discovered in a sibling directory tree!
Output format Output format
------------- -------------

View File

@ -156,4 +156,4 @@ More details can be found in the `original PR <https://github.com/pytest-dev/pyt
.. note:: .. note::
in a future major relase of pytest we will introduce class based markers, in a future major relase of pytest we will introduce class based markers,
at which points markers will no longer be limited to instances of :py:class:`Mark` at which point markers will no longer be limited to instances of :py:class:`Mark`

View File

@ -1,5 +1,3 @@
pygments-pytest>=1.0.4 pygments-pytest>=1.1.0
# pinning sphinx to 1.4.* due to search issues with rtd: sphinx>=1.8.2
# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25
sphinx ==1.4.*
sphinxcontrib-trio sphinxcontrib-trio

View File

@ -117,7 +117,10 @@ class CaptureManager(object):
self._global_capturing = None self._global_capturing = None
def resume_global_capture(self): def resume_global_capture(self):
self._global_capturing.resume_capturing() # During teardown of the python process, and on rare occasions, capture
# attributes can be `None` while trying to resume global capture.
if self._global_capturing is not None:
self._global_capturing.resume_capturing()
def suspend_global_capture(self, in_=False): def suspend_global_capture(self, in_=False):
cap = getattr(self, "_global_capturing", None) cap = getattr(self, "_global_capturing", None)

View File

@ -777,12 +777,21 @@ class Config(object):
for name in _iter_rewritable_modules(package_files): for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name) hook.mark_rewrite(name)
def _validate_args(self, args):
"""Validate known args."""
self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
return args
def _preparse(self, args, addopts=True): def _preparse(self, args, addopts=True):
if addopts: if addopts:
args[:] = shlex.split(os.environ.get("PYTEST_ADDOPTS", "")) + args env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
if len(env_addopts):
args[:] = self._validate_args(shlex.split(env_addopts)) + args
self._initini(args) self._initini(args)
if addopts: if addopts:
args[:] = self.getini("addopts") + args args[:] = self._validate_args(self.getini("addopts")) + args
self._checkversion() self._checkversion()
self._consider_importhook(args) self._consider_importhook(args)
self.pluginmanager.consider_preparse(args) self.pluginmanager.consider_preparse(args)

View File

@ -197,7 +197,8 @@ class TestRaises(object):
pass pass
with pytest.raises( with pytest.raises(
Failed, match="DID NOT RAISE <class 'raises.ClassLooksIterableException'>" Failed,
match=r"DID NOT RAISE <class 'raises(\..*)*ClassLooksIterableException'>",
): ):
pytest.raises(ClassLooksIterableException, lambda: None) pytest.raises(ClassLooksIterableException, lambda: None)

View File

@ -302,14 +302,14 @@ class TestLoggingInteraction(object):
"""\ """\
import logging import logging
def setup_function(function): def setup_function(function):
logging.warn("hello1") logging.warning("hello1")
def test_logging(): def test_logging():
logging.warn("hello2") logging.warning("hello2")
assert 0 assert 0
def teardown_function(function): def teardown_function(function):
logging.warn("hello3") logging.warning("hello3")
assert 0 assert 0
""" """
) )
@ -328,14 +328,14 @@ class TestLoggingInteraction(object):
"""\ """\
import logging import logging
def setup_module(function): def setup_module(function):
logging.warn("hello1") logging.warning("hello1")
def test_logging(): def test_logging():
logging.warn("hello2") logging.warning("hello2")
assert 0 assert 0
def teardown_module(function): def teardown_module(function):
logging.warn("hello3") logging.warning("hello3")
assert 0 assert 0
""" """
) )
@ -354,7 +354,7 @@ class TestLoggingInteraction(object):
"""\ """\
import logging import logging
logging.basicConfig() logging.basicConfig()
logging.warn("hello435") logging.warning("hello435")
""" """
) )
# make sure that logging is still captured in tests # make sure that logging is still captured in tests
@ -375,7 +375,7 @@ class TestLoggingInteraction(object):
"""\ """\
def test_hello(): def test_hello():
import logging import logging
logging.warn("hello433") logging.warning("hello433")
assert 0 assert 0
""" """
) )
@ -385,6 +385,40 @@ class TestLoggingInteraction(object):
assert "something" not in result.stderr.str() assert "something" not in result.stderr.str()
assert "operation on closed file" not in result.stderr.str() assert "operation on closed file" not in result.stderr.str()
def test_logging_after_cap_stopped(self, testdir):
testdir.makeconftest(
"""\
import pytest
import logging
log = logging.getLogger(__name__)
@pytest.fixture
def log_on_teardown():
yield
log.warning('Logging on teardown')
"""
)
# make sure that logging is still captured in tests
p = testdir.makepyfile(
"""\
def test_hello(log_on_teardown):
import logging
logging.warning("hello433")
assert 1
raise KeyboardInterrupt()
"""
)
result = testdir.runpytest_subprocess(p, "--log-cli-level", "info")
assert result.ret != 0
result.stdout.fnmatch_lines(
["*WARNING*hello433*", "*WARNING*Logging on teardown*"]
)
assert (
"AttributeError: 'NoneType' object has no attribute 'resume_capturing'"
not in result.stderr.str()
)
class TestCaptureFixture(object): class TestCaptureFixture(object):
@pytest.mark.parametrize("opt", [[], ["-s"]]) @pytest.mark.parametrize("opt", [[], ["-s"]])
@ -1300,13 +1334,13 @@ def test_capturing_and_logging_fundamentals(testdir, method):
Capture=capture.%s) Capture=capture.%s)
cap.start_capturing() cap.start_capturing()
logging.warn("hello1") logging.warning("hello1")
outerr = cap.readouterr() outerr = cap.readouterr()
print("suspend, captured %%s" %%(outerr,)) print("suspend, captured %%s" %%(outerr,))
logging.warn("hello2") logging.warning("hello2")
cap.pop_outerr_to_orig() cap.pop_outerr_to_orig()
logging.warn("hello3") logging.warning("hello3")
outerr = cap.readouterr() outerr = cap.readouterr()
print("suspend2, captured %%s" %% (outerr,)) print("suspend2, captured %%s" %% (outerr,))

View File

@ -1083,6 +1083,33 @@ class TestOverrideIniArgs(object):
config._preparse([], addopts=True) config._preparse([], addopts=True)
assert config._override_ini == ["cache_dir=%s" % cache_dir] assert config._override_ini == ["cache_dir=%s" % cache_dir]
def test_addopts_from_env_not_concatenated(self, monkeypatch):
"""PYTEST_ADDOPTS should not take values from normal args (#4265)."""
from _pytest.config import get_config
monkeypatch.setenv("PYTEST_ADDOPTS", "-o")
config = get_config()
with pytest.raises(SystemExit) as excinfo:
config._preparse(["cache_dir=ignored"], addopts=True)
assert excinfo.value.args[0] == _pytest.main.EXIT_USAGEERROR
def test_addopts_from_ini_not_concatenated(self, testdir):
"""addopts from ini should not take values from normal args (#4265)."""
testdir.makeini(
"""
[pytest]
addopts=-o
"""
)
result = testdir.runpytest("cache_dir=ignored")
result.stderr.fnmatch_lines(
[
"%s: error: argument -o/--override-ini: expected one argument"
% (testdir.request.config._parser.optparser.prog,)
]
)
assert result.ret == _pytest.main.EXIT_USAGEERROR
def test_override_ini_does_not_contain_paths(self): def test_override_ini_does_not_contain_paths(self):
"""Check that -o no longer swallows all options after it (#3103)""" """Check that -o no longer swallows all options after it (#3103)"""
from _pytest.config import get_config from _pytest.config import get_config