:maxdepth: 2
+ release-6.0.1
release-6.0.0
release-6.0.0rc1
release-5.4.3
--- /dev/null
+pytest-6.0.1
+=======================================
+
+pytest 6.0.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+ pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Mattreex
+* Ran Benita
+* hp310780
+
+
+Happy testing,
+The pytest Development Team
.. towncrier release notes start
+pytest 6.0.1 (2020-07-30)
+=========================
+
+Bug Fixes
+---------
+
+- `#7394 <https://github.com/pytest-dev/pytest/issues/7394>`_: Passing an empty ``help`` value to ``Parser.add_option`` is now accepted instead of crashing when running ``pytest --help``.
+ Passing ``None`` raises a more informative ``TypeError``.
+
+
+- `#7558 <https://github.com/pytest-dev/pytest/issues/7558>`_: Fix pylint ``not-callable`` lint on ``pytest.mark.parametrize()`` and the other builtin marks:
+ ``skip``, ``skipif``, ``xfail``, ``usefixtures``, ``filterwarnings``.
+
+
+- `#7559 <https://github.com/pytest-dev/pytest/issues/7559>`_: Fix regression in plugins using ``TestReport.longreprtext`` (such as ``pytest-html``) when ``TestReport.longrepr`` is not a string.
+
+
+- `#7569 <https://github.com/pytest-dev/pytest/issues/7569>`_: Fix logging capture handler's level not reset on teardown after a call to ``caplog.set_level()``.
+
+
pytest 6.0.0 (2020-07-28)
=========================
.. code-block:: bash
$ pytest --version
- pytest 6.0.0
+ pytest 6.0.1
.. _`simpletest`:
pytest --basetemp=mydir
-When distributing tests on the local machine, ``pytest`` takes care to
-configure a basetemp directory for the sub processes such that all temporary
+.. warning::
+
+ The contents of ``mydir`` will be completely removed, so make sure to use a directory
+ for that purpose only.
+
+When distributing tests on the local machine using ``pytest-xdist``, care is taken to
+automatically configure a basetemp directory for the sub processes such that all temporary
data lands below a single per-test run basetemp directory.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
help, type, default = config._parser._inidict[name]
if type is None:
type = "string"
+ if help is None:
+ raise TypeError("help argument cannot be None for {}".format(name))
spec = "{} ({}):".format(name, type)
tw.write(" %s" % spec)
spec_len = len(spec)
tw.write(" " * (indent_len - spec_len - 2))
wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False)
- tw.line(wrapped[0])
- for line in wrapped[1:]:
- tw.line(indent + line)
+ if wrapped:
+ tw.line(wrapped[0])
+ for line in wrapped[1:]:
+ tw.line(indent + line)
tw.line()
tw.line("environment variables:")
"""Creates a new funcarg."""
self._item = item
# dict of log name -> log level
+ self._initial_handler_level = None # type: Optional[int]
self._initial_logger_levels = {} # type: Dict[Optional[str], int]
def _finalize(self) -> None:
This restores the log levels changed by :meth:`set_level`.
"""
# restore log levels
+ if self._initial_handler_level is not None:
+ self.handler.setLevel(self._initial_handler_level)
for logger_name, level in self._initial_logger_levels.items():
logger = logging.getLogger(logger_name)
logger.setLevel(level)
# save the original log-level to restore it during teardown
self._initial_logger_levels.setdefault(logger, logger_obj.level)
logger_obj.setLevel(level)
+ self._initial_handler_level = self.handler.level
self.handler.setLevel(level)
@contextmanager
import warnings
from typing import Any
from typing import Callable
-from typing import cast
from typing import Iterable
from typing import List
from typing import Mapping
# See TYPE_CHECKING above.
if TYPE_CHECKING:
- # Using casts instead of type comments intentionally - issue #7473.
# TODO(py36): Change to builtin annotation syntax.
- skip = cast(_SkipMarkDecorator, None)
- skipif = cast(_SkipifMarkDecorator, None)
- xfail = cast(_XfailMarkDecorator, None)
- parametrize = cast(_ParametrizeMarkDecorator, None)
- usefixtures = cast(_UsefixturesMarkDecorator, None)
- filterwarnings = cast(_FilterwarningsMarkDecorator, None)
+ skip = _SkipMarkDecorator(Mark("skip", (), {}))
+ skipif = _SkipifMarkDecorator(Mark("skipif", (), {}))
+ xfail = _XfailMarkDecorator(Mark("xfail", (), {}))
+ parametrize = _ParametrizeMarkDecorator(Mark("parametrize", (), {}))
+ usefixtures = _UsefixturesMarkDecorator(Mark("usefixtures", (), {}))
+ filterwarnings = _FilterwarningsMarkDecorator(Mark("filterwarnings", (), {}))
def __getattr__(self, name: str) -> MarkDecorator:
if name[0] == "_":
longrepr.toterminal(out)
else:
try:
- out.line(longrepr)
+ s = str(longrepr)
except UnicodeEncodeError:
- out.line("<unprintable longrepr>")
+ s = "<unprintable longrepr>"
+ out.line(s)
def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]:
for name, content in self.sections:
import pytest
from _pytest.logging import caplog_records_key
+from _pytest.pytester import Testdir
logger = logging.getLogger(__name__)
sublogger = logging.getLogger(__name__ + ".baz")
assert "CRITICAL" in caplog.text
-def test_change_level_undo(testdir):
- """Ensure that 'set_level' is undone after the end of the test"""
+def test_change_level_undo(testdir: Testdir) -> None:
+ """Ensure that 'set_level' is undone after the end of the test.
+
+ Tests the logging output themselves (affacted both by logger and handler levels).
+ """
testdir.makepyfile(
"""
import logging
result.stdout.no_fnmatch_line("*log from test2*")
+def test_change_level_undos_handler_level(testdir: Testdir) -> None:
+ """Ensure that 'set_level' is undone after the end of the test (handler).
+
+ Issue #7569. Tests the handler level specifically.
+ """
+ testdir.makepyfile(
+ """
+ import logging
+
+ def test1(caplog):
+ assert caplog.handler.level == 0
+ caplog.set_level(41)
+ assert caplog.handler.level == 41
+
+ def test2(caplog):
+ assert caplog.handler.level == 0
+
+ def test3(caplog):
+ assert caplog.handler.level == 0
+ caplog.set_level(43)
+ assert caplog.handler.level == 43
+ """
+ )
+ result = testdir.runpytest()
+ result.assert_outcomes(passed=3)
+
+
def test_with_statement(caplog):
with caplog.at_level(logging.INFO):
logger.debug("handler DEBUG level")
)
+def test_none_help_param_raises_exception(testdir):
+ """Tests a None help param raises a TypeError.
+ """
+ testdir.makeconftest(
+ """
+ def pytest_addoption(parser):
+ parser.addini("test_ini", None, default=True, type="bool")
+ """
+ )
+ result = testdir.runpytest("--help")
+ result.stderr.fnmatch_lines(
+ ["*TypeError: help argument cannot be None for test_ini*"]
+ )
+
+
+def test_empty_help_param(testdir):
+ """Tests an empty help param is displayed correctly.
+ """
+ testdir.makeconftest(
+ """
+ def pytest_addoption(parser):
+ parser.addini("test_ini", "", default=True, type="bool")
+ """
+ )
+ result = testdir.runpytest("--help")
+ assert result.ret == 0
+ lines = [
+ " required_plugins (args):",
+ " plugins that must be present for pytest to run*",
+ " test_ini (bool):*",
+ "environment variables:",
+ ]
+ result.stdout.fnmatch_lines(lines, consecutive=True)
+
+
def test_hookvalidation_unknown(testdir):
testdir.makeconftest(
"""
rep = reports[1]
assert rep.longreprtext == ""
+ def test_longreprtext_skip(self, testdir) -> None:
+ """TestReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)"""
+ reports = testdir.runitem(
+ """
+ import pytest
+ def test_func():
+ pytest.skip()
+ """
+ )
+ _, call_rep, _ = reports
+ assert isinstance(call_rep.longrepr, tuple)
+ assert "Skipped" in call_rep.longreprtext
+
+ def test_longreprtext_collect_skip(self, testdir) -> None:
+ """CollectReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)"""
+ testdir.makepyfile(
+ """
+ import pytest
+ pytest.skip(allow_module_level=True)
+ """
+ )
+ rec = testdir.inline_run()
+ calls = rec.getcalls("pytest_collectreport")
+ _, call = calls
+ assert isinstance(call.report.longrepr, tuple)
+ assert "Skipped" in call.report.longreprtext
+
def test_longreprtext_failure(self, testdir) -> None:
reports = testdir.runitem(
"""