From 59e6fb94b5e47bcb8db58c7d704ff389a0b0a8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Stradomski?= Date: Thu, 7 Feb 2019 02:04:06 +0100 Subject: [PATCH 01/22] Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. --- changelog/526.bugfix.rst | 1 + src/_pytest/config/__init__.py | 2 +- testing/test_conftest.py | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 changelog/526.bugfix.rst diff --git a/changelog/526.bugfix.rst b/changelog/526.bugfix.rst new file mode 100644 index 000000000..022183b88 --- /dev/null +++ b/changelog/526.bugfix.rst @@ -0,0 +1 @@ +Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 26999e125..25e1d65bd 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -408,7 +408,7 @@ class PytestPluginManager(PluginManager): continue conftestpath = parent.join("conftest.py") if conftestpath.isfile(): - mod = self._importconftest(conftestpath) + mod = self._importconftest(conftestpath.realpath()) clist.append(mod) self._dirpath2confmods[directory] = clist return clist diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 2b66d8fa7..ac091fed8 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -244,6 +244,42 @@ def test_conftest_symlink(testdir): assert result.ret == EXIT_OK +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_conftest_symlink_files(testdir): + """Check conftest.py loading when running in directory with symlinks.""" + real = testdir.tmpdir.mkdir("real") + source = { + "app/test_foo.py": "def test1(fixture): pass", + "app/__init__.py": "", + "app/conftest.py": textwrap.dedent( + """ + import pytest + + print("conftest_loaded") + + @pytest.fixture + def fixture(): + print("fixture_used") + """ + ), + } + testdir.makepyfile(**{"real/%s" % k: v for k, v in source.items()}) + + # Create a build directory that contains symlinks to actual files + # but doesn't symlink actual directories. + build = testdir.tmpdir.mkdir("build") + build.mkdir("app") + for f in source: + build.join(f).mksymlinkto(real.join(f)) + build.chdir() + result = testdir.runpytest("-vs", "app/test_foo.py") + result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) + assert result.ret == EXIT_OK + + def test_no_conftest(testdir): testdir.makeconftest("assert 0") result = testdir.runpytest("--noconftest") From 391dc549c05a3d262a9f7835c8cfa053f0e4e502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Stradomski?= <44680433+pstradomski@users.noreply.github.com> Date: Thu, 7 Feb 2019 12:56:13 +0100 Subject: [PATCH 02/22] Add comment on why realpath is needed --- src/_pytest/config/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 25e1d65bd..3943f8472 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -408,6 +408,9 @@ class PytestPluginManager(PluginManager): continue conftestpath = parent.join("conftest.py") if conftestpath.isfile(): + # Use realpath to avoid loading the same conftest twice + # with build systems that create build directories containing + # symlinks to actual files. mod = self._importconftest(conftestpath.realpath()) clist.append(mod) self._dirpath2confmods[directory] = clist From ddbea29c128630e7d103e9edbe4a57c57d61a2cf Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Thu, 7 Feb 2019 20:23:45 +0100 Subject: [PATCH 03/22] Remove terminal_reporter workaround from logging.py The workaround was removed from the logging module by creating python properties for verbosity related settings in the terminalreporter. Closes: #4733 --- changelog/4741.trivial.rst | 2 ++ src/_pytest/logging.py | 7 ------- src/_pytest/terminal.py | 27 +++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 changelog/4741.trivial.rst diff --git a/changelog/4741.trivial.rst b/changelog/4741.trivial.rst new file mode 100644 index 000000000..c7903e676 --- /dev/null +++ b/changelog/4741.trivial.rst @@ -0,0 +1,2 @@ +Some verbosity related attributes of the TerminalReporter plugin are now +read only properties. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ba0acd269..343d4dd1c 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -435,13 +435,6 @@ class LoggingPlugin(object): # terminal reporter is disabled e.g. by pytest-xdist. return - # FIXME don't set verbosity level and derived attributes of - # terminalwriter directly - terminal_reporter.verbosity = config.option.verbose - terminal_reporter.showheader = terminal_reporter.verbosity >= 0 - terminal_reporter.showfspath = terminal_reporter.verbosity >= 0 - terminal_reporter.showlongtestinfo = terminal_reporter.verbosity > 0 - capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index eb35577f1..33b76ec9c 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -222,12 +222,9 @@ class TerminalReporter(object): import _pytest.config self.config = config - self.verbosity = self.config.option.verbose - self.showheader = self.verbosity >= 0 - self.showfspath = self.verbosity >= 0 - self.showlongtestinfo = self.verbosity > 0 self._numcollected = 0 self._session = None + self._showfspath = None self.stats = {} self.startdir = py.path.local() @@ -255,6 +252,28 @@ class TerminalReporter(object): return False return self.config.getini("console_output_style") in ("progress", "count") + @property + def verbosity(self): + return self.config.option.verbose + + @property + def showheader(self): + return self.verbosity >= 0 + + @property + def showfspath(self): + if self._showfspath is None: + return self.verbosity >= 0 + return self._showfspath + + @showfspath.setter + def showfspath(self, value): + self._showfspath = value + + @property + def showlongtestinfo(self): + return self.verbosity > 0 + def hasopt(self, char): char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars From 7bb504b8073178711df45335d555c2db60940d34 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 8 Feb 2019 19:13:10 +0100 Subject: [PATCH 04/22] pytest.main: collect: factor out _visit_filter --- src/_pytest/main.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d0d826bb6..920232593 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -550,19 +550,9 @@ class Session(nodes.FSCollector): if argpath.check(dir=1): assert not names, "invalid arg %r" % (arg,) - if six.PY2: - - def filter_(f): - return f.check(file=1) and not f.strpath.endswith("*.pyc") - - else: - - def filter_(f): - return f.check(file=1) - seen_dirs = set() for path in argpath.visit( - fil=filter_, rec=self._recurse, bf=True, sort=True + fil=self._visit_filter, rec=self._recurse, bf=True, sort=True ): dirpath = path.dirpath() if dirpath not in seen_dirs: @@ -636,6 +626,18 @@ class Session(nodes.FSCollector): ihook.pytest_collect_directory(path=dirpath, parent=self) return True + if six.PY2: + + @staticmethod + def _visit_filter(f): + return f.check(file=1) and not f.strpath.endswith("*.pyc") + + else: + + @staticmethod + def _visit_filter(f): + return f.check(file=1) + def _tryconvertpyarg(self, x): """Convert a dotted module name to path.""" try: From 75a12b9d2b6a156f12cfff380ad775766780146c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 8 Feb 2019 21:06:17 -0200 Subject: [PATCH 05/22] Ignore pip-generated 'pip-wheel-metadata' folder [skip ci] --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e2d59502c..a008b4363 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ coverage.xml .project .settings .vscode + +# generated by pip +pip-wheel-metadata/ From 7f6108beb16d761ca123a68ab5e8cc5fefcf5d79 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 8 Feb 2019 13:10:47 -0200 Subject: [PATCH 06/22] Add ref docs for junit_family option --- doc/en/reference.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 92e298a88..6745fb19e 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1015,6 +1015,20 @@ passed multiple times. The expected format is ``name=value``. For example:: This tells pytest to ignore deprecation warnings and turn all other warnings into errors. For more information please refer to :ref:`warnings`. +.. confval:: junit_family + + .. versionadded:: 4.2 + + Configures the format of the generated JUnit XML file. The possible options are: + + * ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. **This is the default**. + * ``xunit2``: produces `xunit 2.0 style output `__, + which should be more compatible with latest Jenkins versions. + + .. code-block:: ini + + [pytest] + junit_family = xunit2 .. confval:: junit_suite_name From fd1684e70b3fc996fcbc8419d1d9e407ee440255 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 10 Feb 2019 13:53:19 +0100 Subject: [PATCH 07/22] tox: use deps for pluggymaster testenvs https://github.com/tox-dev/tox/issues/706 has been fixed. --- tox.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 6c216fd71..66897b9aa 100644 --- a/tox.ini +++ b/tox.ini @@ -116,11 +116,13 @@ commands = {[testenv:py27-numpy]commands} setenv= {[testenv]setenv} _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 - # NOTE: using env instead of "{[testenv]deps}", because of https://github.com/tox-dev/tox/issues/706. - _PYTEST_TOX_EXTRA_DEP=git+https://github.com/pytest-dev/pluggy.git@master +deps = + {[testenv]deps} + git+https://github.com/pytest-dev/pluggy.git@master [testenv:py37-pluggymaster] setenv = {[testenv:py27-pluggymaster]setenv} +deps = {[testenv:py27-pluggymaster]deps} [testenv:docs] basepython = python3 From f73fa47b1fe151ffac268c8e61c4cf8e4dbb0a1b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 10 Feb 2019 13:54:58 +0100 Subject: [PATCH 08/22] Use coverage with pluggymaster --- .travis.yml | 4 ++-- appveyor.yml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6489a1647..66d06fccd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,10 @@ env: # Specialized factors for py27. - TOXENV=py27-nobyte - TOXENV=py27-xdist - - TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1 + - TOXENV=py27-pluggymaster # Specialized factors for py37. - TOXENV=py37-pexpect,py37-trial,py37-numpy - - TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1 + - TOXENV=py37-pluggymaster - TOXENV=py37-freeze PYTEST_NO_COVERAGE=1 matrix: diff --git a/appveyor.yml b/appveyor.yml index 9a50d5bd0..2dea0180f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,11 +13,9 @@ environment: # Specialized factors for py27. - TOXENV: "py27-trial,py27-numpy,py27-nobyte" - TOXENV: "py27-pluggymaster" - PYTEST_NO_COVERAGE: "1" # Specialized factors for py37. - TOXENV: "py37-trial,py37-numpy" - TOXENV: "py37-pluggymaster" - PYTEST_NO_COVERAGE: "1" - TOXENV: "py37-freeze" PYTEST_NO_COVERAGE: "1" From 82b8ec37fc53b6ab823142bb3a8bd0f777dcffe5 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 10 Feb 2019 13:57:44 +0100 Subject: [PATCH 09/22] Bump tox minversion For https://github.com/tox-dev/tox/commit/c611a16afe653e7e96bda5da1949f630cbe37656 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 66897b9aa..60e78c508 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -minversion = 3.3 +minversion = 3.5.3 distshare = {homedir}/.tox/distshare # make sure to update environment list in travis.yml and appveyor.yml envlist = From 5e2d740829f815b50bab3258db42bc66938814ab Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 10 Feb 2019 14:02:56 +0100 Subject: [PATCH 10/22] tox: cleanup/revisit deps --- tox.ini | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tox.ini b/tox.ini index 60e78c508..b6625e7a3 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,8 @@ commands = coverage: coverage report passenv = USER USERNAME COVERAGE_* TRAVIS setenv = - # configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage" + # Configuration to run with coverage similar to Travis/Appveyor, e.g. + # "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 @@ -52,8 +53,8 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:py27-xdist] extras = testing deps = + {[testenv]deps} pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} @@ -61,15 +62,15 @@ commands = # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. extras = testing deps = + {[testenv]deps} pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {[testenv:py27-xdist]commands} [testenv:py27-pexpect] platform = linux|darwin deps = + {[testenv]deps} pexpect - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py} @@ -81,8 +82,8 @@ commands = {[testenv:py27-pexpect]commands} [testenv:py27-nobyte] extras = testing deps = + {[testenv]deps} pytest-xdist>=1.13 - {env:_PYTEST_TOX_EXTRA_DEP:} distribute = true setenv = {[testenv]setenv} @@ -92,8 +93,8 @@ commands = [testenv:py27-trial] deps = + {[testenv]deps} twisted - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py} @@ -103,8 +104,8 @@ commands = {[testenv:py27-trial]commands} [testenv:py27-numpy] deps = + {[testenv]deps} numpy - {env:_PYTEST_TOX_EXTRA_DEP:} commands= {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py} @@ -138,8 +139,8 @@ commands = basepython = python3 skipsdist = True deps = + {[testenv]deps} PyYAML - {env:_PYTEST_TOX_EXTRA_DEP:} commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest From aee67bb1a75f79ccb9bb37617f0044a46c8f410c Mon Sep 17 00:00:00 2001 From: "Kevin J. Foley" Date: Sun, 10 Feb 2019 08:34:35 -0500 Subject: [PATCH 11/22] Clarify pytest_assertrepr_compare docs per #4759 --- doc/en/assert.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 110d00738..186e12853 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -205,8 +205,8 @@ Special comparisons are done for a number of cases: See the :ref:`reporting demo ` for many more examples. -Defining your own assertion comparison ----------------------------------------------- +Defining your own explanation for failed assertions +--------------------------------------------------- It is possible to add your own detailed explanations by implementing the ``pytest_assertrepr_compare`` hook. From 237f690f8b5e976800676cb5c1c6c06fd30755b6 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 10 Feb 2019 14:31:38 +0100 Subject: [PATCH 12/22] --lsof: suppress stderr This can spam a lot of warnings (per invocation), e.g.: > lsof: WARNING: can't stat() nsfs file system /run/docker/netns/default Output information may be incomplete. Or from Travis/MacOS: > lsof: WARNING: can't stat() vmhgfs file system /Volumes/VMware Shared Folders > Output information may be incomplete. > assuming "dev=31000003" from mount table --- src/_pytest/pytester.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 9cadd2f9d..3ac2a9cfd 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -81,7 +81,11 @@ class LsofFdLeakChecker(object): def _exec_lsof(self): pid = os.getpid() - return subprocess.check_output(("lsof", "-Ffn0", "-p", str(pid))).decode() + # py3: use subprocess.DEVNULL directly. + with open(os.devnull, "wb") as devnull: + return subprocess.check_output( + ("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull + ).decode() def _parse_lsof_output(self, out): def isopen(line): From 9feb4941f4e57b718427892eab0510069ec453d5 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 8 Nov 2018 22:17:57 +0100 Subject: [PATCH 13/22] pdb: fix capturing with recursive debugging and pdb++ While I think that pdb++ should be fixed in this regard (by using `pdb.Pdb`, and not `self.__class__` maybe), this ensures that custom debuggers like this are working. --- changelog/4347.bugfix.rst | 1 + src/_pytest/debugging.py | 35 ++++++++++++-------- testing/test_pdb.py | 70 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 changelog/4347.bugfix.rst diff --git a/changelog/4347.bugfix.rst b/changelog/4347.bugfix.rst new file mode 100644 index 000000000..a2e9c6eaf --- /dev/null +++ b/changelog/4347.bugfix.rst @@ -0,0 +1 @@ +Fix output capturing when using pdb++ with recursive debugging. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 7a1060ae0..6b401aa0b 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -75,6 +75,7 @@ class pytestPDB(object): _config = None _pdb_cls = pdb.Pdb _saved = [] + _recursive_debug = 0 @classmethod def _init_pdb(cls, *args, **kwargs): @@ -87,29 +88,37 @@ class pytestPDB(object): capman.suspend_global_capture(in_=True) tw = _pytest.config.create_terminal_writer(cls._config) tw.line() - # Handle header similar to pdb.set_trace in py37+. - header = kwargs.pop("header", None) - if header is not None: - tw.sep(">", header) - elif capman and capman.is_globally_capturing(): - tw.sep(">", "PDB set_trace (IO-capturing turned off)") - else: - tw.sep(">", "PDB set_trace") + if cls._recursive_debug == 0: + # Handle header similar to pdb.set_trace in py37+. + header = kwargs.pop("header", None) + if header is not None: + tw.sep(">", header) + elif capman and capman.is_globally_capturing(): + tw.sep(">", "PDB set_trace (IO-capturing turned off)") + else: + tw.sep(">", "PDB set_trace") class _PdbWrapper(cls._pdb_cls, object): _pytest_capman = capman _continued = False + def do_debug(self, arg): + cls._recursive_debug += 1 + ret = super(_PdbWrapper, self).do_debug(arg) + cls._recursive_debug -= 1 + return ret + def do_continue(self, arg): ret = super(_PdbWrapper, self).do_continue(arg) if self._pytest_capman: tw = _pytest.config.create_terminal_writer(cls._config) tw.line() - if self._pytest_capman.is_globally_capturing(): - tw.sep(">", "PDB continue (IO-capturing resumed)") - else: - tw.sep(">", "PDB continue") - self._pytest_capman.resume_global_capture() + if cls._recursive_debug == 0: + if self._pytest_capman.is_globally_capturing(): + tw.sep(">", "PDB continue (IO-capturing resumed)") + else: + tw.sep(">", "PDB continue") + self._pytest_capman.resume_global_capture() cls._pluginmanager.hook.pytest_leave_pdb( config=cls._config, pdb=self ) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 49fbbad72..43d640614 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -513,6 +513,76 @@ class TestPDB(object): assert "1 failed" in rest self.flush(child) + def test_pdb_interaction_continue_recursive(self, testdir): + p1 = testdir.makepyfile( + mytest=""" + import pdb + import pytest + + count_continue = 0 + + # Simulates pdbpp, which injects Pdb into do_debug, and uses + # self.__class__ in do_continue. + class CustomPdb(pdb.Pdb, object): + def do_debug(self, arg): + import sys + import types + + newglobals = { + 'Pdb': self.__class__, # NOTE: different with pdb.Pdb + 'sys': sys, + } + if sys.version_info < (3, ): + do_debug_func = pdb.Pdb.do_debug.im_func + else: + do_debug_func = pdb.Pdb.do_debug + + orig_do_debug = types.FunctionType( + do_debug_func.__code__, newglobals, + do_debug_func.__name__, do_debug_func.__defaults__, + ) + return orig_do_debug(self, arg) + do_debug.__doc__ = pdb.Pdb.do_debug.__doc__ + + def do_continue(self, *args, **kwargs): + global count_continue + count_continue += 1 + return super(CustomPdb, self).do_continue(*args, **kwargs) + + def foo(): + print("print_from_foo") + + def test_1(): + i = 0 + print("hello17") + pytest.set_trace() + x = 3 + print("hello18") + + assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue + pytest.fail("expected_failure") + """ + ) + child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1)) + child.expect(r"PDB set_trace \(IO-capturing turned off\)") + child.expect(r"\n\(Pdb") + child.sendline("debug foo()") + child.expect("ENTERING RECURSIVE DEBUGGER") + child.expect(r"\n\(\(Pdb") + child.sendline("c") + child.expect("LEAVING RECURSIVE DEBUGGER") + assert b"PDB continue" not in child.before + assert b"print_from_foo" in child.before + child.sendline("c") + child.expect(r"PDB continue \(IO-capturing resumed\)") + rest = child.read().decode("utf8") + assert "hello17" in rest # out is captured + assert "hello18" in rest # out is captured + assert "1 failed" in rest + assert "Failed: expected_failure" in rest + assert "AssertionError: unexpected_failure" not in rest + self.flush(child) + def test_pdb_without_capture(self, testdir): p1 = testdir.makepyfile( """ From 61b9246afe1a6aa76d12a62007e716d831432348 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 8 Feb 2019 18:43:51 +0100 Subject: [PATCH 14/22] Fix/improve handling of pkg init and test file via args Ref: https://github.com/pytest-dev/pytest/issues/4344#issuecomment-441095934 --- changelog/4745.bugfix.rst | 1 + src/_pytest/main.py | 2 +- testing/test_collection.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelog/4745.bugfix.rst diff --git a/changelog/4745.bugfix.rst b/changelog/4745.bugfix.rst new file mode 100644 index 000000000..a7bfad2a7 --- /dev/null +++ b/changelog/4745.bugfix.rst @@ -0,0 +1 @@ +Fix/improve collection of args when passing in ``__init__.py`` and a test file. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index e17385f10..81a597985 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -582,7 +582,7 @@ class Session(nodes.FSCollector): col = self._node_cache[argpath] else: collect_root = self._pkg_roots.get(argpath.dirname, self) - col = collect_root._collectfile(argpath) + col = collect_root._collectfile(argpath, handle_dupes=False) if col: self._node_cache[argpath] = col m = self.matchnodes(col, names) diff --git a/testing/test_collection.py b/testing/test_collection.py index 329182b0f..5fe313f98 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1157,3 +1157,32 @@ def test_collectignore_via_conftest(testdir, monkeypatch): result = testdir.runpytest() assert result.ret == EXIT_NOTESTSCOLLECTED + + +def test_collect_pkg_init_and_file_in_args(testdir): + subdir = testdir.mkdir("sub") + init = subdir.ensure("__init__.py") + init.write("def test_init(): pass") + p = subdir.ensure("test_file.py") + p.write("def test_file(): pass") + + # NOTE: without "-o python_files=*.py" this collects test_file.py twice. + # This changed/broke with "Add package scoped fixtures #2283" (2b1410895) + # initially (causing a RecursionError). + result = testdir.runpytest("-v", str(init), str(p)) + result.stdout.fnmatch_lines( + [ + "sub/test_file.py::test_file PASSED*", + "sub/test_file.py::test_file PASSED*", + "*2 passed in*", + ] + ) + + result = testdir.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) + result.stdout.fnmatch_lines( + [ + "sub/__init__.py::test_init PASSED*", + "sub/test_file.py::test_file PASSED*", + "*2 passed in*", + ] + ) From b4be22833067b226fed035c5336e43c12f89a578 Mon Sep 17 00:00:00 2001 From: Sam Brightman Date: Tue, 12 Feb 2019 11:29:26 +0000 Subject: [PATCH 15/22] Constrain more_itertools for Python 2.7 compatibility Fixes #4772, #4770. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 696fc4028..b286a4f20 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ INSTALL_REQUIRES = [ "six>=1.10.0", "setuptools", "attrs>=17.4.0", - "more-itertools>=4.0.0", + 'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"', + 'more-itertools>=4.0.0;python_version>"2.7"', "atomicwrites>=1.0", 'funcsigs;python_version<"3.0"', 'pathlib2>=2.2.0;python_version<"3.6"', From a8003286b54c3c332103f5b747bbb8d68c5fa5be Mon Sep 17 00:00:00 2001 From: Sam Brightman Date: Tue, 12 Feb 2019 13:32:06 +0000 Subject: [PATCH 16/22] Add CHANGELOG entry for #4770 --- changelog/4770.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4770.bugfix.rst diff --git a/changelog/4770.bugfix.rst b/changelog/4770.bugfix.rst new file mode 100644 index 000000000..8fbb99e1b --- /dev/null +++ b/changelog/4770.bugfix.rst @@ -0,0 +1 @@ +``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. From f9c1329dab085f7729504b83c37a5a32b0450b3c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 12 Feb 2019 06:21:38 -0800 Subject: [PATCH 17/22] Replace flatten() with chain.from_iterable flatten is an alias in more-itertools anyway --- src/_pytest/fixtures.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 9da8f1609..b2ad9aae3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -4,6 +4,7 @@ from __future__ import print_function import functools import inspect +import itertools import sys import warnings from collections import defaultdict @@ -13,7 +14,6 @@ from collections import OrderedDict import attr import py import six -from more_itertools import flatten import _pytest from _pytest import nodes @@ -1109,7 +1109,7 @@ class FixtureManager(object): argnames = getfuncargnames(func, cls=cls) else: argnames = () - usefixtures = flatten( + usefixtures = itertools.chain.from_iterable( mark.args for mark in node.iter_markers(name="usefixtures") ) initialnames = tuple(usefixtures) + argnames From 82cc3d8cc209b05edecbab8ffc8bf7565e862ce7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 20:17:06 +0000 Subject: [PATCH 18/22] Preparing release version 4.2.1 --- CHANGELOG.rst | 56 +++++++++++++++++++++++++++++++ changelog/2895.bugfix.rst | 1 - changelog/3899.bugfix.rst | 1 - changelog/3899.doc.rst | 1 - changelog/4324.doc.rst | 1 - changelog/4347.bugfix.rst | 1 - changelog/4592.bugfix.rst | 1 - changelog/4700.bugfix.rst | 2 -- changelog/4709.doc.rst | 2 -- changelog/4739.bugfix.rst | 1 - changelog/4741.trivial.rst | 2 -- changelog/4745.bugfix.rst | 1 - changelog/4770.bugfix.rst | 1 - changelog/526.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-4.2.1.rst | 30 +++++++++++++++++ 16 files changed, 87 insertions(+), 16 deletions(-) delete mode 100644 changelog/2895.bugfix.rst delete mode 100644 changelog/3899.bugfix.rst delete mode 100644 changelog/3899.doc.rst delete mode 100644 changelog/4324.doc.rst delete mode 100644 changelog/4347.bugfix.rst delete mode 100644 changelog/4592.bugfix.rst delete mode 100644 changelog/4700.bugfix.rst delete mode 100644 changelog/4709.doc.rst delete mode 100644 changelog/4739.bugfix.rst delete mode 100644 changelog/4741.trivial.rst delete mode 100644 changelog/4745.bugfix.rst delete mode 100644 changelog/4770.bugfix.rst delete mode 100644 changelog/526.bugfix.rst create mode 100644 doc/en/announce/release-4.2.1.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ac168dfd3..b1791a6b9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,62 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 4.2.1 (2019-02-12) +========================= + +Bug Fixes +--------- + +- `#2895 `_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. + + +- `#3899 `_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. + + +- `#4347 `_: Fix output capturing when using pdb++ with recursive debugging. + + +- `#4592 `_: Fix handling of ``collect_ignore`` via parent ``conftest.py``. + + +- `#4700 `_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests + were skipped by a ``unittest.skip()`` decorator applied in the subclass. + + +- `#4739 `_: Fix ``parametrize(... ids=)`` when the function returns non-strings. + + +- `#4745 `_: Fix/improve collection of args when passing in ``__init__.py`` and a test file. + + +- `#4770 `_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. + + +- `#526 `_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. + + + +Improved Documentation +---------------------- + +- `#3899 `_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. + + +- `#4324 `_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. + + +- `#4709 `_: Document how to customize test failure messages when using + ``pytest.warns``. + + + +Trivial/Internal Changes +------------------------ + +- `#4741 `_: Some verbosity related attributes of the TerminalReporter plugin are now + read only properties. + + pytest 4.2.0 (2019-01-30) ========================= diff --git a/changelog/2895.bugfix.rst b/changelog/2895.bugfix.rst deleted file mode 100644 index 8e01e193c..000000000 --- a/changelog/2895.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. diff --git a/changelog/3899.bugfix.rst b/changelog/3899.bugfix.rst deleted file mode 100644 index 8f117779e..000000000 --- a/changelog/3899.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. diff --git a/changelog/3899.doc.rst b/changelog/3899.doc.rst deleted file mode 100644 index 675684a01..000000000 --- a/changelog/3899.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. diff --git a/changelog/4324.doc.rst b/changelog/4324.doc.rst deleted file mode 100644 index 5e37a91aa..000000000 --- a/changelog/4324.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. diff --git a/changelog/4347.bugfix.rst b/changelog/4347.bugfix.rst deleted file mode 100644 index a2e9c6eaf..000000000 --- a/changelog/4347.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix output capturing when using pdb++ with recursive debugging. diff --git a/changelog/4592.bugfix.rst b/changelog/4592.bugfix.rst deleted file mode 100644 index f1eaae7eb..000000000 --- a/changelog/4592.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix handling of ``collect_ignore`` via parent ``conftest.py``. diff --git a/changelog/4700.bugfix.rst b/changelog/4700.bugfix.rst deleted file mode 100644 index 3f8acb876..000000000 --- a/changelog/4700.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix regression where ``setUpClass`` would always be called in subclasses even if all tests -were skipped by a ``unittest.skip()`` decorator applied in the subclass. diff --git a/changelog/4709.doc.rst b/changelog/4709.doc.rst deleted file mode 100644 index 5f21728f6..000000000 --- a/changelog/4709.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Document how to customize test failure messages when using -``pytest.warns``. diff --git a/changelog/4739.bugfix.rst b/changelog/4739.bugfix.rst deleted file mode 100644 index dcd44d3fa..000000000 --- a/changelog/4739.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``parametrize(... ids=)`` when the function returns non-strings. diff --git a/changelog/4741.trivial.rst b/changelog/4741.trivial.rst deleted file mode 100644 index c7903e676..000000000 --- a/changelog/4741.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -Some verbosity related attributes of the TerminalReporter plugin are now -read only properties. diff --git a/changelog/4745.bugfix.rst b/changelog/4745.bugfix.rst deleted file mode 100644 index a7bfad2a7..000000000 --- a/changelog/4745.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix/improve collection of args when passing in ``__init__.py`` and a test file. diff --git a/changelog/4770.bugfix.rst b/changelog/4770.bugfix.rst deleted file mode 100644 index 8fbb99e1b..000000000 --- a/changelog/4770.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. diff --git a/changelog/526.bugfix.rst b/changelog/526.bugfix.rst deleted file mode 100644 index 022183b88..000000000 --- a/changelog/526.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 96807c438..62cf5c783 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-4.2.1 release-4.2.0 release-4.1.1 release-4.1.0 diff --git a/doc/en/announce/release-4.2.1.rst b/doc/en/announce/release-4.2.1.rst new file mode 100644 index 000000000..5aec022df --- /dev/null +++ b/doc/en/announce/release-4.2.1.rst @@ -0,0 +1,30 @@ +pytest-4.2.1 +======================================= + +pytest 4.2.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: + +* Anthony Sottile +* Arel Cordero +* Bruno Oliveira +* Daniel Hahler +* Holger Kohr +* Kevin J. Foley +* Nick Murphy +* Paweł Stradomski +* Raphael Pierzina +* Ronny Pfannschmidt +* Sam Brightman +* Thomas Hisch +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team From b5955870318243f9e24ba53fda1f8c0a369d063a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 19:01:30 -0200 Subject: [PATCH 19/22] Set up CI with Azure Pipelines [skip travis] [skip appveyor] Just a few environments for now to see how it will behave for a few days --- azure-pipelines.yml | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..a21cfb140 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,47 @@ +# Python package +# Create and test a Python package on multiple Python versions. +# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/python + +trigger: +- master +- features + +jobs: + +- job: 'Test' + pool: + vmImage: "vs2017-win2016" + strategy: + matrix: + py27: + python.version: '2.7' + tox.env: 'py27' + py35: + python.version: '3.5' + tox.env: 'py35' + py36: + python.version: '3.6' + tox.env: 'py36' + py37: + python.version: '3.7' + tox.env: 'py37' + maxParallel: 4 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: python -m pip install --upgrade pip && pip install tox + displayName: 'Install tox' + + - script: python -m tox -e $(tox.env) -- --junitxml=build/test-results/$(tox.env).xml + displayName: 'Run tests' + + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'build/test-results/$(tox.env).xml' + testRunTitle: '$(tox.env)' + condition: succeededOrFailed() From 215d537624467a875487192208d805f04b62d68a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 20:24:32 -0200 Subject: [PATCH 20/22] Set junitxml and colors using PYTEST_ADDOPTS [skip travis] [skip appveyor] --- azure-pipelines.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a21cfb140..3ded4e731 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,12 +1,10 @@ -# Python package -# Create and test a Python package on multiple Python versions. -# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/python - trigger: - master - features +variables: + PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml --color=yes" + jobs: - job: 'Test' @@ -37,7 +35,7 @@ jobs: - script: python -m pip install --upgrade pip && pip install tox displayName: 'Install tox' - - script: python -m tox -e $(tox.env) -- --junitxml=build/test-results/$(tox.env).xml + - script: python -m tox -e $(tox.env) displayName: 'Run tests' - task: PublishTestResults@2 From 04a941c818a1c5838a14675b61e3c50bcd5e095b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 20:31:29 -0200 Subject: [PATCH 21/22] Pass PYTEST_ADDOPTS to tox envs [skip travis] [skip appveyor] --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b6625e7a3..8041abb07 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {posargs} coverage: coverage combine coverage: coverage report -passenv = USER USERNAME COVERAGE_* TRAVIS +passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS setenv = # Configuration to run with coverage similar to Travis/Appveyor, e.g. # "tox -e py37-coverage". From f729d5d4ee2a8ce8f609cb5b06378aa2ab415f54 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 12 Feb 2019 20:34:51 -0200 Subject: [PATCH 22/22] Remove --color=yes from PYTEST_ADDOPTS [skip travis] [skip appveyor] Does not work on Azure Pipelines at all unfortunately --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3ded4e731..278b9015a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ trigger: - features variables: - PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml --color=yes" + PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml" jobs: