From f9f092a5e6dc27d28b220fde75b348f086ef85c0 Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 15:20:02 +0100 Subject: [PATCH 01/35] Fix for issue #5430 - junit-xml: logs are not passed to junit report for tests failed not in a teardown phase --- src/_pytest/junitxml.py | 1 + testing/test_junitxml.py | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 9cf22705e..21b54ef86 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -591,6 +591,7 @@ class LogXML: if report.when == "call": reporter.append_failure(report) self.open_reports.append(report) + reporter.write_captured_output(report) else: reporter.append_error(report) elif report.skipped: diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 4c2f22a3d..0132db59d 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1477,3 +1477,45 @@ def test_logging_passing_tests_disabled_does_not_log_test_output( node = dom.find_first_by_tag("testcase") assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-out")) == 0 + + +@parametrize_families +@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) +def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( + testdir, junit_logging, run_and_parse, xunit_family +): + testdir.makeini( + """ + [pytest] + junit_log_passing_tests=False + junit_family={family} + """.format( + family=xunit_family + ) + ) + testdir.makepyfile( + """ + import pytest + import logging + import sys + + def test_func(): + logging.warning('hello') + assert 0 + """ + ) + result, dom = run_and_parse( + "-o", "junit_logging=%s" % junit_logging, family=xunit_family + ) + assert result.ret == 1 + node = dom.find_first_by_tag("testcase") + if junit_logging == "system-out": + assert len(node.find_by_tag("system-err")) == 0 + assert len(node.find_by_tag("system-out")) == 1 + elif junit_logging == "system-err": + assert len(node.find_by_tag("system-err")) == 1 + assert len(node.find_by_tag("system-out")) == 0 + else: + assert junit_logging == "no" + assert len(node.find_by_tag("system-err")) == 0 + assert len(node.find_by_tag("system-out")) == 0 From d940d0b6570535a4e39fed31166fef67ed8769af Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 16:08:45 +0100 Subject: [PATCH 02/35] Create changelog file and update AUTHORS --- AUTHORS | 1 + changelog/5430.bugfix.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/5430.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 6e2f472fe..cc9b14be3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,6 +61,7 @@ Christian Theunert Christian Tismer Christopher Gilling Christopher Dignam +Claudio Madotto CrazyMerlyn Cyrus Maden Damian Skrzypczak diff --git a/changelog/5430.bugfix.rst b/changelog/5430.bugfix.rst new file mode 100644 index 000000000..734685063 --- /dev/null +++ b/changelog/5430.bugfix.rst @@ -0,0 +1 @@ +junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. From 2d24c062b6c1beb914399174c1fd9190f6093371 Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 16:33:17 +0100 Subject: [PATCH 03/35] Avoid duplicating system-out and system-error tags --- src/_pytest/junitxml.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 21b54ef86..206e44d96 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -591,7 +591,8 @@ class LogXML: if report.when == "call": reporter.append_failure(report) self.open_reports.append(report) - reporter.write_captured_output(report) + if not self.log_passing_tests: + reporter.write_captured_output(report) else: reporter.append_error(report) elif report.skipped: From b66dc800081bff90b82b6e3649da5838c7531089 Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 15:20:02 +0100 Subject: [PATCH 04/35] Fix for issue #5430 - junit-xml: logs are not passed to junit report for tests failed not in a teardown phase --- src/_pytest/junitxml.py | 1 + testing/test_junitxml.py | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 9cf22705e..21b54ef86 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -591,6 +591,7 @@ class LogXML: if report.when == "call": reporter.append_failure(report) self.open_reports.append(report) + reporter.write_captured_output(report) else: reporter.append_error(report) elif report.skipped: diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 4c2f22a3d..0132db59d 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1477,3 +1477,45 @@ def test_logging_passing_tests_disabled_does_not_log_test_output( node = dom.find_first_by_tag("testcase") assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-out")) == 0 + + +@parametrize_families +@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) +def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( + testdir, junit_logging, run_and_parse, xunit_family +): + testdir.makeini( + """ + [pytest] + junit_log_passing_tests=False + junit_family={family} + """.format( + family=xunit_family + ) + ) + testdir.makepyfile( + """ + import pytest + import logging + import sys + + def test_func(): + logging.warning('hello') + assert 0 + """ + ) + result, dom = run_and_parse( + "-o", "junit_logging=%s" % junit_logging, family=xunit_family + ) + assert result.ret == 1 + node = dom.find_first_by_tag("testcase") + if junit_logging == "system-out": + assert len(node.find_by_tag("system-err")) == 0 + assert len(node.find_by_tag("system-out")) == 1 + elif junit_logging == "system-err": + assert len(node.find_by_tag("system-err")) == 1 + assert len(node.find_by_tag("system-out")) == 0 + else: + assert junit_logging == "no" + assert len(node.find_by_tag("system-err")) == 0 + assert len(node.find_by_tag("system-out")) == 0 From 91b3ff1bb73afdbc5dfcda1f15fb241027e4d0e7 Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 16:08:45 +0100 Subject: [PATCH 05/35] Create changelog file and update AUTHORS --- AUTHORS | 1 + changelog/5430.bugfix.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/5430.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 6e2f472fe..cc9b14be3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,6 +61,7 @@ Christian Theunert Christian Tismer Christopher Gilling Christopher Dignam +Claudio Madotto CrazyMerlyn Cyrus Maden Damian Skrzypczak diff --git a/changelog/5430.bugfix.rst b/changelog/5430.bugfix.rst new file mode 100644 index 000000000..734685063 --- /dev/null +++ b/changelog/5430.bugfix.rst @@ -0,0 +1 @@ +junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. From 31d5cedc6d087d7f5932e8f53e968c15d72fa463 Mon Sep 17 00:00:00 2001 From: Claudio Madotto Date: Sun, 24 Nov 2019 16:33:17 +0100 Subject: [PATCH 06/35] Avoid duplicating system-out and system-error tags --- src/_pytest/junitxml.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 21b54ef86..206e44d96 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -591,7 +591,8 @@ class LogXML: if report.when == "call": reporter.append_failure(report) self.open_reports.append(report) - reporter.write_captured_output(report) + if not self.log_passing_tests: + reporter.write_captured_output(report) else: reporter.append_error(report) elif report.skipped: From c7f9fda42d475a5579aa988d8b491be3de53925b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 3 Dec 2019 19:49:20 -0300 Subject: [PATCH 07/35] Fix assertion rewriting module detection for egg dists Fix #6301 --- changelog/6301.bugfix.rst | 1 + src/_pytest/config/__init__.py | 53 +++++++++++++++++++++++++++++++++- testing/test_config.py | 16 ++++++---- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 changelog/6301.bugfix.rst diff --git a/changelog/6301.bugfix.rst b/changelog/6301.bugfix.rst new file mode 100644 index 000000000..f13c83343 --- /dev/null +++ b/changelog/6301.bugfix.rst @@ -0,0 +1 @@ +Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index eecb29365..038695d98 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -630,16 +630,67 @@ notset = Notset() def _iter_rewritable_modules(package_files): + """ + Given an iterable of file names in a source distribution, return the "names" that should + be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should + be added as "pytest_mock" in the assertion rewrite mechanism. + + This function has to deal with dist-info based distributions and egg based distributions + (which are still very much in use for "editable" installs). + + Here are the file names as seen in a dist-info based distribution: + + pytest_mock/__init__.py + pytest_mock/_version.py + pytest_mock/plugin.py + pytest_mock.egg-info/PKG-INFO + + Here are the file names as seen in an egg based distribution: + + src/pytest_mock/__init__.py + src/pytest_mock/_version.py + src/pytest_mock/plugin.py + src/pytest_mock.egg-info/PKG-INFO + LICENSE + setup.py + + We have to take in account those two distribution flavors in order to determine which + names should be considered for assertion rewriting. + + More information: + https://github.com/pytest-dev/pytest-mock/issues/167 + """ + package_files = list(package_files) + seen_some = False for fn in package_files: is_simple_module = "/" not in fn and fn.endswith(".py") is_package = fn.count("/") == 1 and fn.endswith("__init__.py") if is_simple_module: module_name, _ = os.path.splitext(fn) - yield module_name + # we ignore "setup.py" at the root of the distribution + if module_name != "setup": + seen_some = True + yield module_name elif is_package: package_name = os.path.dirname(fn) + seen_some = True yield package_name + if not seen_some: + # at this point we did not find any packages or modules suitable for assertion + # rewriting, so we try again by stripping the first path component (to account for + # "src" based source trees for example) + # this approach lets us have the common case continue to be fast, as egg-distributions + # are rarer + new_package_files = [] + for fn in package_files: + parts = fn.split("/") + new_fn = "/".join(parts[1:]) + if new_fn: + new_package_files.append(new_fn) + if new_package_files: + yield from _iter_rewritable_modules(new_package_files) + class Config: """ diff --git a/testing/test_config.py b/testing/test_config.py index f146b52a4..9735fc176 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -422,15 +422,21 @@ class TestConfigAPI: @pytest.mark.parametrize( "names, expected", [ + # dist-info based distributions root are files as will be put in PYTHONPATH (["bar.py"], ["bar"]), - (["foo", "bar.py"], []), - (["foo", "bar.pyc"], []), - (["foo", "__init__.py"], ["foo"]), - (["foo", "bar", "__init__.py"], []), + (["foo/bar.py"], ["bar"]), + (["foo/bar.pyc"], []), + (["foo/__init__.py"], ["foo"]), + (["bar/__init__.py", "xz.py"], ["bar", "xz"]), + (["setup.py"], []), + # egg based distributions root contain the files from the dist root + (["src/bar/__init__.py"], ["bar"]), + (["src/bar/__init__.py", "setup.py"], ["bar"]), + (["source/python/bar/__init__.py", "setup.py"], ["bar"]), ], ) def test_iter_rewritable_modules(self, names, expected): - assert list(_iter_rewritable_modules(["/".join(names)])) == expected + assert list(_iter_rewritable_modules(names)) == expected class TestConfigFromdictargs: From e24b6b03886a36461af3381637a33fd8de6bee06 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Thu, 5 Dec 2019 13:56:45 +0100 Subject: [PATCH 08/35] Change -k EXPRESSION matching to be case-insensitive --- src/_pytest/mark/legacy.py | 10 +++++++++- testing/test_collection.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/_pytest/mark/legacy.py b/src/_pytest/mark/legacy.py index 3721e3b02..651288cb8 100644 --- a/src/_pytest/mark/legacy.py +++ b/src/_pytest/mark/legacy.py @@ -57,7 +57,15 @@ class KeywordMapping: return cls(mapped_names) def __getitem__(self, subname): - for name in self._names: + """Return whether subname is included within stored names. + + The string inclusion check is case-insensitive. + + """ + subname = subname.lower() + names = [name.lower() for name in self._names] + + for name in names: if subname in name: return True return False diff --git a/testing/test_collection.py b/testing/test_collection.py index 624e9dd4e..fcbfcf5fb 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -809,6 +809,40 @@ class TestNodekeywords: reprec = testdir.inline_run("-k repr") reprec.assertoutcome(passed=1, failed=0) + def test_keyword_matching_is_case_insensitive_by_default(self, testdir): + """Check that selection via -k EXPRESSION is case-insensitive. + + Since markers are also added to the node keywords, they too can + be matched without having to think about case sensitivity. + + """ + testdir.makepyfile( + """ + import pytest + + def test_sPeCiFiCToPiC_1(): + assert True + + class TestSpecificTopic_2: + def test(self): + assert True + + @pytest.mark.sPeCiFiCToPic_3 + def test(): + assert True + + @pytest.mark.sPeCiFiCToPic_4 + class Test: + def test(self): + assert True + + """ + ) + num_all_tests_passed = 4 + for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"): + reprec = testdir.inline_run("-k " + expression) + reprec.assertoutcome(passed=num_all_tests_passed, failed=0) + COLLECTION_ERROR_PY_FILES = dict( test_01_failure=""" From ac5929eef35508d267bc4010cb9b4ae0b6de3f74 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Thu, 5 Dec 2019 14:13:22 +0100 Subject: [PATCH 09/35] Update docs about case-insensitive expression matching --- doc/en/example/markers.rst | 4 ++++ doc/en/usage.rst | 4 ++-- src/_pytest/mark/__init__.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 8143b3fd4..e64f31fd5 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -148,6 +148,10 @@ which implements a substring match on the test names instead of the exact match on markers that ``-m`` provides. This makes it easy to select tests based on their names: +.. versionadded: 5.3.1/6.0 + +The expression matching is now case-insensitive. + .. code-block:: pytest $ pytest -v -k http # running with the above defined example module diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 245a67b68..527794823 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -94,8 +94,8 @@ Pytest supports several ways to run and select tests from the command-line. pytest -k "MyClass and not method" -This will run tests which contain names that match the given *string expression*, which can -include Python operators that use filenames, class names and function names as variables. +This will run tests which contain names that match the given *string expression* (case-insensitive), +which can include Python operators that use filenames, class names and function names as variables. The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``. .. _nodeids: diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index e21e234e7..f493bd839 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -52,7 +52,8 @@ def pytest_addoption(parser): "-k 'not test_method and not test_other' will eliminate the matches. " "Additionally keywords are matched to classes and functions " "containing extra names in their 'extra_keyword_matches' set, " - "as well as functions which have names assigned directly to them.", + "as well as functions which have names assigned directly to them. " + "The matching is case-insensitive.", ) group._addoption( From 24d4882d8267b9ee0f4e2d0f046b8343361d2e91 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Thu, 5 Dec 2019 14:20:07 +0100 Subject: [PATCH 10/35] Update authors file and sort the list --- AUTHORS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index a3e526c5a..105c1be53 100644 --- a/AUTHORS +++ b/AUTHORS @@ -59,12 +59,12 @@ Christian Fetzer Christian Neumüller Christian Theunert Christian Tismer -Christopher Gilling +Christoph Buelter Christopher Dignam +Christopher Gilling CrazyMerlyn Cyrus Maden Damian Skrzypczak -Dhiren Serai Daniel Grana Daniel Hahler Daniel Nuri @@ -79,6 +79,7 @@ David Szotten David Vierra Daw-Ran Liou Denis Kirisov +Dhiren Serai Diego Russo Dmitry Dygalo Dmitry Pribysh From 5a7de2c2cb34d49c3ea3aa0baee4a6b568ef37cd Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Thu, 5 Dec 2019 14:28:21 +0100 Subject: [PATCH 11/35] Add changelog file for PR 6316 --- changelog/6316.improvement.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/6316.improvement.rst diff --git a/changelog/6316.improvement.rst b/changelog/6316.improvement.rst new file mode 100644 index 000000000..6ab7d8717 --- /dev/null +++ b/changelog/6316.improvement.rst @@ -0,0 +1 @@ +Matching of ``-k EXPRESSION`` to test names is now case-insensitive. From 623b3982b0ac8268c70d204c225893c26d7d9f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BClter?= Date: Thu, 5 Dec 2019 16:59:08 +0100 Subject: [PATCH 12/35] Update doc/en/example/markers.rst Co-Authored-By: Bruno Oliveira --- doc/en/example/markers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index e64f31fd5..e83beedd0 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -148,7 +148,7 @@ which implements a substring match on the test names instead of the exact match on markers that ``-m`` provides. This makes it easy to select tests based on their names: -.. versionadded: 5.3.1/6.0 +.. versionadded: 5.4 The expression matching is now case-insensitive. From a326fa22c6e5ad711ffecd3a8f9432737d0ccc1b Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Thu, 5 Dec 2019 17:02:18 +0100 Subject: [PATCH 13/35] Add a failing test to ensure not everything matches by accident --- testing/test_collection.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index fcbfcf5fb..e7bcb60a9 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -836,12 +836,15 @@ class TestNodekeywords: def test(self): assert True + def test_failing_5(): + assert False, "This should not match" + """ ) - num_all_tests_passed = 4 + num_matching_tests = 4 for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"): reprec = testdir.inline_run("-k " + expression) - reprec.assertoutcome(passed=num_all_tests_passed, failed=0) + reprec.assertoutcome(passed=num_matching_tests, failed=0) COLLECTION_ERROR_PY_FILES = dict( From 64c71daa21ba58ca6e960dc0744b9d7b32ecd633 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 5 Dec 2019 17:32:45 -0300 Subject: [PATCH 14/35] Add 4.6.7 changelog to master --- doc/en/changelog.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 1c9140f39..972ebf177 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -796,6 +796,20 @@ Improved Documentation - `#5416 `_: Fix PytestUnknownMarkWarning in run/skip example. +pytest 4.6.7 (2019-12-05) +========================= + +Bug Fixes +--------- + +- `#5477 `_: The XML file produced by ``--junitxml`` now correctly contain a ```` root element. + + +- `#6044 `_: Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories, + for instance when multiple processes try to remove the same directory (common with ``pytest-xdist`` + for example). + + pytest 4.6.6 (2019-10-11) ========================= From 3a0f436c1a02f41af942e2d9f3d3a64f5fff3db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BClter?= Date: Fri, 6 Dec 2019 08:40:49 +0100 Subject: [PATCH 15/35] Iterate a generator expression instead of a temporary list Co-Authored-By: Bruno Oliveira --- src/_pytest/mark/legacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/mark/legacy.py b/src/_pytest/mark/legacy.py index 651288cb8..766b8f9bd 100644 --- a/src/_pytest/mark/legacy.py +++ b/src/_pytest/mark/legacy.py @@ -63,7 +63,7 @@ class KeywordMapping: """ subname = subname.lower() - names = [name.lower() for name in self._names] + names = (name.lower() for name in self._names) for name in names: if subname in name: From c6ed69a6663657655579e76265750a6dbe9bfab9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 6 Dec 2019 08:47:39 -0300 Subject: [PATCH 16/35] Replace 'removal' by 'breaking' changelog category As discussed, sometimes we will need to introduce changes which are not necessarily removals but might break existing suites --- .pre-commit-config.yaml | 4 ++-- changelog/{6316.improvement.rst => 6316.breaking.rst} | 0 changelog/README.rst | 2 +- pyproject.toml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename changelog/{6316.improvement.rst => 6316.breaking.rst} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c89a6272..64f3f32ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,8 +53,8 @@ repos: - id: changelogs-rst name: changelog filenames language: fail - entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst' - exclude: changelog/(\d+\.(feature|improvement|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst) + entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst' + exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst) files: ^changelog/ - id: py-deprecated name: py library is deprecated diff --git a/changelog/6316.improvement.rst b/changelog/6316.breaking.rst similarity index 100% rename from changelog/6316.improvement.rst rename to changelog/6316.breaking.rst diff --git a/changelog/README.rst b/changelog/README.rst index adabc9ca1..dd0e7dfea 100644 --- a/changelog/README.rst +++ b/changelog/README.rst @@ -18,7 +18,7 @@ Each file should be named like ``..rst``, where * ``bugfix``: fixes a reported bug. * ``doc``: documentation improvement, like rewording an entire session or adding missing docs. * ``deprecation``: feature deprecation. -* ``removal``: feature removal. +* ``breaking``: a change which may break existing suites, such as feature removal or behavior change. * ``vendor``: changes in packages vendored in pytest. * ``trivial``: fixing a small typo or internal change that might be noteworthy. diff --git a/pyproject.toml b/pyproject.toml index 31bf3bf4b..4ac1fd754 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ title_format = "pytest {version} ({project_date})" template = "changelog/_template.rst" [[tool.towncrier.type]] - directory = "removal" - name = "Removals" + directory = "breaking" + name = "Breaking Changes" showcontent = true [[tool.towncrier.type]] From 3812985ed4601d67543d0592e39480d33ed80268 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 1 Dec 2019 12:46:16 +0100 Subject: [PATCH 17/35] update backward compatibility policy to allow for breakage Co-Authored-By: Anthony Sottile Co-Authored-By: Bruno Oliveira Co-Authored-By: Hugo van Kemenade --- doc/en/backwards-compatibility.rst | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 56afd98af..d5b2d79d6 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -3,6 +3,61 @@ Backwards Compatibility Policy ============================== +.. versionadded: 6.0 + +pytest is actively evolving and is a project that has been decades in the making, +we keep learning about new and better structures to express different details about testing. + +While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors. + +As of now, pytest considers multipe types of backward compatibility transitions: + +a) trivial: APIs which trivially translate to the new mechanism, + and do not cause problematic changes. + + We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation. + +b) transitional: the old and new API don't conflict + and we can help users transition by using warnings, while supporting both for a prolonged time. + + We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). + + When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed. + + +c) true breakage: should only to be considered when normal transition is unreasonably unsustainable and would offset important development/features by years. + In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance. + + Examples for such upcoming changes: + + * removal of ``pytest_runtest_protocol/nextitem`` - `#895`_ + * rearranging of the node tree to include ``FunctionDefinition`` + * rearranging of ``SetupState`` `#895`_ + + True breakages must be announced first in an issue containing: + + * Detailed description of the change + * Rationale + * Expected impact on users and plugin authors (example in `#895`_) + + After there's no hard *-1* on the issue it should be followed up by an initial proof-of-concept Pull Request. + + This POC serves as both a coordination point to assess impact and potential inspriation to come up with a transitional solution after all. + + After a reasonable amount of time the PR can be merged to base a new major release. + + For the PR to mature from POC to acceptance, it must contain: + * Setup of deprecation errors/warnings that help users fix and port their code. If it is possible to introduce a deprecation period under the current series, before the true breakage, it should be introduced in a separate PR and be part of the current release stream. + * Detailed description of the rationale and examples on how to port code in ``doc/en/deprecations.rst``. + + +History +========= + + +Focus primary on smooth transition - stance (pre 6.0) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary. With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around. @@ -20,3 +75,6 @@ Deprecation Roadmap Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`. We track future deprecation and removal of features using milestones and the `deprecation `_ and `removal `_ labels on GitHub. + + +.. _`#895`: https://github.com/pytest-dev/pytest/issues/895 From 2ddc330b626705fc83a01d87e40d584ab33af625 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Sun, 8 Dec 2019 22:26:53 -0500 Subject: [PATCH 18/35] Fixes #6326: Typo in the Security section docs home page. --- README.rst | 2 +- doc/en/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 482dde6f5..53e643a5b 100644 --- a/README.rst +++ b/README.rst @@ -137,7 +137,7 @@ Save time, reduce risk, and improve code health, while paying the maintainers of Security ^^^^^^^^ -pytest has never been associated with a security vunerability, but in any case, to report a +pytest has never been associated with a security vulnerability, but in any case, to report a security vulnerability please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. diff --git a/doc/en/index.rst b/doc/en/index.rst index b0abe7446..9c2f08f3a 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -112,7 +112,7 @@ Save time, reduce risk, and improve code health, while paying the maintainers of Security ^^^^^^^^ -pytest has never been associated with a security vunerability, but in any case, to report a +pytest has never been associated with a security vulnerability, but in any case, to report a security vulnerability please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. From e13ad22364ce4c2403268988228df73a5d26d8bb Mon Sep 17 00:00:00 2001 From: cmachalo Date: Tue, 3 Dec 2019 14:15:13 -0800 Subject: [PATCH 19/35] Include new --capture-mode=tee-sys option Fix #4597 --- AUTHORS | 1 + changelog/4597.feature.rst | 1 + doc/en/capture.rst | 19 ++++++++++++----- src/_pytest/capture.py | 20 ++++++++++++++++-- src/_pytest/compat.py | 11 ++++++++++ testing/acceptance_test.py | 25 +++++++++++++++++++++++ testing/test_capture.py | 42 +++++++++++++++++++++++++++++++++++++- 7 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 changelog/4597.feature.rst diff --git a/AUTHORS b/AUTHORS index a3e526c5a..163b317bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -52,6 +52,7 @@ Carl Friedrich Bolz Carlos Jenkins Ceridwen Charles Cloud +Charles Machalow Charnjit SiNGH (CCSJ) Chris Lamb Christian Boelsen diff --git a/changelog/4597.feature.rst b/changelog/4597.feature.rst new file mode 100644 index 000000000..aac395373 --- /dev/null +++ b/changelog/4597.feature.rst @@ -0,0 +1 @@ +New :ref:`--capture=tee-sys ` option to allow both live printing and capturing of test output. diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 3e744e764..3982c6116 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -21,27 +21,36 @@ file descriptors. This allows to capture output from simple print statements as well as output from a subprocess started by a test. +.. _capture-method: + Setting capturing methods or disabling capturing ------------------------------------------------- -There are two ways in which ``pytest`` can perform capturing: +There are three ways in which ``pytest`` can perform capturing: -* file descriptor (FD) level capturing (default): All writes going to the +* ``fd`` (file descriptor) level capturing (default): All writes going to the operating system file descriptors 1 and 2 will be captured. * ``sys`` level capturing: Only writes to Python files ``sys.stdout`` and ``sys.stderr`` will be captured. No capturing of writes to filedescriptors is performed. +* ``tee-sys`` capturing: Python writes to ``sys.stdout`` and ``sys.stderr`` + will be captured, however the writes will also be passed-through to + the actual ``sys.stdout`` and ``sys.stderr``. This allows output to be + 'live printed' and captured for plugin use, such as junitxml (new in pytest 5.4). + .. _`disable capturing`: You can influence output capturing mechanisms from the command line: .. code-block:: bash - pytest -s # disable all capturing - pytest --capture=sys # replace sys.stdout/stderr with in-mem files - pytest --capture=fd # also point filedescriptors 1 and 2 to temp file + pytest -s # disable all capturing + pytest --capture=sys # replace sys.stdout/stderr with in-mem files + pytest --capture=fd # also point filedescriptors 1 and 2 to temp file + pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr + # and passing it along to the actual sys.stdout/stderr .. _printdebugging: diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 0cd3ce604..24072d34a 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -11,6 +11,7 @@ from io import UnsupportedOperation from tempfile import TemporaryFile import pytest +from _pytest.compat import CaptureAndPassthroughIO from _pytest.compat import CaptureIO from _pytest.fixtures import FixtureRequest @@ -24,8 +25,8 @@ def pytest_addoption(parser): action="store", default="fd" if hasattr(os, "dup") else "sys", metavar="method", - choices=["fd", "sys", "no"], - help="per-test capturing method: one of fd|sys|no.", + choices=["fd", "sys", "no", "tee-sys"], + help="per-test capturing method: one of fd|sys|no|tee-sys.", ) group._addoption( "-s", @@ -90,6 +91,8 @@ class CaptureManager: return MultiCapture(out=True, err=True, Capture=SysCapture) elif method == "no": return MultiCapture(out=False, err=False, in_=False) + elif method == "tee-sys": + return MultiCapture(out=True, err=True, in_=False, Capture=TeeSysCapture) raise ValueError("unknown capturing method: %r" % method) # pragma: no cover def is_capturing(self): @@ -681,6 +684,19 @@ class SysCapture: self._old.flush() +class TeeSysCapture(SysCapture): + def __init__(self, fd, tmpfile=None): + name = patchsysdict[fd] + self._old = getattr(sys, name) + self.name = name + if tmpfile is None: + if name == "stdin": + tmpfile = DontReadFromInput() + else: + tmpfile = CaptureAndPassthroughIO(self._old) + self.tmpfile = tmpfile + + class SysCaptureBinary(SysCapture): # Ignore type because it doesn't match the type in the superclass (str). EMPTY_BUFFER = b"" # type: ignore diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 8dd74b577..c566a39e8 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -13,6 +13,7 @@ from inspect import signature from typing import Any from typing import Callable from typing import Generic +from typing import IO from typing import Optional from typing import overload from typing import Tuple @@ -371,6 +372,16 @@ class CaptureIO(io.TextIOWrapper): return self.buffer.getvalue().decode("UTF-8") +class CaptureAndPassthroughIO(CaptureIO): + def __init__(self, other: IO) -> None: + self._other = other + super().__init__() + + def write(self, s) -> int: + super().write(s) + return self._other.write(s) + + if sys.version_info < (3, 5, 2): # pragma: no cover def overload(f): # noqa: F811 diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 8f7be14be..ffb6836e3 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1285,3 +1285,28 @@ def test_pdb_can_be_rewritten(testdir): ] ) assert result.ret == 1 + + +def test_tee_stdio_captures_and_live_prints(testdir): + testpath = testdir.makepyfile( + """ + import sys + def test_simple(): + print ("@this is stdout@") + print ("@this is stderr@", file=sys.stderr) + """ + ) + result = testdir.runpytest_subprocess( + testpath, "--capture=tee-sys", "--junitxml=output.xml" + ) + + # ensure stdout/stderr were 'live printed' + result.stdout.fnmatch_lines(["*@this is stdout@*"]) + result.stderr.fnmatch_lines(["*@this is stderr@*"]) + + # now ensure the output is in the junitxml + with open(os.path.join(testdir.tmpdir.strpath, "output.xml"), "r") as f: + fullXml = f.read() + + assert "@this is stdout@\n" in fullXml + assert "@this is stderr@\n" in fullXml diff --git a/testing/test_capture.py b/testing/test_capture.py index 94af3aef7..1885c9bb6 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -32,6 +32,10 @@ def StdCapture(out=True, err=True, in_=True): return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture) +def TeeStdCapture(out=True, err=True, in_=True): + return capture.MultiCapture(out, err, in_, Capture=capture.TeeSysCapture) + + class TestCaptureManager: def test_getmethod_default_no_fd(self, monkeypatch): from _pytest.capture import pytest_addoption @@ -816,6 +820,25 @@ class TestCaptureIO: assert f.getvalue() == "foo\r\n" +class TestCaptureAndPassthroughIO(TestCaptureIO): + def test_text(self): + sio = io.StringIO() + f = capture.CaptureAndPassthroughIO(sio) + f.write("hello") + s1 = f.getvalue() + assert s1 == "hello" + s2 = sio.getvalue() + assert s2 == s1 + f.close() + sio.close() + + def test_unicode_and_str_mixture(self): + sio = io.StringIO() + f = capture.CaptureAndPassthroughIO(sio) + f.write("\u00f6") + pytest.raises(TypeError, f.write, b"hello") + + def test_dontreadfrominput(): from _pytest.capture import DontReadFromInput @@ -1112,6 +1135,23 @@ class TestStdCapture: pytest.raises(IOError, sys.stdin.read) +class TestTeeStdCapture(TestStdCapture): + captureclass = staticmethod(TeeStdCapture) + + def test_capturing_error_recursive(self): + """ for TeeStdCapture since we passthrough stderr/stdout, cap1 + should get all output, while cap2 should only get "cap2\n" """ + + with self.getcapture() as cap1: + print("cap1") + with self.getcapture() as cap2: + print("cap2") + out2, err2 = cap2.readouterr() + out1, err1 = cap1.readouterr() + assert out1 == "cap1\ncap2\n" + assert out2 == "cap2\n" + + class TestStdCaptureFD(TestStdCapture): pytestmark = needsosdup captureclass = staticmethod(StdCaptureFD) @@ -1252,7 +1292,7 @@ def test_close_and_capture_again(testdir): ) -@pytest.mark.parametrize("method", ["SysCapture", "FDCapture"]) +@pytest.mark.parametrize("method", ["SysCapture", "FDCapture", "TeeSysCapture"]) def test_capturing_and_logging_fundamentals(testdir, method): if method == "StdCaptureFD" and not hasattr(os, "dup"): pytest.skip("need os.dup") From 071106042289e67341e5435e456780fe72854809 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 9 Dec 2019 15:57:19 -0300 Subject: [PATCH 20/35] Change 4639 from feature to improvement An improvement seems more adequate here. --- changelog/{4639.feature.rst => 4639.improvement.rst} | 1 + 1 file changed, 1 insertion(+) rename changelog/{4639.feature.rst => 4639.improvement.rst} (99%) diff --git a/changelog/4639.feature.rst b/changelog/4639.improvement.rst similarity index 99% rename from changelog/4639.feature.rst rename to changelog/4639.improvement.rst index f296f1649..e18b3b619 100644 --- a/changelog/4639.feature.rst +++ b/changelog/4639.improvement.rst @@ -1,3 +1,4 @@ Revert "A warning is now issued when assertions are made for ``None``". + The warning proved to be less useful than initially expected and had quite a few false positive cases. From 59067ad33d55e66a54ec617effe4350e42d66f65 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Dec 2019 07:41:23 -0300 Subject: [PATCH 21/35] Make -r letters "f" and "F" aliases As far as the output is concerned, they are both identical so it doesn't make sense to have both. setup, teardown, and collect failures are already reported as "errors", "E". --- src/_pytest/terminal.py | 13 ++++++------- testing/test_terminal.py | 22 +++++++++++++++++++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index e88545eca..d1ee701e8 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -173,6 +173,9 @@ def getreportopt(config: Config) -> str: elif config.option.disable_warnings and "w" in reportchars: reportchars = reportchars.replace("w", "") for char in reportchars: + # f and F are aliases + if char == "F": + char = "f" if char == "a": reportopts = "sxXwEf" elif char == "A": @@ -185,19 +188,16 @@ def getreportopt(config: Config) -> str: @pytest.hookimpl(trylast=True) # after _pytest.runner def pytest_report_teststatus(report: TestReport) -> Tuple[str, str, str]: + letter = "F" if report.passed: letter = "." elif report.skipped: letter = "s" - elif report.failed: - letter = "F" - if report.when != "call": - letter = "f" - # Report failed CollectReports as "error" (in line with pytest_collectreport). outcome = report.outcome - if report.when == "collect" and outcome == "failed": + if report.when in ("collect", "setup", "teardown") and outcome == "failed": outcome = "error" + letter = "E" return outcome, letter, outcome.upper() @@ -988,7 +988,6 @@ class TerminalReporter: "x": show_xfailed, "X": show_xpassed, "f": partial(show_simple, "failed"), - "F": partial(show_simple, "failed"), "s": show_skipped, "S": show_skipped, "p": partial(show_simple, "passed"), diff --git a/testing/test_terminal.py b/testing/test_terminal.py index fab13b07e..2d875a64c 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -754,6 +754,18 @@ class TestTerminalFunctional: result = testdir.runpytest(*params) result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"]) + def test_summary_f_alias(self, testdir): + testdir.makepyfile( + """ + def test(): + assert False + """ + ) + result = testdir.runpytest("-rfF") + expected = "FAILED test_summary_f_alias.py::test - assert False" + result.stdout.fnmatch_lines([expected]) + assert result.stdout.lines.count(expected) == 1 + def test_fail_extra_reporting(testdir, monkeypatch): monkeypatch.setenv("COLUMNS", "80") @@ -1685,12 +1697,16 @@ class TestProgressWithTeardown: testdir.makepyfile( """ def test_foo(fail_teardown): - assert False + assert 0 """ ) - output = testdir.runpytest() + output = testdir.runpytest("-rfE") output.stdout.re_match_lines( - [r"test_teardown_with_test_also_failing.py FE\s+\[100%\]"] + [ + r"test_teardown_with_test_also_failing.py FE\s+\[100%\]", + "FAILED test_teardown_with_test_also_failing.py::test_foo - assert 0", + "ERROR test_teardown_with_test_also_failing.py::test_foo - assert False", + ] ) def test_teardown_many(self, testdir, many_files): From fa51a26743930576e2587a92217b1d9f5063175d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Dec 2019 07:48:07 -0300 Subject: [PATCH 22/35] Make -r letters "s" and "S" aliases Similar reasons as the previous commit --- src/_pytest/terminal.py | 8 ++++---- testing/test_terminal.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index d1ee701e8..2a99bfdd5 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -172,10 +172,11 @@ def getreportopt(config: Config) -> str: reportchars += "w" elif config.option.disable_warnings and "w" in reportchars: reportchars = reportchars.replace("w", "") + aliases = {"F", "S"} for char in reportchars: - # f and F are aliases - if char == "F": - char = "f" + # handle old aliases + if char in aliases: + char = char.lower() if char == "a": reportopts = "sxXwEf" elif char == "A": @@ -989,7 +990,6 @@ class TerminalReporter: "X": show_xpassed, "f": partial(show_simple, "failed"), "s": show_skipped, - "S": show_skipped, "p": partial(show_simple, "passed"), "E": partial(show_simple, "error"), } # type: Mapping[str, Callable[[List[str]], None]] diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 2d875a64c..0fe0e09e1 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -755,6 +755,7 @@ class TestTerminalFunctional: result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"]) def test_summary_f_alias(self, testdir): + """Test that 'f' and 'F' report chars are aliases and don't show up twice in the summary (#6334)""" testdir.makepyfile( """ def test(): @@ -766,6 +767,22 @@ class TestTerminalFunctional: result.stdout.fnmatch_lines([expected]) assert result.stdout.lines.count(expected) == 1 + def test_summary_s_alias(self, testdir): + """Test that 's' and 'S' report chars are aliases and don't show up twice in the summary""" + testdir.makepyfile( + """ + import pytest + + @pytest.mark.skip + def test(): + pass + """ + ) + result = testdir.runpytest("-rsS") + expected = "SKIPPED [1] test_summary_s_alias.py:3: unconditional skip" + result.stdout.fnmatch_lines([expected]) + assert result.stdout.lines.count(expected) == 1 + def test_fail_extra_reporting(testdir, monkeypatch): monkeypatch.setenv("COLUMNS", "80") From 9b74bf1e0c4e0f3ce4e440ec50ca9b5f190ce8c7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Dec 2019 08:05:22 -0300 Subject: [PATCH 23/35] Add CHANGELOG entry for #6334 --- changelog/6334.bugfix.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/6334.bugfix.rst diff --git a/changelog/6334.bugfix.rst b/changelog/6334.bugfix.rst new file mode 100644 index 000000000..abd4c748b --- /dev/null +++ b/changelog/6334.bugfix.rst @@ -0,0 +1,3 @@ +Fix summary entries appearing twice when ``f/F`` and ``s/S`` report chars were used at the same time in the ``-r`` command-line option (for example ``-rFf``). + +The upper case variants were never documented and the preferred form should be the lower case. From 8942a05cfec4b97d7aa94e55d08e41a08397e78e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 9 Dec 2019 15:57:19 -0300 Subject: [PATCH 24/35] Change 4639 from feature to improvement An improvement seems more adequate here. --- changelog/{4639.feature.rst => 4639.improvement.rst} | 1 + 1 file changed, 1 insertion(+) rename changelog/{4639.feature.rst => 4639.improvement.rst} (99%) diff --git a/changelog/4639.feature.rst b/changelog/4639.improvement.rst similarity index 99% rename from changelog/4639.feature.rst rename to changelog/4639.improvement.rst index f296f1649..e18b3b619 100644 --- a/changelog/4639.feature.rst +++ b/changelog/4639.improvement.rst @@ -1,3 +1,4 @@ Revert "A warning is now issued when assertions are made for ``None``". + The warning proved to be less useful than initially expected and had quite a few false positive cases. From 10fcac7f908b792264c3e6a402aa1aa01345acc4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Dec 2019 08:51:15 -0300 Subject: [PATCH 25/35] Preparing release version 5.3.2 --- changelog/4639.improvement.rst | 4 ---- changelog/5430.bugfix.rst | 1 - changelog/6290.bugfix.rst | 1 - changelog/6301.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-5.3.2.rst | 27 +++++++++++++++++++++++++++ doc/en/changelog.rst | 29 ++++++++++++++++++++++++----- doc/en/example/parametrize.rst | 8 ++++---- doc/en/example/reportingdemo.rst | 4 ++-- doc/en/example/simple.rst | 4 ++-- doc/en/getting-started.rst | 2 +- 11 files changed, 61 insertions(+), 21 deletions(-) delete mode 100644 changelog/4639.improvement.rst delete mode 100644 changelog/5430.bugfix.rst delete mode 100644 changelog/6290.bugfix.rst delete mode 100644 changelog/6301.bugfix.rst create mode 100644 doc/en/announce/release-5.3.2.rst diff --git a/changelog/4639.improvement.rst b/changelog/4639.improvement.rst deleted file mode 100644 index e18b3b619..000000000 --- a/changelog/4639.improvement.rst +++ /dev/null @@ -1,4 +0,0 @@ -Revert "A warning is now issued when assertions are made for ``None``". - -The warning proved to be less useful than initially expected and had quite a -few false positive cases. diff --git a/changelog/5430.bugfix.rst b/changelog/5430.bugfix.rst deleted file mode 100644 index 734685063..000000000 --- a/changelog/5430.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. diff --git a/changelog/6290.bugfix.rst b/changelog/6290.bugfix.rst deleted file mode 100644 index f6f1560d4..000000000 --- a/changelog/6290.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now. diff --git a/changelog/6301.bugfix.rst b/changelog/6301.bugfix.rst deleted file mode 100644 index f13c83343..000000000 --- a/changelog/6301.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 7d8c4c5ac..e44ab212e 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-5.3.2 release-5.3.1 release-5.3.0 release-5.2.4 diff --git a/doc/en/announce/release-5.3.2.rst b/doc/en/announce/release-5.3.2.rst new file mode 100644 index 000000000..a1b37a18a --- /dev/null +++ b/doc/en/announce/release-5.3.2.rst @@ -0,0 +1,27 @@ +pytest-5.3.2 +======================================= + +pytest 5.3.2 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 +* Claudio Madotto +* Daniel Hahler +* Jared Vasquez +* Michael Rose +* Ran Benita +* Ronny Pfannschmidt +* Zac Hatfield-Dodds +* zupermanzupereroe + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 972ebf177..46fa240e0 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -24,13 +24,32 @@ with advance notice in the **Deprecations** section of releases. .. include:: _changelog_towncrier_draft.rst - - - - - .. towncrier release notes start +pytest 5.3.2 (2019-12-13) +========================= + +Improvements +------------ + +- `#4639 `_: Revert "A warning is now issued when assertions are made for ``None``". + + The warning proved to be less useful than initially expected and had quite a + few false positive cases. + + + +Bug Fixes +--------- + +- `#5430 `_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. + + +- `#6290 `_: The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now. + + +- `#6301 `_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). + pytest 5.3.1 (2019-11-25) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 7230f2b00..15593b28a 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -475,10 +475,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 - ssssssssssssssssssssssss... [100%] + ssssssssssss...ssssssssssss [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.6' not found + SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.7' not found 3 passed, 24 skipped in 0.12s Indirect parametrization of optional implementations/imports @@ -604,13 +604,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collecting ... collected 18 items / 15 deselected / 3 selected + collecting ... collected 17 items / 14 deselected / 3 selected test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%] - =============== 2 passed, 15 deselected, 1 xfailed in 0.12s ================ + =============== 2 passed, 14 deselected, 1 xfailed in 0.12s ================ As the result: diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index eb978c5ea..1c06782f6 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -436,7 +436,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: items = [1, 2, 3] print("items is {!r}".format(items)) > a, b = items.pop() - E TypeError: cannot unpack non-iterable int object + E TypeError: 'int' object is not iterable failure_demo.py:181: TypeError --------------------------- Captured stdout call --------------------------- @@ -516,7 +516,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_z2_type_error(self): items = 3 > a, b = items - E TypeError: cannot unpack non-iterable int object + E TypeError: 'int' object is not iterable failure_demo.py:222: TypeError ______________________ TestMoreErrors.test_startswith ______________________ diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index c1e13e3b1..05ccbc9b2 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -442,8 +442,8 @@ Now we can profile which test functions execute the slowest: ========================= slowest 3 test durations ========================= 0.30s call test_some_are_slow.py::test_funcslow2 - 0.21s call test_some_are_slow.py::test_funcslow1 - 0.11s call test_some_are_slow.py::test_funcfast + 0.20s call test_some_are_slow.py::test_funcslow1 + 0.10s call test_some_are_slow.py::test_funcfast ============================ 3 passed in 0.12s ============================= incremental testing - test steps diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 97347f126..59197d0d7 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -28,7 +28,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py + This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest/__init__.py .. _`simpletest`: From f7409f86851f95c42af2b881b56ab60657107bd0 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Dec 2019 09:17:12 -0300 Subject: [PATCH 26/35] Improve warning about incoming change to 'junitxml_family' default Fix #6265 --- src/_pytest/deprecated.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 5a7066041..09861be64 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -37,5 +37,6 @@ FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning( JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning( "The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n" - "Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible." + "Add 'junit_family=xunit1' to your pytest.ini file to keep the current format " + "in future versions of pytest and silence this warning." ) From 7f24cc2feb87af41e95d2c0aee59fc81a44dbea8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 14 Dec 2019 10:06:59 -0300 Subject: [PATCH 27/35] Remove duplicated user from announcement --- doc/en/announce/release-5.3.2.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/announce/release-5.3.2.rst b/doc/en/announce/release-5.3.2.rst index a1b37a18a..dbd657da3 100644 --- a/doc/en/announce/release-5.3.2.rst +++ b/doc/en/announce/release-5.3.2.rst @@ -20,7 +20,6 @@ Thanks to all who contributed to this release, among them: * Ran Benita * Ronny Pfannschmidt * Zac Hatfield-Dodds -* zupermanzupereroe Happy testing, From afbaee7649c7b2c7a6167e47aea8fb0f46444755 Mon Sep 17 00:00:00 2001 From: Vinay Calastry Date: Tue, 10 Dec 2019 19:47:51 -0800 Subject: [PATCH 28/35] Deprecate --no-print-logs option --- changelog/3238.deprecation.rst | 5 +++++ doc/en/deprecations.rst | 14 ++++++++++++ src/_pytest/deprecated.py | 7 ++++-- src/_pytest/logging.py | 6 +++++ testing/deprecated_test.py | 41 ++++++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 changelog/3238.deprecation.rst diff --git a/changelog/3238.deprecation.rst b/changelog/3238.deprecation.rst new file mode 100644 index 000000000..e4e8f25ea --- /dev/null +++ b/changelog/3238.deprecation.rst @@ -0,0 +1,5 @@ +Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and +provide feedback. + +``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to +display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 88112b12a..bb91b9a11 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -20,6 +20,20 @@ Below is a complete list of all pytest features which are considered deprecated. :ref:`standard warning filters `. +``--no-print-logs`` command-line option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.4 + + +Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and +provide feedback. + +``--show-capture`` command-line option was added in ``pytest 3.5.0` and allows to specify how to +display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). + + + Node Construction changed to ``Node.from_parent`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 1fdc37c04..afaa0e72a 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -19,13 +19,11 @@ DEPRECATED_EXTERNAL_PLUGINS = { "pytest_faulthandler", } - FUNCARGNAMES = PytestDeprecationWarning( "The `funcargnames` attribute was an alias for `fixturenames`, " "since pytest 2.3 - use the newer attribute instead." ) - RESULT_LOG = PytestDeprecationWarning( "--result-log is deprecated, please try the new pytest-reportlog plugin.\n" "See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information." @@ -45,3 +43,8 @@ JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning( "The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n" "Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible." ) + +NO_PRINT_LOGS = PytestDeprecationWarning( + "--no-print-logs is deprecated and scheduled for removal in pytest 6.0.\n" + "Please use --show-capture instead." +) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ccd79b834..e4ccad2c3 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -485,6 +485,12 @@ class LoggingPlugin: self._config = config self.print_logs = get_option_ini(config, "log_print") + if not self.print_logs: + from _pytest.warnings import _issue_warning_captured + from _pytest.deprecated import NO_PRINT_LOGS + + _issue_warning_captured(NO_PRINT_LOGS, self._config.hook, stacklevel=2) + self.formatter = self._create_formatter( get_option_ini(config, "log_format"), get_option_ini(config, "log_date_format"), diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 59cb69a00..c7ca80cbd 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -90,3 +90,44 @@ def test_node_direct_ctor_warning(): nodes.Node(name="test", config=ms, session=ms, nodeid="None") assert w[0].lineno == inspect.currentframe().f_lineno - 1 assert w[0].filename == __file__ + + +def assert_no_print_logs(testdir, args): + result = testdir.runpytest(*args) + result.stdout.fnmatch_lines( + [ + "*--no-print-logs is deprecated and scheduled for removal in pytest 6.0*", + "*Please use --show-capture instead.*", + ] + ) + + +@pytest.mark.filterwarnings("default") +def test_noprintlogs_is_deprecated_cmdline(testdir): + testdir.makepyfile( + """ + def test_foo(): + pass + """ + ) + + assert_no_print_logs(testdir, ("--no-print-logs",)) + + +@pytest.mark.filterwarnings("default") +def test_noprintlogs_is_deprecated_ini(testdir): + testdir.makeini( + """ + [pytest] + log_print=False + """ + ) + + testdir.makepyfile( + """ + def test_foo(): + pass + """ + ) + + assert_no_print_logs(testdir, ()) From ed57b8e08a34c0c80e7e1abc0c28400feb2f7404 Mon Sep 17 00:00:00 2001 From: captainCapitalism <32553875+captainCapitalism@users.noreply.github.com> Date: Thu, 19 Dec 2019 11:35:52 +0100 Subject: [PATCH 29/35] invocation in last section 'pythonpath.rst' title swapped The order of invocations 'python -m pytest' and 'pytest' are different in the header and the explanation. Me being lazy reading about the behaviour of 'former' looked up quickly the title and rushed to implementation to discover it actually works the other way - as stated in the documentation. So I propose to switch the order in the title to achieve consistent ordering and not confusing somebody like me again! :) --- doc/en/pythonpath.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/pythonpath.rst b/doc/en/pythonpath.rst index 0054acc59..65f7b51c8 100644 --- a/doc/en/pythonpath.rst +++ b/doc/en/pythonpath.rst @@ -74,7 +74,7 @@ This is also discussed in details in :ref:`test discovery`. .. _`pytest vs python -m pytest`: -Invoking ``pytest`` versus ``python -m pytest`` +Invoking ``python -m pytest`` versus ``pytest`` ----------------------------------------------- Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly From a9608d54e0c8db747abe02644415d95fbd75e441 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 26 Dec 2019 08:19:11 -0300 Subject: [PATCH 30/35] Switch the order of the commands back and update the text As suggested during review --- doc/en/pythonpath.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/en/pythonpath.rst b/doc/en/pythonpath.rst index 65f7b51c8..f2c86fab9 100644 --- a/doc/en/pythonpath.rst +++ b/doc/en/pythonpath.rst @@ -74,9 +74,11 @@ This is also discussed in details in :ref:`test discovery`. .. _`pytest vs python -m pytest`: -Invoking ``python -m pytest`` versus ``pytest`` +Invoking ``pytest`` versus ``python -m pytest`` ----------------------------------------------- -Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly -equivalent behaviour, except that the former call will add the current directory to ``sys.path``. +Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly +equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which +is standard ``python`` behavior. + See also :ref:`cmdline`. From a5224f74904bd39a7c0e25ee75936734a6cbfe15 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 30 Dec 2019 15:28:37 +0100 Subject: [PATCH 31/35] Revert black formatting of essential_plugins Done in a02310a140 (likely automatic), but loses information of the comment obviously. --- src/_pytest/config/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index eba0906b5..fb965b261 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -133,7 +133,13 @@ def directory_arg(path, optname): # Plugins that cannot be disabled via "-p no:X" currently. -essential_plugins = ("mark", "main", "runner", "fixtures", "helpconfig") # Provides -p. +essential_plugins = ( + "mark", + "main", + "runner", + "fixtures", + "helpconfig", # Provides -p. +) default_plugins = essential_plugins + ( "python", From 4848bbdf9a4480ec85b520c6f3224256f1346679 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 1 Jan 2020 14:49:59 +0200 Subject: [PATCH 32/35] Update mypy 0.750 -> 0.761 This fixes some type: ignores due to typeshed update. Newer mypy seem to ignore unannotated functions better, so add a few minor annotations so that existing correct type:ignores make sense. --- .pre-commit-config.yaml | 2 +- src/_pytest/_code/source.py | 4 +--- src/_pytest/assertion/__init__.py | 2 +- src/_pytest/doctest.py | 4 +++- src/_pytest/pytester.py | 2 +- src/_pytest/recwarn.py | 4 +--- src/_pytest/reports.py | 2 +- src/_pytest/runner.py | 2 +- testing/code/test_source.py | 8 ++------ 9 files changed, 12 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64f3f32ac..b2368cf8f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: - id: pyupgrade args: [--py3-plus] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.750 + rev: v0.761 hooks: - id: mypy files: ^(src/|testing/) diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index ac3ee231e..d7cef683d 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -339,9 +339,7 @@ def getstatementrange_ast( block_finder.started = source.lines[start][0].isspace() it = ((x + "\n") for x in source.lines[start:end]) try: - # Type ignored until next mypy release. - # https://github.com/python/typeshed/commit/c0d46a20353b733befb85d8b9cc24e5b0bcd8f9a - for tok in tokenize.generate_tokens(lambda: next(it)): # type: ignore + for tok in tokenize.generate_tokens(lambda: next(it)): block_finder.tokeneater(*tok) except (inspect.EndOfBlock, IndentationError): end = block_finder.last + start diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 34d6701ed..f96afce6d 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -33,7 +33,7 @@ def pytest_addoption(parser): ) -def register_assert_rewrite(*names): +def register_assert_rewrite(*names) -> None: """Register one or more module names to be rewritten on import. This function will make sure that this module or all modules inside diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 75eac7db6..3475ac9c2 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -451,7 +451,9 @@ class DoctestModule(pytest.Module): obj = getattr(obj, "fget", obj) return doctest.DocTestFinder._find_lineno(self, obj, source_lines) - def _find(self, tests, obj, name, module, source_lines, globs, seen): + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: if _is_mocked(obj): return with _patch_unwrap_mock_aware(): diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index d5744167c..605dd5eb7 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -829,7 +829,7 @@ class Testdir: items = [x.item for x in rec.getcalls("pytest_itemcollected")] return items, rec - def inline_run(self, *args, plugins=(), no_reraise_ctrlc=False): + def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): """Run ``pytest.main()`` in-process, returning a HookRecorder. Runs the :py:func:`pytest.main` function to run all of pytest inside diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index e3d7b72ec..5cf32c894 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -129,9 +129,7 @@ def warns( # noqa: F811 return func(*args[1:], **kwargs) -# Type ignored until next mypy release. Regression fixed by: -# https://github.com/python/typeshed/commit/41bf6a19822d6694973449d795f8bfe1d50d5a03 -class WarningsRecorder(warnings.catch_warnings): # type: ignore +class WarningsRecorder(warnings.catch_warnings): """A context manager to record raised warnings. Adapted from `warnings.catch_warnings`. diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 5d445c2f8..95236450d 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -259,7 +259,7 @@ class TestReport(BaseReport): ) @classmethod - def from_item_and_call(cls, item, call): + def from_item_and_call(cls, item, call) -> "TestReport": """ Factory method to create and fill a TestReport with standard item and call info. """ diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 67e28e905..50e4d4307 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -250,7 +250,7 @@ def pytest_runtest_makereport(item, call): return TestReport.from_item_and_call(item, call) -def pytest_make_collect_report(collector): +def pytest_make_collect_report(collector) -> CollectReport: call = CallInfo.from_call(lambda: list(collector.collect()), "collect") longrepr = None if not call.excinfo: diff --git a/testing/code/test_source.py b/testing/code/test_source.py index bf52dccd7..1390d8b0a 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -318,16 +318,12 @@ class TestSourceParsingAndCompiling: @pytest.mark.parametrize("name", ["", None, "my"]) def test_compilefuncs_and_path_sanity(self, name: Optional[str]) -> None: - def check(comp, name): + def check(comp, name) -> None: co = comp(self.source, name) if not name: expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) # type: ignore else: - expected = "codegen %r %s:%d>" % ( - name, - mypath, # type: ignore - mylineno + 2 + 2, # type: ignore - ) # type: ignore + expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2) # type: ignore fn = co.co_filename assert fn.endswith(expected) From c627ac4e599464e9ce2d1b33d1139b9dcf89468a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 5 Jan 2020 12:33:12 -0300 Subject: [PATCH 33/35] Remove unused _pytest.code.Source.isparseable function Besides unused, it uses the (deprecated in Python 3.9) parser module Fix #6404 --- changelog/6404.trivial.rst | 1 + src/_pytest/_code/source.py | 20 -------------------- testing/code/test_source.py | 10 ---------- 3 files changed, 1 insertion(+), 30 deletions(-) create mode 100644 changelog/6404.trivial.rst diff --git a/changelog/6404.trivial.rst b/changelog/6404.trivial.rst new file mode 100644 index 000000000..5a60d01a4 --- /dev/null +++ b/changelog/6404.trivial.rst @@ -0,0 +1 @@ +Removed unused ``_pytest.code.Source.isparseable`` function. diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index d7cef683d..341462792 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -136,26 +136,6 @@ class Source: newsource.lines[:] = deindent(self.lines) return newsource - def isparseable(self, deindent: bool = True) -> bool: - """ return True if source is parseable, heuristically - deindenting it by default. - """ - from parser import suite as syntax_checker - - if deindent: - source = str(self.deindent()) - else: - source = str(self) - try: - # compile(source+'\n', "x", "exec") - syntax_checker(source + "\n") - except KeyboardInterrupt: - raise - except Exception: - return False - else: - return True - def __str__(self) -> str: return "\n".join(self.lines) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 1390d8b0a..511626fa0 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -121,15 +121,6 @@ def test_syntaxerror_rerepresentation() -> None: assert ex.value.text == "xyz xyz\n" -def test_isparseable() -> None: - assert Source("hello").isparseable() - assert Source("if 1:\n pass").isparseable() - assert Source(" \nif 1:\n pass").isparseable() - assert not Source("if 1:\n").isparseable() - assert not Source(" \nif 1:\npass").isparseable() - assert not Source(chr(0)).isparseable() - - class TestAccesses: def setup_class(self) -> None: self.source = Source( @@ -143,7 +134,6 @@ class TestAccesses: def test_getrange(self) -> None: x = self.source[0:2] - assert x.isparseable() assert len(x.lines) == 2 assert str(x) == "def f(x):\n pass" From 12f74a28fada7badd7d7830611ba14b8a40e1dd1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 5 Jan 2020 14:12:40 -0300 Subject: [PATCH 34/35] Revert "Remove unused _pytest.code.Source.isparseable function" This reverts commit c627ac4e599464e9ce2d1b33d1139b9dcf89468a. --- changelog/6404.trivial.rst | 1 - src/_pytest/_code/source.py | 20 ++++++++++++++++++++ testing/code/test_source.py | 10 ++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) delete mode 100644 changelog/6404.trivial.rst diff --git a/changelog/6404.trivial.rst b/changelog/6404.trivial.rst deleted file mode 100644 index 5a60d01a4..000000000 --- a/changelog/6404.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Removed unused ``_pytest.code.Source.isparseable`` function. diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 341462792..d7cef683d 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -136,6 +136,26 @@ class Source: newsource.lines[:] = deindent(self.lines) return newsource + def isparseable(self, deindent: bool = True) -> bool: + """ return True if source is parseable, heuristically + deindenting it by default. + """ + from parser import suite as syntax_checker + + if deindent: + source = str(self.deindent()) + else: + source = str(self) + try: + # compile(source+'\n', "x", "exec") + syntax_checker(source + "\n") + except KeyboardInterrupt: + raise + except Exception: + return False + else: + return True + def __str__(self) -> str: return "\n".join(self.lines) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 511626fa0..1390d8b0a 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -121,6 +121,15 @@ def test_syntaxerror_rerepresentation() -> None: assert ex.value.text == "xyz xyz\n" +def test_isparseable() -> None: + assert Source("hello").isparseable() + assert Source("if 1:\n pass").isparseable() + assert Source(" \nif 1:\n pass").isparseable() + assert not Source("if 1:\n").isparseable() + assert not Source(" \nif 1:\npass").isparseable() + assert not Source(chr(0)).isparseable() + + class TestAccesses: def setup_class(self) -> None: self.source = Source( @@ -134,6 +143,7 @@ class TestAccesses: def test_getrange(self) -> None: x = self.source[0:2] + assert x.isparseable() assert len(x.lines) == 2 assert str(x) == "def f(x):\n pass" From 91a96ec3d68f81372dc8279b8d8e4d8a02679709 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 5 Jan 2020 14:15:51 -0300 Subject: [PATCH 35/35] Remove usage of parser module, deprecated in Python 3.9 Fix #6404 --- changelog/6404.trivial.rst | 1 + src/_pytest/_code/source.py | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 changelog/6404.trivial.rst diff --git a/changelog/6404.trivial.rst b/changelog/6404.trivial.rst new file mode 100644 index 000000000..8252098b6 --- /dev/null +++ b/changelog/6404.trivial.rst @@ -0,0 +1 @@ +Remove usage of ``parser`` module, deprecated in Python 3.9. diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index d7cef683d..2b9c2a6ea 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -140,18 +140,13 @@ class Source: """ return True if source is parseable, heuristically deindenting it by default. """ - from parser import suite as syntax_checker - if deindent: source = str(self.deindent()) else: source = str(self) try: - # compile(source+'\n', "x", "exec") - syntax_checker(source + "\n") - except KeyboardInterrupt: - raise - except Exception: + ast.parse(source) + except (SyntaxError, ValueError, TypeError): return False else: return True