From fc5d4654e5f6ec11eac5f5fcd5e4ba840414925a Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Tue, 5 Feb 2019 17:19:24 +0100 Subject: [PATCH 01/13] Add ability to exclude files matching glob patterns with --ignore-glob This adds the `--ignore-glob` option to allow Unix-style wildcards so that `--ignore-glob=integration*` excludes all tests that reside in files starting with `integration`. Fixes: #3711 --- AUTHORS | 1 + changelog/3711.feature.rst | 1 + doc/en/example/pythoncollection.rst | 3 +++ src/_pytest/main.py | 18 ++++++++++++++++++ testing/test_session.py | 15 +++++++++++++++ 5 files changed, 38 insertions(+) create mode 100644 changelog/3711.feature.rst diff --git a/AUTHORS b/AUTHORS index 35e07dcb4..737c07b1b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -50,6 +50,7 @@ Charles Cloud Charnjit SiNGH (CCSJ) Chris Lamb Christian Boelsen +Christian Fetzer Christian Theunert Christian Tismer Christopher Gilling diff --git a/changelog/3711.feature.rst b/changelog/3711.feature.rst new file mode 100644 index 000000000..ba3322dfb --- /dev/null +++ b/changelog/3711.feature.rst @@ -0,0 +1 @@ +Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 8dcaa97d7..15eb63187 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -41,6 +41,9 @@ you will see that ``pytest`` only collects test-modules, which do not match the ========================= 5 passed in 0.02 seconds ========================= +The ``--ignore-glob`` option allows to ignore test file paths based on Unix shell-style wildcards. +If you want to exclude test-modules that end with ``_01.py``, execute ``pytest`` with ``--ignore-glob='*_01.py'``. + Deselect tests during test collection ------------------------------------- diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d0d826bb6..1d0c48c27 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -4,6 +4,7 @@ from __future__ import division from __future__ import print_function import contextlib +import fnmatch import functools import os import pkgutil @@ -117,6 +118,12 @@ def pytest_addoption(parser): metavar="path", help="ignore path during collection (multi-allowed).", ) + group.addoption( + "--ignore-glob", + action="append", + metavar="path", + help="ignore path pattern during collection (multi-allowed).", + ) group.addoption( "--deselect", action="append", @@ -296,6 +303,17 @@ def pytest_ignore_collect(path, config): if py.path.local(path) in ignore_paths: return True + ignore_globs = [] + excludeglobopt = config.getoption("ignore_glob") + if excludeglobopt: + ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) + + if any( + fnmatch.fnmatch(six.text_type(path), six.text_type(glob)) + for glob in ignore_globs + ): + return True + allow_in_venv = config.getoption("collect_in_virtualenv") if not allow_in_venv and _in_venv(path): return True diff --git a/testing/test_session.py b/testing/test_session.py index d64a1a519..6b185f76b 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -253,6 +253,21 @@ def test_exclude(testdir): result.stdout.fnmatch_lines(["*1 passed*"]) +def test_exclude_glob(testdir): + hellodir = testdir.mkdir("hello") + hellodir.join("test_hello.py").write("x y syntaxerror") + hello2dir = testdir.mkdir("hello2") + hello2dir.join("test_hello2.py").write("x y syntaxerror") + hello3dir = testdir.mkdir("hallo3") + hello3dir.join("test_hello3.py").write("x y syntaxerror") + subdir = testdir.mkdir("sub") + subdir.join("test_hello4.py").write("x y syntaxerror") + testdir.makepyfile(test_ok="def test_pass(): pass") + result = testdir.runpytest("--ignore-glob=*h[ea]llo*") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*1 passed*"]) + + def test_deselect(testdir): testdir.makepyfile( test_a=""" From 2dc2a19db5bd88afbbf8aa5a2d6703bfaf279766 Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Wed, 6 Feb 2019 10:50:46 +0100 Subject: [PATCH 02/13] Add ability to exclude files matching glob patterns in conftest.py This adds the `collect_ignore_glob` option for `conftest.py` to allow Unix-style wildcards for excluding files. --- changelog/3711.feature.rst | 1 + doc/en/example/pythoncollection.rst | 14 ++++++++++++++ src/_pytest/main.py | 5 ++++- testing/test_collection.py | 20 ++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/changelog/3711.feature.rst b/changelog/3711.feature.rst index ba3322dfb..1503bac60 100644 --- a/changelog/3711.feature.rst +++ b/changelog/3711.feature.rst @@ -1 +1,2 @@ Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. +Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 15eb63187..750bc58d8 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -269,3 +269,17 @@ file will be left out: collected 0 items ======================= no tests ran in 0.12 seconds ======================= + +It's also possible to ignore files based on Unix shell-style wildcards by adding +patterns to ``collect_ignore_glob``. + +The following example ``conftest.py`` ignores the file ``setup.py`` and in +addition all files that end with ``*_py2.py`` when executed with a Python 3 +interpreter:: + + # content of conftest.py + import sys + + collect_ignore = ["setup.py"] + if sys.version_info[0] > 2: + collect_ignore_glob = ["*_py2.py"] diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 1d0c48c27..9d4a9f962 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -303,7 +303,10 @@ def pytest_ignore_collect(path, config): if py.path.local(path) in ignore_paths: return True - ignore_globs = [] + ignore_globs = config._getconftest_pathlist( + "collect_ignore_glob", path=path.dirpath() + ) + ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") if excludeglobopt: ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) diff --git a/testing/test_collection.py b/testing/test_collection.py index 36e8a69ce..8ed5caa3a 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -374,6 +374,26 @@ class TestCustomConftests(object): assert result.ret == 0 assert "passed" in result.stdout.str() + def test_collectignoreglob_exclude_on_option(self, testdir): + testdir.makeconftest( + """ + collect_ignore_glob = ['*w*l[dt]*'] + def pytest_addoption(parser): + parser.addoption("--XX", action="store_true", default=False) + def pytest_configure(config): + if config.getvalue("XX"): + collect_ignore_glob[:] = [] + """ + ) + testdir.makepyfile(test_world="def test_hello(): pass") + testdir.makepyfile(test_welt="def test_hallo(): pass") + result = testdir.runpytest() + assert result.ret == EXIT_NOTESTSCOLLECTED + result.stdout.fnmatch_lines("*collected 0 items*") + result = testdir.runpytest("--XX") + assert result.ret == 0 + result.stdout.fnmatch_lines("*2 passed*") + def test_pytest_fs_collect_hooks_are_seen(self, testdir): testdir.makeconftest( """ From 1876a928d3dfc038cf25550385acb19cef69a188 Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Wed, 6 Feb 2019 17:15:28 +0100 Subject: [PATCH 03/13] Document collect_ignore and collect_ignore_glob in reference --- doc/en/reference.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 92e298a88..c1e22f854 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -797,6 +797,33 @@ Special Variables pytest treats some global variables in a special manner when defined in a test module. +collect_ignore +~~~~~~~~~~~~~~ + +**Tutorial**: :ref:`customizing-test-collection` + +Can be declared in *conftest.py files* to exclude test directories or modules. +Needs to be ``list[str]``. + +.. code-block:: python + + collect_ignore = ["setup.py"] + + +collect_ignore_glob +~~~~~~~~~~~~~~~~~~~ + +**Tutorial**: :ref:`customizing-test-collection` + +Can be declared in *conftest.py files* to exclude test directories or modules +with Unix shell-style wildcards. Needs to be ``list[str]`` where ``str`` can +contain glob patterns. + +.. code-block:: python + + collect_ignore_glob = ["*_ignore.py"] + + pytest_plugins ~~~~~~~~~~~~~~ From e276bd3332015977c2534c8dde39b49588aeb4ed Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 5 Feb 2019 19:14:02 -0200 Subject: [PATCH 04/13] pytest.warns emits a warning on unknown keyword arguments --- changelog/4724.deprecation.rst | 3 +++ src/_pytest/deprecated.py | 7 +++++++ src/_pytest/recwarn.py | 13 ++++++++----- testing/deprecated_test.py | 9 +++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 changelog/4724.deprecation.rst diff --git a/changelog/4724.deprecation.rst b/changelog/4724.deprecation.rst new file mode 100644 index 000000000..6dc2fe98c --- /dev/null +++ b/changelog/4724.deprecation.rst @@ -0,0 +1,3 @@ +``pytest.warns()`` now emits a warning when it receives unknown keyword arguments. + +This will be changed into an error in the future. diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 494a453b6..4afde6902 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -14,6 +14,7 @@ from __future__ import print_function from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import RemovedInPytest4Warning +from _pytest.warning_types import UnformattedWarning YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored" @@ -87,3 +88,9 @@ PYTEST_LOGWARNING = PytestDeprecationWarning( "pytest_logwarning is deprecated, no longer being called, and will be removed soon\n" "please use pytest_warning_captured instead" ) + +PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning( + PytestDeprecationWarning, + "pytest.warns() got unexpected keyword arguments: {args!r}.\n" + "This will be an error in future versions.", +) diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index a58c75d3a..3e2ec86de 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -11,6 +11,7 @@ import warnings import six import _pytest._code +from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS from _pytest.deprecated import WARNS_EXEC from _pytest.fixtures import yield_fixture from _pytest.outcomes import fail @@ -84,10 +85,12 @@ def warns(expected_warning, *args, **kwargs): """ __tracebackhide__ = True - match_expr = None if not args: - if "match" in kwargs: - match_expr = kwargs.pop("match") + match_expr = kwargs.pop("match", None) + if kwargs: + warnings.warn( + PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2 + ) return WarningsChecker(expected_warning, match_expr=match_expr) elif isinstance(args[0], str): warnings.warn(WARNS_EXEC, stacklevel=2) @@ -97,12 +100,12 @@ def warns(expected_warning, *args, **kwargs): loc = frame.f_locals.copy() loc.update(kwargs) - with WarningsChecker(expected_warning, match_expr=match_expr): + with WarningsChecker(expected_warning): code = _pytest._code.Source(code).compile() six.exec_(code, frame.f_globals, loc) else: func = args[0] - with WarningsChecker(expected_warning, match_expr=match_expr): + with WarningsChecker(expected_warning): return func(*args[1:], **kwargs) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 92cfcbff8..5db3cceed 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -6,6 +6,7 @@ import os import sys import pytest +from _pytest.warning_types import PytestDeprecationWarning from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG pytestmark = pytest.mark.pytester_example_path("deprecated") @@ -238,3 +239,11 @@ def test_python_deprecation(testdir): ) else: assert msg not in result.stdout.str() + + +def test_pytest_warns_unknown_kwargs(): + with pytest.warns( + PytestDeprecationWarning, + match=r"pytest.warns\(\) got unexpected keyword arguments: \['foo'\]", + ): + pytest.warns(UserWarning, foo="hello") From 9bcbf552d6a3cf7eb18299a1e18b26b25eb6432b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 18 Jan 2019 17:35:49 +0100 Subject: [PATCH 05/13] Add __repr__ for RunResult --- src/_pytest/pytester.py | 6 ++++++ testing/test_pytester.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 9cadd2f9d..26859e660 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -398,6 +398,12 @@ class RunResult(object): self.stderr = LineMatcher(errlines) self.duration = duration + def __repr__(self): + return ( + "" + % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) + ) + def parseoutcomes(self): """Return a dictionary of outcomestring->num from parsing the terminal output that the test process produced. diff --git a/testing/test_pytester.py b/testing/test_pytester.py index d14fbd18e..675108460 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -127,6 +127,17 @@ def test_runresult_assertion_on_xpassed(testdir): assert result.ret == 0 +def test_runresult_repr(): + from _pytest.pytester import RunResult + + assert ( + repr( + RunResult(ret="ret", outlines=[""], errlines=["some", "errors"], duration=1) + ) + == "" + ) + + def test_xpassed_with_strict_is_considered_a_failure(testdir): testdir.makepyfile( """ From 7bee359459142be1c81776ab19aee5c3ee60e215 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 8 Feb 2019 23:53:19 +0100 Subject: [PATCH 06/13] tox: add generic xdist factor --- tox.ini | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/tox.ini b/tox.ini index 6c216fd71..487fe40b1 100644 --- a/tox.ini +++ b/tox.ini @@ -20,18 +20,21 @@ envlist = [testenv] commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {posargs} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {env:_PYTEST_TOX_ARGS:} {posargs} coverage: coverage combine coverage: coverage report passenv = USER USERNAME COVERAGE_* TRAVIS setenv = + _PYTEST_TOX_ARGS=--lsof # configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage" coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess coverage: COVERAGE_FILE={toxinidir}/.coverage coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc + xdist: _PYTEST_TOX_ARGS={env:_PYTEST_TOX_ARGS:-n auto} extras = testing deps = + xdist: pytest-xdist>=1.13 {env:_PYTEST_TOX_EXTRA_DEP:} [testenv:py27-subprocess] @@ -49,22 +52,6 @@ basepython = python3 deps = pre-commit>=1.11.0 commands = pre-commit run --all-files --show-diff-on-failure -[testenv:py27-xdist] -extras = testing -deps = - pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} -commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} - -[testenv:py37-xdist] -# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. -extras = testing -deps = - pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} -commands = {[testenv:py27-xdist]commands} - [testenv:py27-pexpect] platform = linux|darwin deps = From f13935da53241cd53900df758e4853c07e23a9dc Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 17 Jan 2019 22:06:45 +0100 Subject: [PATCH 07/13] Display --help/--version with ArgumentErrors --- changelog/2753.feature.rst | 1 + changelog/4651.bugfix.rst | 1 + src/_pytest/config/__init__.py | 46 +++++++++++++++---- src/_pytest/config/argparsing.py | 17 +++---- src/_pytest/helpconfig.py | 20 +++++---- testing/test_config.py | 76 ++++++++++++++++++++++++++++++-- testing/test_parseopt.py | 7 ++- 7 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 changelog/2753.feature.rst create mode 100644 changelog/4651.bugfix.rst diff --git a/changelog/2753.feature.rst b/changelog/2753.feature.rst new file mode 100644 index 000000000..067e0cad7 --- /dev/null +++ b/changelog/2753.feature.rst @@ -0,0 +1 @@ +Usage errors from argparse are mapped to pytest's ``UsageError``. diff --git a/changelog/4651.bugfix.rst b/changelog/4651.bugfix.rst new file mode 100644 index 000000000..f9c639f3e --- /dev/null +++ b/changelog/4651.bugfix.rst @@ -0,0 +1 @@ +``--help`` and ``--version`` are handled with ``UsageError``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 26999e125..c6ebe8ad8 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -648,8 +648,27 @@ class Config(object): return self.pluginmanager.get_plugin("terminalreporter")._tw def pytest_cmdline_parse(self, pluginmanager, args): - # REF1 assert self == pluginmanager.config, (self, pluginmanager.config) - self.parse(args) + try: + self.parse(args) + except UsageError: + + # Handle --version and --help here in a minimal fashion. + # This gets done via helpconfig normally, but its + # pytest_cmdline_main is not called in case of errors. + if getattr(self.option, "version", False) or "--version" in args: + from _pytest.helpconfig import showversion + + showversion(self) + elif ( + getattr(self.option, "help", False) or "--help" in args or "-h" in args + ): + self._parser._getparser().print_help() + sys.stdout.write( + "\nNOTE: displaying only minimal help due to UsageError.\n\n" + ) + + raise + return self def notify_exception(self, excinfo, option=None): @@ -760,21 +779,32 @@ class Config(object): for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args): + def _validate_args(self, args, via): """Validate known args.""" - self._parser.parse_known_and_unknown_args( - args, namespace=copy.copy(self.option) - ) + self._parser._config_source_hint = via + try: + self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + finally: + del self._parser._config_source_hint + return args def _preparse(self, args, addopts=True): if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") if len(env_addopts): - args[:] = self._validate_args(shlex.split(env_addopts)) + args + args[:] = ( + self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") + + args + ) self._initini(args) if addopts: - args[:] = self._validate_args(self.getini("addopts")) + args + args[:] = ( + self._validate_args(self.getini("addopts"), "via addopts config") + args + ) + self._checkversion() self._consider_importhook(args) self.pluginmanager.consider_preparse(args) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 51f708335..cc48ed337 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,12 +1,10 @@ import argparse -import sys as _sys import warnings -from gettext import gettext as _ import py import six -from ..main import EXIT_USAGEERROR +from _pytest.config.exceptions import UsageError FILE_OR_DIR = "file_or_dir" @@ -337,14 +335,13 @@ class MyOptionParser(argparse.ArgumentParser): self.extra_info = extra_info def error(self, message): - """error(message: string) + """Transform argparse error message into UsageError.""" + msg = "%s: error: %s" % (self.prog, message) - Prints a usage message incorporating the message to stderr and - exits. - Overrides the method in parent class to change exit code""" - self.print_usage(_sys.stderr) - args = {"prog": self.prog, "message": message} - self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args) + if hasattr(self._parser, "_config_source_hint"): + msg = "%s (%s)" % (msg, self._parser._config_source_hint) + + raise UsageError(self.format_usage() + msg) def parse_args(self, args=None, namespace=None): """allow splitting of positional arguments""" diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 5e60d2a7f..d5c4c043a 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -118,16 +118,20 @@ def pytest_cmdline_parse(): config.add_cleanup(unset_tracing) +def showversion(config): + p = py.path.local(pytest.__file__) + sys.stderr.write( + "This is pytest version %s, imported from %s\n" % (pytest.__version__, p) + ) + plugininfo = getpluginversioninfo(config) + if plugininfo: + for line in plugininfo: + sys.stderr.write(line + "\n") + + def pytest_cmdline_main(config): if config.option.version: - p = py.path.local(pytest.__file__) - sys.stderr.write( - "This is pytest version %s, imported from %s\n" % (pytest.__version__, p) - ) - plugininfo = getpluginversioninfo(config) - if plugininfo: - for line in plugininfo: - sys.stderr.write(line + "\n") + showversion(config) return 0 elif config.option.help: config._do_configure() diff --git a/testing/test_config.py b/testing/test_config.py index b0b09f44a..f9f22a63e 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -8,10 +8,12 @@ import textwrap import _pytest._code import pytest from _pytest.config import _iter_rewritable_modules +from _pytest.config.exceptions import UsageError from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import getcfg from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.main import EXIT_USAGEERROR class TestParseIni(object): @@ -1031,9 +1033,12 @@ class TestOverrideIniArgs(object): monkeypatch.setenv("PYTEST_ADDOPTS", "-o") config = get_config() - with pytest.raises(SystemExit) as excinfo: + with pytest.raises(UsageError) as excinfo: config._preparse(["cache_dir=ignored"], addopts=True) - assert excinfo.value.args[0] == _pytest.main.EXIT_USAGEERROR + assert ( + "error: argument -o/--override-ini: expected one argument (via PYTEST_ADDOPTS)" + in excinfo.value.args[0] + ) def test_addopts_from_ini_not_concatenated(self, testdir): """addopts from ini should not take values from normal args (#4265).""" @@ -1046,7 +1051,7 @@ class TestOverrideIniArgs(object): result = testdir.runpytest("cache_dir=ignored") result.stderr.fnmatch_lines( [ - "%s: error: argument -o/--override-ini: expected one argument" + "%s: error: argument -o/--override-ini: expected one argument (via addopts config)" % (testdir.request.config._parser.optparser.prog,) ] ) @@ -1083,3 +1088,68 @@ class TestOverrideIniArgs(object): result = testdir.runpytest("-o", "foo=1", "-o", "bar=0", "test_foo.py") assert "ERROR:" not in result.stderr.str() result.stdout.fnmatch_lines(["collected 1 item", "*= 1 passed in *="]) + + +def test_help_via_addopts(testdir): + testdir.makeini( + """ + [pytest] + addopts = --unknown-option-should-allow-for-help --help + """ + ) + result = testdir.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "usage: *", + "positional arguments:", + # Displays full/default help. + "to see available markers type: pytest --markers", + ] + ) + + +def test_help_and_version_after_argument_error(testdir): + testdir.makeconftest( + """ + def validate(arg): + raise argparse.ArgumentTypeError("argerror") + + def pytest_addoption(parser): + group = parser.getgroup('cov') + group.addoption( + "--invalid-option-should-allow-for-help", + type=validate, + ) + """ + ) + testdir.makeini( + """ + [pytest] + addopts = --invalid-option-should-allow-for-help + """ + ) + result = testdir.runpytest("--help") + result.stdout.fnmatch_lines( + [ + "usage: *", + "positional arguments:", + "NOTE: displaying only minimal help due to UsageError.", + ] + ) + result.stderr.fnmatch_lines( + [ + "ERROR: usage: *", + "%s: error: argument --invalid-option-should-allow-for-help: expected one argument" + % (testdir.request.config._parser.optparser.prog,), + ] + ) + # Does not display full/default help. + assert "to see available markers type: pytest --markers" not in result.stdout.lines + assert result.ret == EXIT_USAGEERROR + + result = testdir.runpytest("--version") + result.stderr.fnmatch_lines( + ["*pytest*{}*imported from*".format(pytest.__version__)] + ) + assert result.ret == EXIT_USAGEERROR diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index baf58a4f5..e25705d00 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -11,6 +11,7 @@ import py import pytest from _pytest.config import argparsing as parseopt +from _pytest.config.exceptions import UsageError @pytest.fixture @@ -19,11 +20,9 @@ def parser(): class TestParser(object): - def test_no_help_by_default(self, capsys): + def test_no_help_by_default(self): parser = parseopt.Parser(usage="xyz") - pytest.raises(SystemExit, lambda: parser.parse(["-h"])) - out, err = capsys.readouterr() - assert err.find("error: unrecognized arguments") != -1 + pytest.raises(UsageError, lambda: parser.parse(["-h"])) def test_custom_prog(self, parser): """Custom prog can be set for `argparse.ArgumentParser`.""" From b41632e9a81fa79977632112e9bd2b1d56cd2ade Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 10:39:25 -0200 Subject: [PATCH 08/13] Revert "Show deprecation message when running under Python 2.7 and 3.4" This reverts commit eb92e575098f9e2e29bb0e69b173f794d9094498. --- src/_pytest/terminal.py | 15 --------------- testing/acceptance_test.py | 4 +--- testing/deprecated_test.py | 19 ------------------- 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index eb35577f1..f4f8b3dd2 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -653,7 +653,6 @@ class TerminalReporter(object): self.summary_passes() # Display any extra warnings from teardown here (if any). self.summary_warnings() - self.summary_deprecated_python() def pytest_keyboard_interrupt(self, excinfo): self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) @@ -775,20 +774,6 @@ class TerminalReporter(object): self.write_sep("_", msg) self._outrep_summary(rep) - def summary_deprecated_python(self): - if sys.version_info[:2] <= (3, 4) and self.verbosity >= 0: - self.write_sep("=", "deprecated python version", yellow=True, bold=False) - using_version = ".".join(str(x) for x in sys.version_info[:3]) - self.line( - "You are using Python {}, which will no longer be supported in pytest 5.0".format( - using_version - ), - yellow=True, - bold=False, - ) - self.line("For more information, please read:") - self.line(" https://docs.pytest.org/en/latest/py27-py34-deprecation.html") - def print_teardown_sections(self, rep): showcapture = self.config.option.showcapture if showcapture == "no": diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 95c419599..59771185f 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -854,9 +854,7 @@ class TestDurations(object): result = testdir.runpytest("--durations=2") assert result.ret == 0 lines = result.stdout.get_lines_after("*slowest*durations*") - # account for the "deprecated python version" header - index = 2 if sys.version_info[:2] > (3, 4) else 6 - assert "4 passed" in lines[index] + assert "4 passed" in lines[2] def test_calls_showall(self, testdir): testdir.makepyfile(self.source) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5db3cceed..536370f92 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -3,7 +3,6 @@ from __future__ import division from __future__ import print_function import os -import sys import pytest from _pytest.warning_types import PytestDeprecationWarning @@ -223,24 +222,6 @@ def test_fixture_named_request(testdir): ) -def test_python_deprecation(testdir): - result = testdir.runpytest() - python_ver = ".".join(str(x) for x in sys.version_info[:3]) - msg = "You are using Python {}, which will no longer be supported in pytest 5.0".format( - python_ver - ) - if sys.version_info[:2] <= (3, 4): - result.stdout.fnmatch_lines( - [ - msg, - "For more information, please read:", - " https://docs.pytest.org/en/latest/py27-py34-deprecation.html", - ] - ) - else: - assert msg not in result.stdout.str() - - def test_pytest_warns_unknown_kwargs(): with pytest.warns( PytestDeprecationWarning, From b759ebdb93380319355b1e2681d6718acf23df66 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 10:39:58 -0200 Subject: [PATCH 09/13] Add CHANGELOG entry for #4698 --- changelog/4698.feature.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/4698.feature.rst diff --git a/changelog/4698.feature.rst b/changelog/4698.feature.rst new file mode 100644 index 000000000..18d4c0216 --- /dev/null +++ b/changelog/4698.feature.rst @@ -0,0 +1,5 @@ +The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed. + +In the end it was considered to be more +of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not +install pytest 5.0 on those interpreters. From 747a8ae3a653440bf0bf752f3d7d7be64da03a4c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 11 Feb 2019 23:40:40 +0100 Subject: [PATCH 10/13] AppVeyor: use xdist for py?? envs, drop py27/py37 --- appveyor.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9a50d5bd0..c17c368a5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,12 +2,10 @@ environment: matrix: - TOXENV: "py37-xdist" - TOXENV: "py27-xdist" - - TOXENV: "py27" - - TOXENV: "py37" - TOXENV: "linting,docs,doctesting" - - TOXENV: "py36" - - TOXENV: "py35" - - TOXENV: "py34" + - TOXENV: "py34-xdist" + - TOXENV: "py35-xdist" + - TOXENV: "py36-xdist" - TOXENV: "pypy" PYTEST_NO_COVERAGE: "1" # Specialized factors for py27. From e3824d23bcf1ee255d0720e393f2bd9a289b0853 Mon Sep 17 00:00:00 2001 From: Andras Mitzki Date: Fri, 8 Feb 2019 22:35:50 +0100 Subject: [PATCH 11/13] LoggingPlugin: Expose setting log_file_handler - This patch allows to set log_file (path) from hook Signed-off-by: Thomas Hisch Signed-off-by: Andras Mitzki --- AUTHORS | 1 + changelog/4707.feature.rst | 1 + doc/en/logging.rst | 3 ++ src/_pytest/logging.py | 52 ++++++++++++++++++++++++------- testing/logging/test_reporting.py | 48 ++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 changelog/4707.feature.rst diff --git a/AUTHORS b/AUTHORS index 6bc084d88..7faa1cd33 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,6 +16,7 @@ Allan Feldman Aly Sivji Anatoly Bubenkoff Anders Hovmöller +Andras Mitzki Andras Tim Andrea Cimatoribus Andreas Zeidler diff --git a/changelog/4707.feature.rst b/changelog/4707.feature.rst new file mode 100644 index 000000000..20c387eb9 --- /dev/null +++ b/changelog/4707.feature.rst @@ -0,0 +1 @@ +With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks. diff --git a/doc/en/logging.rst b/doc/en/logging.rst index 00829c15e..197528d7c 100644 --- a/doc/en/logging.rst +++ b/doc/en/logging.rst @@ -198,6 +198,9 @@ option names are: * ``log_file_format`` * ``log_file_date_format`` +You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality +is considered **experimental**. + .. _log_release_notes: Release notes diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ba0acd269..fd3e21992 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -13,6 +13,7 @@ import six import pytest from _pytest.compat import dummy_context_manager from _pytest.config import create_terminal_writer +from _pytest.pathlib import Path DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" @@ -399,22 +400,21 @@ class LoggingPlugin(object): ) self.log_level = get_actual_log_level(config, "log_level") + self.log_file_level = get_actual_log_level(config, "log_file_level") + self.log_file_format = get_option_ini(config, "log_file_format", "log_format") + self.log_file_date_format = get_option_ini( + config, "log_file_date_format", "log_date_format" + ) + self.log_file_formatter = logging.Formatter( + self.log_file_format, datefmt=self.log_file_date_format + ) + log_file = get_option_ini(config, "log_file") if log_file: - self.log_file_level = get_actual_log_level(config, "log_file_level") - - log_file_format = get_option_ini(config, "log_file_format", "log_format") - log_file_date_format = get_option_ini( - config, "log_file_date_format", "log_date_format" - ) - # Each pytest runtests session will write to a clean logfile self.log_file_handler = logging.FileHandler( log_file, mode="w", encoding="UTF-8" ) - log_file_formatter = logging.Formatter( - log_file_format, datefmt=log_file_date_format - ) - self.log_file_handler.setFormatter(log_file_formatter) + self.log_file_handler.setFormatter(self.log_file_formatter) else: self.log_file_handler = None @@ -468,6 +468,27 @@ class LoggingPlugin(object): log_cli_handler, formatter=log_cli_formatter, level=log_cli_level ) + def set_log_path(self, fname): + """Public method, which can set filename parameter for + Logging.FileHandler(). Also creates parent directory if + it does not exist. + + .. warning:: + Please considered as an experimental API. + """ + fname = Path(fname) + + if not fname.is_absolute(): + fname = Path(self._config.rootdir, fname) + + if not fname.parent.exists(): + fname.parent.mkdir(exist_ok=True, parents=True) + + self.log_file_handler = logging.FileHandler( + str(fname), mode="w", encoding="UTF-8" + ) + self.log_file_handler.setFormatter(self.log_file_formatter) + def _log_cli_enabled(self): """Return True if log_cli should be considered enabled, either explicitly or because --log-cli-level was given in the command-line. @@ -490,6 +511,15 @@ class LoggingPlugin(object): @contextmanager def _runtest_for(self, item, when): + with self._runtest_for_main(item, when): + if self.log_file_handler is not None: + with catching_logs(self.log_file_handler, level=self.log_file_level): + yield + else: + yield + + @contextmanager + def _runtest_for_main(self, item, when): """Implements the internals of pytest_runtest_xxx() hook.""" with catching_logs( LogCaptureHandler(), formatter=self.formatter, level=self.log_level diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 9debc2165..afeccfcc5 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1002,3 +1002,51 @@ def test_log_in_hooks(testdir): assert "sessionstart" in contents assert "runtestloop" in contents assert "sessionfinish" in contents + + +def test_log_set_path(testdir): + report_dir_base = testdir.tmpdir.strpath + + testdir.makeini( + """ + [pytest] + log_file_level = DEBUG + log_cli=true + """ + ) + testdir.makeconftest( + """ + import os + import pytest + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_runtest_setup(item): + config = item.config + logging_plugin = config.pluginmanager.get_plugin("logging-plugin") + report_file = os.path.join({}, item._request.node.name) + logging_plugin.set_log_path(report_file) + yield + """.format( + repr(report_dir_base) + ) + ) + testdir.makepyfile( + """ + import logging + logger = logging.getLogger("testcase-logger") + def test_first(): + logger.info("message from test 1") + assert True + + def test_second(): + logger.debug("message from test 2") + assert True + """ + ) + testdir.runpytest() + with open(os.path.join(report_dir_base, "test_first"), "r") as rfh: + content = rfh.read() + assert "message from test 1" in content + + with open(os.path.join(report_dir_base, "test_second"), "r") as rfh: + content = rfh.read() + assert "message from test 2" in content From 31c869b4c49a999dee8c93eee3c8e2115cef0feb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 16 Feb 2019 14:11:58 +0000 Subject: [PATCH 12/13] Preparing release version 4.3.0 --- CHANGELOG.rst | 42 +++++++++++++++ changelog/2753.feature.rst | 1 - changelog/3711.feature.rst | 2 - changelog/4651.bugfix.rst | 1 - changelog/4698.feature.rst | 5 -- changelog/4707.feature.rst | 1 - changelog/4724.deprecation.rst | 3 -- changelog/4782.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-4.3.0.rst | 36 +++++++++++++ doc/en/cache.rst | 86 +++++++++++++++++++++++++++++-- 11 files changed, 161 insertions(+), 18 deletions(-) delete mode 100644 changelog/2753.feature.rst delete mode 100644 changelog/3711.feature.rst delete mode 100644 changelog/4651.bugfix.rst delete mode 100644 changelog/4698.feature.rst delete mode 100644 changelog/4707.feature.rst delete mode 100644 changelog/4724.deprecation.rst delete mode 100644 changelog/4782.bugfix.rst create mode 100644 doc/en/announce/release-4.3.0.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1791a6b9..9f98f6408 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,48 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 4.3.0 (2019-02-16) +========================= + +Deprecations +------------ + +- `#4724 `_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments. + + This will be changed into an error in the future. + + + +Features +-------- + +- `#2753 `_: Usage errors from argparse are mapped to pytest's ``UsageError``. + + +- `#3711 `_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. + Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards. + + +- `#4698 `_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed. + + In the end it was considered to be more + of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not + install pytest 5.0 on those interpreters. + + +- `#4707 `_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks. + + + +Bug Fixes +--------- + +- `#4651 `_: ``--help`` and ``--version`` are handled with ``UsageError``. + + +- `#4782 `_: Fix ``AssertionError`` with collection of broken symlinks with packages. + + pytest 4.2.1 (2019-02-12) ========================= diff --git a/changelog/2753.feature.rst b/changelog/2753.feature.rst deleted file mode 100644 index 067e0cad7..000000000 --- a/changelog/2753.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Usage errors from argparse are mapped to pytest's ``UsageError``. diff --git a/changelog/3711.feature.rst b/changelog/3711.feature.rst deleted file mode 100644 index 1503bac60..000000000 --- a/changelog/3711.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. -Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards. diff --git a/changelog/4651.bugfix.rst b/changelog/4651.bugfix.rst deleted file mode 100644 index f9c639f3e..000000000 --- a/changelog/4651.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``--help`` and ``--version`` are handled with ``UsageError``. diff --git a/changelog/4698.feature.rst b/changelog/4698.feature.rst deleted file mode 100644 index 18d4c0216..000000000 --- a/changelog/4698.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed. - -In the end it was considered to be more -of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not -install pytest 5.0 on those interpreters. diff --git a/changelog/4707.feature.rst b/changelog/4707.feature.rst deleted file mode 100644 index 20c387eb9..000000000 --- a/changelog/4707.feature.rst +++ /dev/null @@ -1 +0,0 @@ -With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks. diff --git a/changelog/4724.deprecation.rst b/changelog/4724.deprecation.rst deleted file mode 100644 index 6dc2fe98c..000000000 --- a/changelog/4724.deprecation.rst +++ /dev/null @@ -1,3 +0,0 @@ -``pytest.warns()`` now emits a warning when it receives unknown keyword arguments. - -This will be changed into an error in the future. diff --git a/changelog/4782.bugfix.rst b/changelog/4782.bugfix.rst deleted file mode 100644 index 12e08d00c..000000000 --- a/changelog/4782.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``AssertionError`` with collection of broken symlinks with packages. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 62cf5c783..9574229d0 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-4.3.0 release-4.2.1 release-4.2.0 release-4.1.1 diff --git a/doc/en/announce/release-4.3.0.rst b/doc/en/announce/release-4.3.0.rst new file mode 100644 index 000000000..593938148 --- /dev/null +++ b/doc/en/announce/release-4.3.0.rst @@ -0,0 +1,36 @@ +pytest-4.3.0 +======================================= + +The pytest team is proud to announce the 4.3.0 release! + +pytest is a mature Python testing tool with more than a 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/latest/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/latest/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Andras Mitzki +* Anthony Sottile +* Bruno Oliveira +* Christian Fetzer +* Daniel Hahler +* Grygorii Iermolenko +* R. Alex Matevish +* Ronny Pfannschmidt +* cclauss + + +Happy testing, +The Pytest Development Team diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 0cb42e6e9..c5a81b13f 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -220,8 +220,6 @@ If you run this command for the first time, you can see the print statement: E +23 test_caching.py:17: AssertionError - -------------------------- Captured stdout setup --------------------------- - running expensive computation... 1 failed in 0.12 seconds If you run it a second time the value will be retrieved from @@ -264,12 +262,92 @@ You can always peek at the content of the cache using the cachedir: $PYTHON_PREFIX/.pytest_cache ------------------------------- cache values ------------------------------- cache/lastfailed contains: - {'test_50.py::test_num[17]': True, + {'a/test_db.py::test_a1': True, + 'a/test_db2.py::test_a2': True, + 'b/test_error.py::test_root': True, + 'failure_demo.py::TestCustomAssertMsg::test_custom_repr': True, + 'failure_demo.py::TestCustomAssertMsg::test_multiline': True, + 'failure_demo.py::TestCustomAssertMsg::test_single_line': True, + 'failure_demo.py::TestFailing::test_not': True, + 'failure_demo.py::TestFailing::test_simple': True, + 'failure_demo.py::TestFailing::test_simple_multiline': True, + 'failure_demo.py::TestMoreErrors::test_compare': True, + 'failure_demo.py::TestMoreErrors::test_complex_error': True, + 'failure_demo.py::TestMoreErrors::test_global_func': True, + 'failure_demo.py::TestMoreErrors::test_instance': True, + 'failure_demo.py::TestMoreErrors::test_startswith': True, + 'failure_demo.py::TestMoreErrors::test_startswith_nested': True, + 'failure_demo.py::TestMoreErrors::test_try_finally': True, + 'failure_demo.py::TestMoreErrors::test_z1_unpack_error': True, + 'failure_demo.py::TestMoreErrors::test_z2_type_error': True, + 'failure_demo.py::TestRaises::test_raise': True, + 'failure_demo.py::TestRaises::test_raises': True, + 'failure_demo.py::TestRaises::test_raises_doesnt': True, + 'failure_demo.py::TestRaises::test_reinterpret_fails_with_print_for_the_fun_of_it': True, + 'failure_demo.py::TestRaises::test_some_error': True, + 'failure_demo.py::TestRaises::test_tupleerror': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_attrs': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_dataclass': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_dict': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_list': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_list_long': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text_multiline': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_multiline_text': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_set': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_similar_text': True, + 'failure_demo.py::TestSpecialisedExplanations::test_eq_text': True, + 'failure_demo.py::TestSpecialisedExplanations::test_in_list': True, + 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline': True, + 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single': True, + 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long': True, + 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long_term': True, + 'failure_demo.py::test_attribute': True, + 'failure_demo.py::test_attribute_failure': True, + 'failure_demo.py::test_attribute_instance': True, + 'failure_demo.py::test_attribute_multiple': True, + 'failure_demo.py::test_dynamic_compile_shows_nicely': True, + 'failure_demo.py::test_generative[3-6]': True, + 'test_50.py::test_num[17]': True, 'test_50.py::test_num[25]': True, + 'test_anothersmtp.py::test_showhelo': True, 'test_assert1.py::test_function': True, 'test_assert2.py::test_set_comparison': True, + 'test_backends.py::test_db_initialized[d2]': True, 'test_caching.py::test_function': True, - 'test_foocompare.py::test_compare': True} + 'test_checkconfig.py::test_something': True, + 'test_class.py::TestClass::test_two': True, + 'test_compute.py::test_compute[4]': True, + 'test_example.py::test_error': True, + 'test_example.py::test_fail': True, + 'test_foocompare.py::test_compare': True, + 'test_module.py::test_call_fails': True, + 'test_module.py::test_ehlo': True, + 'test_module.py::test_ehlo[mail.python.org]': True, + 'test_module.py::test_ehlo[smtp.gmail.com]': True, + 'test_module.py::test_event_simple': True, + 'test_module.py::test_fail1': True, + 'test_module.py::test_fail2': True, + 'test_module.py::test_func2': True, + 'test_module.py::test_interface_complex': True, + 'test_module.py::test_interface_simple': True, + 'test_module.py::test_noop': True, + 'test_module.py::test_noop[mail.python.org]': True, + 'test_module.py::test_noop[smtp.gmail.com]': True, + 'test_module.py::test_setup_fails': True, + 'test_parametrize.py::TestClass::test_equals[1-2]': True, + 'test_sample.py::test_answer': True, + 'test_show_warnings.py::test_one': True, + 'test_simple.yml::hello': True, + 'test_smtpsimple.py::test_ehlo': True, + 'test_step.py::TestUserHandling::test_modification': True, + 'test_strings.py::test_valid_string[!]': True, + 'test_tmp_path.py::test_create_file': True, + 'test_tmpdir.py::test_create_file': True, + 'test_tmpdir.py::test_needsfiles': True, + 'test_unittest_db.py::MyTest::test_method1': True, + 'test_unittest_db.py::MyTest::test_method2': True} cache/nodeids contains: ['test_caching.py::test_function'] cache/stepwise contains: From ff015f630851927a39899b35b175a39945dd6c74 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 18 Feb 2019 18:46:03 +0100 Subject: [PATCH 13/13] Fix docs (tox -e regen, plus pre-commit) --- doc/en/cache.rst | 86 ++-------------------------------- doc/en/example/parametrize.rst | 6 +-- 2 files changed, 6 insertions(+), 86 deletions(-) diff --git a/doc/en/cache.rst b/doc/en/cache.rst index c5a81b13f..0cb42e6e9 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -220,6 +220,8 @@ If you run this command for the first time, you can see the print statement: E +23 test_caching.py:17: AssertionError + -------------------------- Captured stdout setup --------------------------- + running expensive computation... 1 failed in 0.12 seconds If you run it a second time the value will be retrieved from @@ -262,92 +264,12 @@ You can always peek at the content of the cache using the cachedir: $PYTHON_PREFIX/.pytest_cache ------------------------------- cache values ------------------------------- cache/lastfailed contains: - {'a/test_db.py::test_a1': True, - 'a/test_db2.py::test_a2': True, - 'b/test_error.py::test_root': True, - 'failure_demo.py::TestCustomAssertMsg::test_custom_repr': True, - 'failure_demo.py::TestCustomAssertMsg::test_multiline': True, - 'failure_demo.py::TestCustomAssertMsg::test_single_line': True, - 'failure_demo.py::TestFailing::test_not': True, - 'failure_demo.py::TestFailing::test_simple': True, - 'failure_demo.py::TestFailing::test_simple_multiline': True, - 'failure_demo.py::TestMoreErrors::test_compare': True, - 'failure_demo.py::TestMoreErrors::test_complex_error': True, - 'failure_demo.py::TestMoreErrors::test_global_func': True, - 'failure_demo.py::TestMoreErrors::test_instance': True, - 'failure_demo.py::TestMoreErrors::test_startswith': True, - 'failure_demo.py::TestMoreErrors::test_startswith_nested': True, - 'failure_demo.py::TestMoreErrors::test_try_finally': True, - 'failure_demo.py::TestMoreErrors::test_z1_unpack_error': True, - 'failure_demo.py::TestMoreErrors::test_z2_type_error': True, - 'failure_demo.py::TestRaises::test_raise': True, - 'failure_demo.py::TestRaises::test_raises': True, - 'failure_demo.py::TestRaises::test_raises_doesnt': True, - 'failure_demo.py::TestRaises::test_reinterpret_fails_with_print_for_the_fun_of_it': True, - 'failure_demo.py::TestRaises::test_some_error': True, - 'failure_demo.py::TestRaises::test_tupleerror': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_attrs': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_dataclass': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_dict': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_list': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_list_long': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text_multiline': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_multiline_text': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_set': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_similar_text': True, - 'failure_demo.py::TestSpecialisedExplanations::test_eq_text': True, - 'failure_demo.py::TestSpecialisedExplanations::test_in_list': True, - 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline': True, - 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single': True, - 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long': True, - 'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long_term': True, - 'failure_demo.py::test_attribute': True, - 'failure_demo.py::test_attribute_failure': True, - 'failure_demo.py::test_attribute_instance': True, - 'failure_demo.py::test_attribute_multiple': True, - 'failure_demo.py::test_dynamic_compile_shows_nicely': True, - 'failure_demo.py::test_generative[3-6]': True, - 'test_50.py::test_num[17]': True, + {'test_50.py::test_num[17]': True, 'test_50.py::test_num[25]': True, - 'test_anothersmtp.py::test_showhelo': True, 'test_assert1.py::test_function': True, 'test_assert2.py::test_set_comparison': True, - 'test_backends.py::test_db_initialized[d2]': True, 'test_caching.py::test_function': True, - 'test_checkconfig.py::test_something': True, - 'test_class.py::TestClass::test_two': True, - 'test_compute.py::test_compute[4]': True, - 'test_example.py::test_error': True, - 'test_example.py::test_fail': True, - 'test_foocompare.py::test_compare': True, - 'test_module.py::test_call_fails': True, - 'test_module.py::test_ehlo': True, - 'test_module.py::test_ehlo[mail.python.org]': True, - 'test_module.py::test_ehlo[smtp.gmail.com]': True, - 'test_module.py::test_event_simple': True, - 'test_module.py::test_fail1': True, - 'test_module.py::test_fail2': True, - 'test_module.py::test_func2': True, - 'test_module.py::test_interface_complex': True, - 'test_module.py::test_interface_simple': True, - 'test_module.py::test_noop': True, - 'test_module.py::test_noop[mail.python.org]': True, - 'test_module.py::test_noop[smtp.gmail.com]': True, - 'test_module.py::test_setup_fails': True, - 'test_parametrize.py::TestClass::test_equals[1-2]': True, - 'test_sample.py::test_answer': True, - 'test_show_warnings.py::test_one': True, - 'test_simple.yml::hello': True, - 'test_smtpsimple.py::test_ehlo': True, - 'test_step.py::TestUserHandling::test_modification': True, - 'test_strings.py::test_valid_string[!]': True, - 'test_tmp_path.py::test_create_file': True, - 'test_tmpdir.py::test_create_file': True, - 'test_tmpdir.py::test_needsfiles': True, - 'test_unittest_db.py::MyTest::test_method1': True, - 'test_unittest_db.py::MyTest::test_method2': True} + 'test_foocompare.py::test_compare': True} cache/nodeids contains: ['test_caching.py::test_function'] cache/stepwise contains: diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index b5d4693ad..05932d164 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -436,10 +436,8 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - ...sss...sssssssss...sss... [100%] - ========================= short test summary info ========================== - SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found - 12 passed, 15 skipped in 0.12 seconds + ........................... [100%] + 27 passed in 0.12 seconds Indirect parametrization of optional implementations/imports --------------------------------------------------------------------