From a368ab2b2249b5d03a145f1d8b70e5636fc6eca8 Mon Sep 17 00:00:00 2001 From: Miro HronĨok Date: May 20 2021 14:06:27 +0000 Subject: Python 3.10 backports Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1948522 --- diff --git a/8227.patch b/8227.patch new file mode 100644 index 0000000..0c7462d --- /dev/null +++ b/8227.patch @@ -0,0 +1,76 @@ +From 78fb97105f38dc286353bbc331a243b6e753fe3c Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 6 Jan 2021 13:33:33 +0100 +Subject: [PATCH 1/2] Make code.FormattedExcinfo.get_source more defensive + +When line_index was a large negative number, get_source failed +on `source.lines[line_index]`. +Use the same dummy Source as with a large positive line_index. +--- + src/_pytest/_code/code.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py +index b852175606..af3bdf0561 100644 +--- a/src/_pytest/_code/code.py ++++ b/src/_pytest/_code/code.py +@@ -721,11 +721,11 @@ def get_source( + ) -> List[str]: + """Return formatted and marked up source lines.""" + lines = [] +- if source is None or line_index >= len(source.lines): ++ if source is not None and line_index < 0: ++ line_index += len(source.lines) ++ if source is None or line_index >= len(source.lines) or line_index < 0: + source = Source("???") + line_index = 0 +- if line_index < 0: +- line_index += len(source) + space_prefix = " " + if short: + lines.append(space_prefix + source.lines[line_index].strip()) + +From 0a75c8e57b4b87ee533f3b08612590704b6743bb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= +Date: Thu, 22 Apr 2021 19:13:44 +0200 +Subject: [PATCH 2/2] Add a regression test for a more defensive + code.FormattedExcinfo.get_source + +--- + testing/code/test_excinfo.py | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py +index e6a9cbaf73..287733dac7 100644 +--- a/testing/code/test_excinfo.py ++++ b/testing/code/test_excinfo.py +@@ -1397,6 +1397,29 @@ def test(tmp_path): + result.stderr.no_fnmatch_line("*INTERNALERROR*") + + ++def test_regression_nagative_line_index(pytester): ++ """ ++ With Python 3.10 alphas, there was an INTERNALERROR reported in ++ https://github.com/pytest-dev/pytest/pull/8227 ++ This test ensures it does not regress. ++ """ ++ pytester.makepyfile( ++ """ ++ import ast ++ import pytest ++ ++ ++ def test_literal_eval(): ++ with pytest.raises(ValueError, match="^$"): ++ ast.literal_eval("pytest") ++ """ ++ ) ++ result = pytester.runpytest() ++ result.stdout.fnmatch_lines(["* 1 failed in *"]) ++ result.stdout.no_fnmatch_line("*INTERNALERROR*") ++ result.stderr.no_fnmatch_line("*INTERNALERROR*") ++ ++ + @pytest.mark.usefixtures("limited_recursion_depth") + def test_exception_repr_extraction_error_on_recursion(): + """ diff --git a/8555.patch b/8555.patch new file mode 100644 index 0000000..7c8e9ea --- /dev/null +++ b/8555.patch @@ -0,0 +1,436 @@ +From 20738461aa93c54fe6e161439d888240a35f5913 Mon Sep 17 00:00:00 2001 +From: Florian Bruhin +Date: Wed, 14 Apr 2021 11:31:28 +0200 +Subject: [PATCH] Ignore various warnings from Python 3.10 + +https://github.com/benjaminp/six/issues/341 +https://github.com/benjaminp/six/pull/352 +https://github.com/pypa/setuptools/pull/2517 +--- + pyproject.toml | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/pyproject.toml b/pyproject.toml +index dd4be6c..6fc4184 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -40,6 +40,14 @@ filterwarnings = [ + "default:invalid escape sequence:DeprecationWarning", + # ignore use of unregistered marks, because we use many to test the implementation + "ignore::_pytest.warning_types.PytestUnknownMarkWarning", ++ # https://github.com/benjaminp/six/issues/341 ++ "ignore:_SixMetaPathImporter\\.exec_module\\(\\) not found; falling back to load_module\\(\\):ImportWarning", ++ # https://github.com/benjaminp/six/pull/352 ++ "ignore:_SixMetaPathImporter\\.find_spec\\(\\) not found; falling back to find_module\\(\\):ImportWarning", ++ # https://github.com/pypa/setuptools/pull/2517 ++ "ignore:VendorImporter\\.find_spec\\(\\) not found; falling back to find_module\\(\\):ImportWarning", ++ # https://github.com/pytest-dev/execnet/pull/127 ++ "ignore:isSet\\(\\) is deprecated, use is_set\\(\\) instead:DeprecationWarning", + ] + pytester_example_dir = "testing/example_scripts" + markers = [ + + +From b4d2330fd4c8a9661d981f9a63b3d029bbf674d9 Mon Sep 17 00:00:00 2001 +From: Florian Bruhin +Date: Wed, 14 Apr 2021 11:37:34 +0200 +Subject: [PATCH] Adjust enum reprs for Python 3.10 + +Potential fix for #8546 +--- + testing/python/metafunc.py | 5 ++++- + testing/test_pytester.py | 14 ++++++++++---- + 2 files changed, 14 insertions(+), 5 deletions(-) + +diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py +index 676f1d9..21ab226 100644 +--- a/testing/python/metafunc.py ++++ b/testing/python/metafunc.py +@@ -448,7 +448,10 @@ class TestMetafunc: + enum = pytest.importorskip("enum") + e = enum.Enum("Foo", "one, two") + result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) +- assert result == ["Foo.one-Foo.two"] ++ if sys.version_info[:2] >= (3, 10): ++ assert result == ["one-two"] ++ else: ++ assert result == ["Foo.one-Foo.two"] + + def test_idmaker_idfn(self) -> None: + """#351""" +diff --git a/testing/test_pytester.py b/testing/test_pytester.py +index f2e8dd5..4234cdf 100644 +--- a/testing/test_pytester.py ++++ b/testing/test_pytester.py +@@ -741,10 +741,16 @@ def test_run_result_repr() -> None: + + # known exit code + r = pytester.RunResult(1, outlines, errlines, duration=0.5) +- assert ( +- repr(r) == "" +- ) ++ if sys.version_info[:2] >= (3, 10): ++ assert repr(r) == ( ++ "" ++ ) ++ else: ++ assert repr(r) == ( ++ "" ++ ) + + # unknown exit code: just the number + r = pytester.RunResult(99, outlines, errlines, duration=0.5) + + +From 99dedde9c9e77e454e28f7811fcc27018d74686c Mon Sep 17 00:00:00 2001 +From: Florian Bruhin +Date: Tue, 4 May 2021 14:27:21 +0200 +Subject: [PATCH] Fix warning filters used in tests + +--- + testing/acceptance_test.py | 4 ++-- + testing/python/collect.py | 2 +- + testing/test_config.py | 2 +- + testing/test_terminal.py | 4 ++-- + testing/test_threadexception.py | 6 +++--- + testing/test_unraisableexception.py | 6 +++--- + testing/test_warnings.py | 28 ++++++++++++++-------------- + 7 files changed, 26 insertions(+), 26 deletions(-) + +diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py +index b7ec18a..9196336 100644 +--- a/testing/acceptance_test.py ++++ b/testing/acceptance_test.py +@@ -1173,7 +1173,7 @@ def test_usage_error_code(pytester: Pytester) -> None: + assert result.ret == ExitCode.USAGE_ERROR + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") + def test_warn_on_async_function(pytester: Pytester) -> None: + # In the below we .close() the coroutine only to avoid + # "RuntimeWarning: coroutine 'test_2' was never awaited" +@@ -1206,7 +1206,7 @@ def test_warn_on_async_function(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") + def test_warn_on_async_gen_function(pytester: Pytester) -> None: + pytester.makepyfile( + test_async=""" +diff --git a/testing/python/collect.py b/testing/python/collect.py +index 4d5f4c6..c9d3dfc 100644 +--- a/testing/python/collect.py ++++ b/testing/python/collect.py +@@ -1210,7 +1210,7 @@ def test_unorderable_types(testdir): + assert result.ret == ExitCode.NO_TESTS_COLLECTED + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestCollectionWarning") + def test_dont_collect_non_function_callable(testdir): + """Test for issue https://github.com/pytest-dev/pytest/issues/331 + +diff --git a/testing/test_config.py b/testing/test_config.py +index b931797..881023a 100644 +--- a/testing/test_config.py ++++ b/testing/test_config.py +@@ -290,7 +290,7 @@ class TestParseIni: + result = pytester.runpytest() + result.stdout.no_fnmatch_line("*PytestConfigWarning*") + +- @pytest.mark.filterwarnings("default") ++ @pytest.mark.filterwarnings("default::pytest.PytestConfigWarning") + def test_disable_warnings_plugin_disables_config_warnings( + self, pytester: Pytester + ) -> None: +diff --git a/testing/test_terminal.py b/testing/test_terminal.py +index 5e833f4..fad6611 100644 +--- a/testing/test_terminal.py ++++ b/testing/test_terminal.py +@@ -1618,7 +1618,7 @@ def test_terminal_summary(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::UserWarning") + def test_terminal_summary_warnings_are_displayed(pytester: Pytester) -> None: + """Test that warnings emitted during pytest_terminal_summary are displayed. + (#1305). +@@ -1655,7 +1655,7 @@ def test_terminal_summary_warnings_are_displayed(pytester: Pytester) -> None: + assert stdout.count("=== warnings summary ") == 2 + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::UserWarning") + def test_terminal_summary_warnings_header_once(pytester: Pytester) -> None: + pytester.makepyfile( + """ +diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py +index 399692b..5b7519f 100644 +--- a/testing/test_threadexception.py ++++ b/testing/test_threadexception.py +@@ -8,7 +8,7 @@ if sys.version_info < (3, 8): + pytest.skip("threadexception plugin needs Python>=3.8", allow_module_level=True) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnhandledThreadExceptionWarning") + def test_unhandled_thread_exception(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +@@ -42,7 +42,7 @@ def test_unhandled_thread_exception(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnhandledThreadExceptionWarning") + def test_unhandled_thread_exception_in_setup(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +@@ -78,7 +78,7 @@ def test_unhandled_thread_exception_in_setup(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnhandledThreadExceptionWarning") + def test_unhandled_thread_exception_in_teardown(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +diff --git a/testing/test_unraisableexception.py b/testing/test_unraisableexception.py +index 32f8903..f625833 100644 +--- a/testing/test_unraisableexception.py ++++ b/testing/test_unraisableexception.py +@@ -8,7 +8,7 @@ if sys.version_info < (3, 8): + pytest.skip("unraisableexception plugin needs Python>=3.8", allow_module_level=True) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") + def test_unraisable(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +@@ -40,7 +40,7 @@ def test_unraisable(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") + def test_unraisable_in_setup(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +@@ -76,7 +76,7 @@ def test_unraisable_in_setup(pytester: Pytester) -> None: + ) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning") + def test_unraisable_in_teardown(pytester: Pytester) -> None: + pytester.makepyfile( + test_it=""" +diff --git a/testing/test_warnings.py b/testing/test_warnings.py +index 6689804..76c5730 100644 +--- a/testing/test_warnings.py ++++ b/testing/test_warnings.py +@@ -38,7 +38,7 @@ def pyfile_with_warnings(testdir: Testdir, request: FixtureRequest) -> str: + return str(test_file) + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::UserWarning", "default::RuntimeWarning") + def test_normal_flow(testdir, pyfile_with_warnings): + """Check that the warnings section is displayed.""" + result = testdir.runpytest(pyfile_with_warnings) +@@ -55,7 +55,7 @@ def test_normal_flow(testdir, pyfile_with_warnings): + ) + + +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_setup_teardown_warnings(testdir): + testdir.makepyfile( + """ +@@ -123,7 +123,7 @@ def test_ignore(testdir, pyfile_with_warnings, method): + assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() + + +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_unicode(testdir): + testdir.makepyfile( + """ +@@ -202,7 +202,9 @@ def test_filterwarnings_mark(testdir, default_config): + warnings.warn(RuntimeWarning()) + """ + ) +- result = testdir.runpytest("-W always" if default_config == "cmdline" else "") ++ result = testdir.runpytest( ++ "-W always::RuntimeWarning" if default_config == "cmdline" else "" ++ ) + result.stdout.fnmatch_lines(["*= 1 failed, 2 passed, 1 warning in *"]) + + +@@ -217,7 +219,7 @@ def test_non_string_warning_argument(testdir): + warnings.warn(UserWarning(1, 'foo')) + """ + ) +- result = testdir.runpytest("-W", "always") ++ result = testdir.runpytest("-W", "always::UserWarning") + result.stdout.fnmatch_lines(["*= 1 passed, 1 warning in *"]) + + +@@ -236,7 +238,7 @@ def test_filterwarnings_mark_registration(testdir): + assert result.ret == 0 + + +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_warning_captured_hook(testdir): + testdir.makeconftest( + """ +@@ -297,7 +299,7 @@ def test_warning_captured_hook(testdir): + assert collected_result[3] is None, str(collected) + + +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_collection_warnings(testdir): + """Check that we also capture warnings issued during test collection (#3251).""" + testdir.makepyfile( +@@ -321,7 +323,7 @@ def test_collection_warnings(testdir): + ) + + +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_mark_regex_escape(testdir): + """@pytest.mark.filterwarnings should not try to escape regex characters (#3936)""" + testdir.makepyfile( +@@ -337,7 +339,7 @@ def test_mark_regex_escape(testdir): + assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() + + +-@pytest.mark.filterwarnings("default") ++@pytest.mark.filterwarnings("default::pytest.PytestWarning") + @pytest.mark.parametrize("ignore_pytest_warnings", ["no", "ini", "cmdline"]) + def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings): + """Make sure we can ignore internal pytest warnings using a warnings filter.""" +@@ -383,7 +385,7 @@ def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline): + testdir.makeini( + """ + [pytest] +- filterwarnings = error ++ filterwarnings = error::UserWarning + """ + ) + testdir.makepyfile( +@@ -577,8 +579,7 @@ def test_warnings_checker_twice(): + warnings.warn("Message B", UserWarning) + + +-@pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_group_warnings_by_message(testdir): + testdir.copy_example("warnings/test_group_warnings_by_message.py") + result = testdir.runpytest() +@@ -609,8 +610,7 @@ def test_group_warnings_by_message(testdir): + ) + + +-@pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") +-@pytest.mark.filterwarnings("always") ++@pytest.mark.filterwarnings("always::UserWarning") + def test_group_warnings_by_message_summary(testdir): + testdir.copy_example("warnings/test_group_warnings_by_message_summary") + testdir.syspathinsert() + + +From d7eb2dd40bf8f01452a483114097921e3cfb73b0 Mon Sep 17 00:00:00 2001 +From: Florian Bruhin +Date: Tue, 4 May 2021 14:45:10 +0200 +Subject: [PATCH] Fix test_collect_symlink_dir on Windows + +--- + testing/test_collection.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/testing/test_collection.py b/testing/test_collection.py +index 1138c2b..610180a 100644 +--- a/testing/test_collection.py ++++ b/testing/test_collection.py +@@ -1212,7 +1212,7 @@ def test_collect_symlink_dir(pytester: Pytester) -> None: + """A symlinked directory is collected.""" + dir = pytester.mkdir("dir") + dir.joinpath("test_it.py").write_text("def test_it(): pass", "utf-8") +- pytester.path.joinpath("symlink_dir").symlink_to(dir) ++ symlink_or_skip(pytester.path.joinpath("symlink_dir"), dir) + result = pytester.runpytest() + result.assert_outcomes(passed=2) + + + +From 077f54f27a6261d46e617c3d317274437130883f Mon Sep 17 00:00:00 2001 +From: Florian Bruhin +Date: Tue, 4 May 2021 17:18:05 +0200 +Subject: [PATCH] Fix test_errors_in_xfail_skip_expressions on Python 3.10 + +--- + testing/test_skipping.py | 33 +++++++++++++++++++++++---------- + 1 file changed, 23 insertions(+), 10 deletions(-) + +diff --git a/testing/test_skipping.py b/testing/test_skipping.py +index fc66eb1..3cb8bdf 100644 +--- a/testing/test_skipping.py ++++ b/testing/test_skipping.py +@@ -1126,21 +1126,34 @@ def test_errors_in_xfail_skip_expressions(pytester: Pytester) -> None: + pypy_version_info = getattr(sys, "pypy_version_info", None) + if pypy_version_info is not None and pypy_version_info < (6,): + markline = markline[5:] ++ elif sys.version_info[:2] >= (3, 10): ++ markline = markline[11:] + elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): + markline = markline[4:] +- result.stdout.fnmatch_lines( +- [ ++ ++ if sys.version_info[:2] >= (3, 10): ++ expected = [ + "*ERROR*test_nameerror*", +- "*evaluating*skipif*condition*", + "*asd*", +- "*ERROR*test_syntax*", +- "*evaluating*xfail*condition*", +- " syntax error", +- markline, +- "SyntaxError: invalid syntax", +- "*1 pass*2 errors*", ++ "", ++ "During handling of the above exception, another exception occurred:", + ] +- ) ++ else: ++ expected = [ ++ "*ERROR*test_nameerror*", ++ ] ++ ++ expected += [ ++ "*evaluating*skipif*condition*", ++ "*asd*", ++ "*ERROR*test_syntax*", ++ "*evaluating*xfail*condition*", ++ " syntax error", ++ markline, ++ "SyntaxError: invalid syntax", ++ "*1 pass*2 errors*", ++ ] ++ result.stdout.fnmatch_lines(expected) + + + def test_xfail_skipif_with_globals(pytester: Pytester) -> None: + + diff --git a/8664.patch b/8664.patch new file mode 100644 index 0000000..4fc5581 --- /dev/null +++ b/8664.patch @@ -0,0 +1,27 @@ +From d9635d560d30fd732c878a9f5fbddd85d4345a48 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= +Date: Wed, 12 May 2021 13:30:46 +0200 +Subject: [PATCH] Ignore DeprecationWarnings in test_trial_error + +Fixes https://github.com/pytest-dev/pytest/issues/8663 +--- + testing/test_unittest.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/testing/test_unittest.py b/testing/test_unittest.py +index 8b00cb8..de8fc5c 100644 +--- a/testing/test_unittest.py ++++ b/testing/test_unittest.py +@@ -533,7 +533,9 @@ class TestTrialUnittest: + # will crash both at test time and at teardown + """ + ) +- result = testdir.runpytest("-vv", "-oconsole_output_style=classic") ++ result = testdir.runpytest( ++ "-vv", "-oconsole_output_style=classic", "-W", "ignore::DeprecationWarning" ++ ) + result.stdout.fnmatch_lines( + [ + "test_trial_error.py::TC::test_four FAILED", + + diff --git a/pytest.spec b/pytest.spec index 4260747..4ca3eaf 100644 --- a/pytest.spec +++ b/pytest.spec @@ -6,6 +6,21 @@ License: MIT URL: https://pytest.org Source0: %{pypi_source} +# Make code.FormattedExcinfo.get_source more defensive +# Merged upstream, https://github.com/pytest-dev/pytest/pull/8227 +# Rebased slightly +Patch1: 8227.patch + +# Fix Python 3.10 test issues +# Merged upstream, https://github.com/pytest-dev/pytest/pull/8555 +# Rebased slightly +Patch2: 8555.patch + +# Ignore DeprecationWarnings in test_trial_error +# Merged upstream, https://github.com/pytest-dev/pytest/pull/8664 +# Rebased slightly +Patch3: 8664.patch + # When building pytest for the first time with new Python version # we might not yet have all the BRs, those conditionals allow us to do that.