From af6548a4e7a78d7a5a2f1b5fb3a3e056b14ba593 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 23 May 2020 12:09:42 -0700 Subject: [PATCH 1/7] Merge pull request #7244 from DahlitzFlorian/fix-issue-7150 Prevent hiding underlying exception when ConfTestImportFailure is raised (cherry picked from commit 45f53266e6fa1539b702df315847cfbce3e3f19e) --- changelog/7150.bugfix.rst | 1 + src/_pytest/debugging.py | 5 +++++ testing/test_debugging.py | 9 +++++++++ 3 files changed, 15 insertions(+) create mode 100644 changelog/7150.bugfix.rst diff --git a/changelog/7150.bugfix.rst b/changelog/7150.bugfix.rst new file mode 100644 index 00000000000..42cf5c7d2b9 --- /dev/null +++ b/changelog/7150.bugfix.rst @@ -0,0 +1 @@ +Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 17915db73fc..26c3095dccd 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -4,6 +4,7 @@ import sys from _pytest import outcomes +from _pytest.config import ConftestImportFailure from _pytest.config import hookimpl from _pytest.config.exceptions import UsageError @@ -338,6 +339,10 @@ def _postmortem_traceback(excinfo): # A doctest.UnexpectedException is not useful for post_mortem. # Use the underlying exception instead: return excinfo.value.exc_info[2] + elif isinstance(excinfo.value, ConftestImportFailure): + # A config.ConftestImportFailure is not useful for post_mortem. + # Use the underlying exception instead: + return excinfo.value.excinfo[2] else: return excinfo._excinfo[2] diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 719d6477bff..00af4a088a7 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -342,6 +342,15 @@ def pytest_runtest_protocol(): child.sendeof() self.flush(child) + def test_pdb_prevent_ConftestImportFailure_hiding_exception(self, testdir): + testdir.makepyfile("def test_func(): pass") + sub_dir = testdir.tmpdir.join("ns").ensure_dir() + sub_dir.join("conftest").new(ext=".py").write("import unknown") + sub_dir.join("test_file").new(ext=".py").write("def test_func(): pass") + + result = testdir.runpytest_subprocess("--pdb", ".") + result.stdout.fnmatch_lines(["-> import unknown"]) + def test_pdb_interaction_capturing_simple(self, testdir): p1 = testdir.makepyfile( """ From 21ca38b932acc703c4fdd31364ab37ebc07a8cb1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 27 May 2020 13:49:43 -0700 Subject: [PATCH 2/7] Merge pull request #7257 from DahlitzFlorian/fix-issue-6956 Prevent pytest from printing ConftestImportFailure traceback (cherry picked from commit b3db440d4c41c91dd761dee98d7014149e1d7c08) --- changelog/6956.bugfix.rst | 1 + src/_pytest/nodes.py | 6 ++++-- testing/python/collect.py | 9 +++------ testing/test_reports.py | 8 ++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 changelog/6956.bugfix.rst diff --git a/changelog/6956.bugfix.rst b/changelog/6956.bugfix.rst new file mode 100644 index 00000000000..a88ef94b6d5 --- /dev/null +++ b/changelog/6956.bugfix.rst @@ -0,0 +1 @@ +Prevent pytest from printing ConftestImportFailure traceback to stdout. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 7e6720d6026..7edea1e976c 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -19,6 +19,7 @@ from _pytest.compat import cached_property from _pytest.compat import TYPE_CHECKING from _pytest.config import Config +from _pytest.config import ConftestImportFailure from _pytest.config import PytestPluginManager from _pytest.deprecated import NODE_USE_FROM_PARENT from _pytest.fixtures import FixtureDef @@ -28,7 +29,6 @@ from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords from _pytest.outcomes import fail -from _pytest.outcomes import Failed from _pytest.store import Store if TYPE_CHECKING: @@ -318,8 +318,10 @@ def _prunetraceback(self, excinfo): pass def _repr_failure_py( - self, excinfo: ExceptionInfo[Union[Failed, FixtureLookupError]], style=None + self, excinfo: ExceptionInfo[BaseException], style=None, ) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]: + if isinstance(excinfo.value, ConftestImportFailure): + excinfo = ExceptionInfo(excinfo.value.excinfo) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: return str(excinfo.value) diff --git a/testing/python/collect.py b/testing/python/collect.py index 496a22b0504..f493477c130 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1225,7 +1225,7 @@ def test_syntax_error_with_non_ascii_chars(testdir): result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"]) -def test_collecterror_with_fulltrace(testdir): +def test_collect_error_with_fulltrace(testdir): testdir.makepyfile("assert 0") result = testdir.runpytest("--fulltrace") result.stdout.fnmatch_lines( @@ -1233,15 +1233,12 @@ def test_collecterror_with_fulltrace(testdir): "collected 0 items / 1 error", "", "*= ERRORS =*", - "*_ ERROR collecting test_collecterror_with_fulltrace.py _*", - "", - "*/_pytest/python.py:*: ", - "_ _ _ _ _ _ _ _ *", + "*_ ERROR collecting test_collect_error_with_fulltrace.py _*", "", "> assert 0", "E assert 0", "", - "test_collecterror_with_fulltrace.py:1: AssertionError", + "test_collect_error_with_fulltrace.py:1: AssertionError", "*! Interrupted: 1 error during collection !*", ] ) diff --git a/testing/test_reports.py b/testing/test_reports.py index 8c509ec479d..ef87af05883 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -395,6 +395,14 @@ def check_longrepr(longrepr): # for same reasons as previous test, ensure we don't blow up here loaded_report.longrepr.toterminal(tw_mock) + def test_report_prevent_ConftestImportFailure_hiding_exception(self, testdir): + sub_dir = testdir.tmpdir.join("ns").ensure_dir() + sub_dir.join("conftest").new(ext=".py").write("import unknown") + + result = testdir.runpytest_subprocess(".") + result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) + result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") + class TestHooks: """Test that the hooks are working correctly for plugins""" From 551400e8d6f20bddde19a29be3545bb8109a4f13 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 30 May 2020 14:33:22 -0300 Subject: [PATCH 3/7] Do not call TestCase.tearDown for skipped tests (#7236) Fix #7215 --- changelog/7215.bugfix.rst | 2 ++ src/_pytest/unittest.py | 11 ++++++++--- testing/test_unittest.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 changelog/7215.bugfix.rst diff --git a/changelog/7215.bugfix.rst b/changelog/7215.bugfix.rst new file mode 100644 index 00000000000..81514913285 --- /dev/null +++ b/changelog/7215.bugfix.rst @@ -0,0 +1,2 @@ +Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase`` +subclasses for skipped tests. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index fc3d1a51533..36158c62d2f 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -41,7 +41,7 @@ def collect(self): if not getattr(cls, "__test__", True): return - skipped = getattr(cls, "__unittest_skip__", False) + skipped = _is_skipped(cls) if not skipped: self._inject_setup_teardown_fixtures(cls) self._inject_setup_class_fixture() @@ -89,7 +89,7 @@ def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self): @pytest.fixture(scope=scope, autouse=True) def fixture(self, request): - if getattr(self, "__unittest_skip__", None): + if _is_skipped(self): reason = self.__unittest_skip_why__ pytest.skip(reason) if setup is not None: @@ -220,7 +220,7 @@ def runtest(self): # arguably we could always postpone tearDown(), but this changes the moment where the # TestCase instance interacts with the results object, so better to only do it # when absolutely needed - if self.config.getoption("usepdb"): + if self.config.getoption("usepdb") and not _is_skipped(self.obj): self._explicit_tearDown = self._testcase.tearDown setattr(self._testcase, "tearDown", lambda *args: None) @@ -301,3 +301,8 @@ def check_testcase_implements_trial_reporter(done=[]): classImplements(TestCaseFunction, IReporter) done.append(1) + + +def _is_skipped(obj) -> bool: + """Return True if the given object has been marked with @unittest.skip""" + return bool(getattr(obj, "__unittest_skip__", False)) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 83f1b6b2a85..74a36c41bc0 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1193,6 +1193,40 @@ def test_2(self): ] +@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) +def test_pdb_teardown_skipped(testdir, monkeypatch, mark): + """ + With --pdb, setUp and tearDown should not be called for skipped tests. + """ + tracked = [] + monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) + + testdir.makepyfile( + """ + import unittest + import pytest + + class MyTestCase(unittest.TestCase): + + def setUp(self): + pytest.test_pdb_teardown_skipped.append("setUp:" + self.id()) + + def tearDown(self): + pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id()) + + {mark}("skipped for reasons") + def test_1(self): + pass + + """.format( + mark=mark + ) + ) + result = testdir.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 1 skipped in *") + assert tracked == [] + + def test_async_support(testdir): pytest.importorskip("unittest.async_case") From e1a21e46b00959b8831b195c524bbf504e458bec Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 30 May 2020 20:14:26 -0300 Subject: [PATCH 4/7] Merge pull request #7220 from nicoddemus/issue-6428 --- changelog/6428.bugfix.rst | 2 ++ src/_pytest/nodes.py | 10 ++++++++-- testing/test_nodes.py | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 changelog/6428.bugfix.rst diff --git a/changelog/6428.bugfix.rst b/changelog/6428.bugfix.rst new file mode 100644 index 00000000000..581b2b7cece --- /dev/null +++ b/changelog/6428.bugfix.rst @@ -0,0 +1,2 @@ +Paths appearing in error messages are now correct in case the current working directory has +changed since the start of the session. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 7edea1e976c..6f22a8daaa0 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -29,6 +29,7 @@ from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords from _pytest.outcomes import fail +from _pytest.pathlib import Path from _pytest.store import Store if TYPE_CHECKING: @@ -348,9 +349,14 @@ def _repr_failure_py( else: truncate_locals = True + # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. + # It is possible for a fixture/test to change the CWD while this code runs, which + # would then result in the user seeing confusing paths in the failure message. + # To fix this, if the CWD changed, always display the full absolute path. + # It will be better to just always display paths relative to invocation_dir, but + # this requires a lot of plumbing (#6428). try: - os.getcwd() - abspath = False + abspath = Path(os.getcwd()) != Path(self.config.invocation_dir) except OSError: abspath = True diff --git a/testing/test_nodes.py b/testing/test_nodes.py index dbb3e2e8f64..5bd31b34261 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -58,3 +58,30 @@ class FakeSession: outside = py.path.local("/outside") assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None + + +def test_failure_with_changed_cwd(testdir): + """ + Test failure lines should use absolute paths if cwd has changed since + invocation, so the path is correct (#6428). + """ + p = testdir.makepyfile( + """ + import os + import pytest + + @pytest.fixture + def private_dir(): + out_dir = 'ddd' + os.mkdir(out_dir) + old_dir = os.getcwd() + os.chdir(out_dir) + yield out_dir + os.chdir(old_dir) + + def test_show_wrong_path(private_dir): + assert False + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines([str(p) + ":*: AssertionError", "*1 failed in *"]) From 56e64824053abea9299b8ecd306ef02fe13a31e9 Mon Sep 17 00:00:00 2001 From: Tor Colvin Date: Tue, 2 Jun 2020 07:56:33 -0400 Subject: [PATCH 5/7] Fix removal of very long paths on Windows (#6755) Co-authored-by: Bruno Oliveira --- AUTHORS | 1 + changelog/6755.bugfix.rst | 1 + src/_pytest/pathlib.py | 32 ++++++++++++++++++++++++++++++++ testing/test_pathlib.py | 24 ++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 changelog/6755.bugfix.rst diff --git a/AUTHORS b/AUTHORS index af0dc62c4d8..3de12aa12f5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -267,6 +267,7 @@ Tom Dalton Tom Viner Tomáš Gavenčiak Tomer Keren +Tor Colvin Trevor Bekolay Tyler Goodlet Tzu-ping Chung diff --git a/changelog/6755.bugfix.rst b/changelog/6755.bugfix.rst new file mode 100644 index 00000000000..8077baa4f55 --- /dev/null +++ b/changelog/6755.bugfix.rst @@ -0,0 +1 @@ +Support deleting paths longer than 260 characters on windows created inside tmpdir. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 8d25b21dd7d..2f04b02d7a7 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -100,10 +100,41 @@ def chmod_rw(p: str) -> None: return True +def ensure_extended_length_path(path: Path) -> Path: + """Get the extended-length version of a path (Windows). + + On Windows, by default, the maximum length of a path (MAX_PATH) is 260 + characters, and operations on paths longer than that fail. But it is possible + to overcome this by converting the path to "extended-length" form before + performing the operation: + https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + + On Windows, this function returns the extended-length absolute version of path. + On other platforms it returns path unchanged. + """ + if sys.platform.startswith("win32"): + path = path.resolve() + path = Path(get_extended_length_path_str(str(path))) + return path + + +def get_extended_length_path_str(path: str) -> str: + """Converts to extended length path as a str""" + long_path_prefix = "\\\\?\\" + unc_long_path_prefix = "\\\\?\\UNC\\" + if path.startswith((long_path_prefix, unc_long_path_prefix)): + return path + # UNC + if path.startswith("\\\\"): + return unc_long_path_prefix + path[2:] + return long_path_prefix + path + + def rm_rf(path: Path) -> None: """Remove the path contents recursively, even if some elements are read-only. """ + path = ensure_extended_length_path(path) onerror = partial(on_rm_rf_error, start_path=path) shutil.rmtree(str(path), onerror=onerror) @@ -220,6 +251,7 @@ def cleanup_on_exit(lock_path: Path = lock_path, original_pid: int = pid) -> Non def maybe_delete_a_numbered_dir(path: Path) -> None: """removes a numbered directory if its lock can be obtained and it does not seem to be in use""" + path = ensure_extended_length_path(path) lock_path = None try: lock_path = create_cleanup_lock(path) diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 45daeaed76a..03bed26ec3a 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -5,6 +5,7 @@ import pytest from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import get_extended_length_path_str from _pytest.pathlib import get_lock_path from _pytest.pathlib import maybe_delete_a_numbered_dir from _pytest.pathlib import Path @@ -89,3 +90,26 @@ def renamed_failed(*args): lock_path = get_lock_path(path) maybe_delete_a_numbered_dir(path) assert not lock_path.is_file() + + +def test_long_path_during_cleanup(tmp_path): + """Ensure that deleting long path works (particularly on Windows (#6775)).""" + path = (tmp_path / ("a" * 250)).resolve() + if sys.platform == "win32": + # make sure that the full path is > 260 characters without any + # component being over 260 characters + assert len(str(path)) > 260 + extended_path = "\\\\?\\" + str(path) + else: + extended_path = str(path) + os.mkdir(extended_path) + assert os.path.isdir(extended_path) + maybe_delete_a_numbered_dir(path) + assert not os.path.isdir(extended_path) + + +def test_get_extended_length_path_str(): + assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo" + assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo" + assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo" + assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo" From 703d0f50d8bc19e3d52313a28d349a2240c97184 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 2 Jun 2020 09:02:05 -0300 Subject: [PATCH 6/7] Merge pull request #7294 from nicoddemus/codecov-adjustments --- codecov.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index db2472009c6..f1cc8697338 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1 +1,6 @@ -comment: off +# reference: https://docs.codecov.io/docs/codecovyml-reference +coverage: + status: + patch: true + project: false +comment: false From b322004047774f4b0992bfc8b9b1123069a6d0f1 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Tue, 2 Jun 2020 15:22:35 +0000 Subject: [PATCH 7/7] Preparing release version 5.4.3 --- changelog/6428.bugfix.rst | 2 -- changelog/6755.bugfix.rst | 1 - changelog/6956.bugfix.rst | 1 - changelog/7150.bugfix.rst | 1 - changelog/7215.bugfix.rst | 2 -- doc/en/announce/index.rst | 1 + doc/en/announce/release-5.4.3.rst | 21 +++++++++++++++++++++ doc/en/changelog.rst | 23 +++++++++++++++++++++++ doc/en/example/parametrize.rst | 7 +++---- 9 files changed, 48 insertions(+), 11 deletions(-) delete mode 100644 changelog/6428.bugfix.rst delete mode 100644 changelog/6755.bugfix.rst delete mode 100644 changelog/6956.bugfix.rst delete mode 100644 changelog/7150.bugfix.rst delete mode 100644 changelog/7215.bugfix.rst create mode 100644 doc/en/announce/release-5.4.3.rst diff --git a/changelog/6428.bugfix.rst b/changelog/6428.bugfix.rst deleted file mode 100644 index 581b2b7cece..00000000000 --- a/changelog/6428.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Paths appearing in error messages are now correct in case the current working directory has -changed since the start of the session. diff --git a/changelog/6755.bugfix.rst b/changelog/6755.bugfix.rst deleted file mode 100644 index 8077baa4f55..00000000000 --- a/changelog/6755.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Support deleting paths longer than 260 characters on windows created inside tmpdir. diff --git a/changelog/6956.bugfix.rst b/changelog/6956.bugfix.rst deleted file mode 100644 index a88ef94b6d5..00000000000 --- a/changelog/6956.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Prevent pytest from printing ConftestImportFailure traceback to stdout. diff --git a/changelog/7150.bugfix.rst b/changelog/7150.bugfix.rst deleted file mode 100644 index 42cf5c7d2b9..00000000000 --- a/changelog/7150.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised. diff --git a/changelog/7215.bugfix.rst b/changelog/7215.bugfix.rst deleted file mode 100644 index 81514913285..00000000000 --- a/changelog/7215.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase`` -subclasses for skipped tests. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index eeea782743d..4405e6fe04b 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-5.4.3 release-5.4.2 release-5.4.1 release-5.4.0 diff --git a/doc/en/announce/release-5.4.3.rst b/doc/en/announce/release-5.4.3.rst new file mode 100644 index 00000000000..4d48fc1193f --- /dev/null +++ b/doc/en/announce/release-5.4.3.rst @@ -0,0 +1,21 @@ +pytest-5.4.3 +======================================= + +pytest 5.4.3 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 +* Bruno Oliveira +* Ran Benita +* Tor Colvin + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index ed4a8ded881..c7b447dfc25 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,29 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 5.4.3 (2020-06-02) +========================= + +Bug Fixes +--------- + +- `#6428 `_: Paths appearing in error messages are now correct in case the current working directory has + changed since the start of the session. + + +- `#6755 `_: Support deleting paths longer than 260 characters on windows created inside tmpdir. + + +- `#6956 `_: Prevent pytest from printing ConftestImportFailure traceback to stdout. + + +- `#7150 `_: Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised. + + +- `#7215 `_: Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase`` + subclasses for skipped tests. + + pytest 5.4.2 (2020-05-08) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 14e6537adaa..96fc55be5a8 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -481,11 +481,10 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - ssssssssssss...ssssssssssss [100%] + ssssssssssss......sss...... [100%] ========================= short test summary info ========================== - SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found - SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.7' not found - 3 passed, 24 skipped in 0.12s + SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found + 12 passed, 15 skipped in 0.12s Indirect parametrization of optional implementations/imports --------------------------------------------------------------------