Permalink
Comparing changes
Choose two branches to see what’s changed or to start a new pull request.
If you need to, you can also .
Open a pull request
Create a new pull request by comparing changes across two branches. If you need to, you can also .
- 15 commits
- 12 files changed
- 0 comments
- 5 contributors
Commits on Jul 28, 2020
Commits on Jul 29, 2020
mark: fix pylint not-callable error on pytest.mark.parametrize(...), again (cherry picked from commit f9837f9)
(cherry picked from commit 54e08b7)
[6.0.x] mark: fix pylint not-callable error on pytest.mark.parametrize(...), again
logging: fix capture handler level not reset on teardown after caplog.set_level()
[6.0.x] logging: fix capture handler level not reset on teardown after caplog.set_level()
[6.0.x] Merge pull request #7561 from nicoddemus/longreprtext-7559
Co-authored-by: mattreex <mattreex.9@gail.com> Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com> (cherry picked from commit 1e66ed0)
[6.0.x] Fix --help crash on add_ini(.., help='') and improve message on help=None (#7427)
[6.0.x] Warn about --basetemp removing the entire directory (#7555)
Commits on Jul 30, 2020
Add missing changelog for issue 7569 (cherry picked from commit 645cbc9)
[6.0.x] Add missing changelog for issue 7569
Unified
Split
Showing
with
164 additions
and 18 deletions.
- +1 −0 doc/en/announce/index.rst
- +21 −0 doc/en/announce/release-6.0.1.rst
- +20 −0 doc/en/changelog.rst
- +1 −1 doc/en/getting-started.rst
- +7 −2 doc/en/tmpdir.rst
- +6 −3 src/_pytest/helpconfig.py
- +4 −0 src/_pytest/logging.py
- +6 −8 src/_pytest/mark/structures.py
- +3 −2 src/_pytest/reports.py
- +33 −2 testing/logging/test_fixture.py
- +35 −0 testing/test_helpconfig.py
- +27 −0 testing/test_runner.py
| @@ -6,6 +6,7 @@ Release announcements | ||
| :maxdepth: 2 | ||
|
|
||
|
|
||
| release-6.0.1 | ||
| release-6.0.0 | ||
| release-6.0.0rc1 | ||
| release-5.4.3 | ||
| @@ -0,0 +1,21 @@ | ||
| 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 |
| @@ -28,6 +28,26 @@ with advance notice in the **Deprecations** section of releases. | ||
|
|
||
| .. 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) | ||
| ========================= | ||
|
|
||
| @@ -28,7 +28,7 @@ Install ``pytest`` | ||
| .. code-block:: bash | ||
| $ pytest --version | ||
| pytest 6.0.0 | ||
| pytest 6.0.1 | ||
| .. _`simpletest`: | ||
|
|
||
| @@ -192,8 +192,13 @@ You can override the default temporary directory setting like this: | ||
| 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 | ||
| @@ -170,6 +170,8 @@ def showhelp(config: Config) -> None: | ||
| 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) | ||
| @@ -191,9 +193,10 @@ def showhelp(config: Config) -> None: | ||
| 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:") | ||
| @@ -345,6 +345,7 @@ def __init__(self, item: nodes.Node) -> None: | ||
| """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: | ||
| @@ -353,6 +354,8 @@ 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) | ||
| @@ -434,6 +437,7 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non | ||
| # 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 | ||
| @@ -4,7 +4,6 @@ | ||
| 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 | ||
| @@ -473,14 +472,13 @@ def test_function(): | ||
|
|
||
| # 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] == "_": | ||
| @@ -82,9 +82,10 @@ def toterminal(self, out: TerminalWriter) -> None: | ||
| 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: | ||
| @@ -2,6 +2,7 @@ | ||
|
|
||
| import pytest | ||
| from _pytest.logging import caplog_records_key | ||
| from _pytest.pytester import Testdir | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
| sublogger = logging.getLogger(__name__ + ".baz") | ||
| @@ -27,8 +28,11 @@ def test_change_level(caplog): | ||
| 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 | ||
| @@ -50,6 +54,33 @@ def test2(caplog): | ||
| 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") | ||
| @@ -38,6 +38,41 @@ def test_help(testdir): | ||
| ) | ||
|
|
||
|
|
||
| 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( | ||
| """ | ||
| @@ -951,6 +951,33 @@ def test_func(): | ||
| 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( | ||
| """ | ||