From ac89d6532a8c1f652f6a68c0b9caad80cde0042f Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sat, 20 Jun 2020 12:15:58 -0400 Subject: [PATCH 1/6] replace stderr warnings with the warnings module --- changelog/7295.trivial.rst | 1 + src/_pytest/config/__init__.py | 48 ++++++++++++++++++++-------------- testing/test_assertion.py | 14 ++++++++-- testing/test_config.py | 16 +++++++----- 4 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 changelog/7295.trivial.rst diff --git a/changelog/7295.trivial.rst b/changelog/7295.trivial.rst new file mode 100644 index 000000000..113a7ee60 --- /dev/null +++ b/changelog/7295.trivial.rst @@ -0,0 +1 @@ +``src/_pytest/config/__init__.py`` now uses the ``warnings`` module to report warnings instead of ``sys.stderr.write``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 31b73a2c9..45e0c05ea 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -992,7 +992,7 @@ class Config: mode = "plain" else: self._mark_plugins_for_rewrite(hook) - _warn_about_missing_assertion(mode) + self._warn_about_missing_assertion(mode) def _mark_plugins_for_rewrite(self, hook) -> None: """ @@ -1136,7 +1136,12 @@ class Config: def _warn_or_fail_if_strict(self, message: str) -> None: if self.known_args_namespace.strict_config: fail(message, pytrace=False) - sys.stderr.write("WARNING: {}".format(message)) + + from _pytest.warnings import _issue_warning_captured + + _issue_warning_captured( + PytestConfigWarning(message), self.hook, stacklevel=2, + ) def _get_unknown_ini_keys(self) -> List[str]: parser_inicfg = self._parser._inidict @@ -1303,6 +1308,27 @@ class Config: """ (deprecated, use getoption(skip=True)) """ return self.getoption(name, skip=True) + def _warn_about_missing_assertion(self, mode: str) -> None: + if not _assertion_supported(): + from _pytest.warnings import _issue_warning_captured + + warning_text = ( + "assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n" + ) + if mode == "plain": + warning_text = ( + "ASSERTIONS ARE NOT EXECUTED" + " and FAILING TESTS WILL PASS. Are you" + " using python -O?" + ) + _issue_warning_captured( + PytestConfigWarning(warning_text), self.hook, stacklevel=2, + ) + def _assertion_supported(): try: @@ -1313,24 +1339,6 @@ def _assertion_supported(): return False -def _warn_about_missing_assertion(mode): - if not _assertion_supported(): - if mode == "plain": - sys.stderr.write( - "WARNING: ASSERTIONS ARE NOT EXECUTED" - " and FAILING TESTS WILL PASS. Are you" - " using python -O?" - ) - else: - sys.stderr.write( - "WARNING: assertions not in test modules or" - " plugins will be ignored" - " because assert statements are not executed " - "by the underlying Python interpreter " - "(are you using python -O?)\n" - ) - - def create_terminal_writer(config: Config, *args, **kwargs) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. Every code which requires a TerminalWriter object diff --git a/testing/test_assertion.py b/testing/test_assertion.py index ae5e75dbf..5dbae96a0 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1385,11 +1385,21 @@ def test_exception_handling_no_traceback(testdir): @pytest.mark.skipif("'__pypy__' in sys.builtin_module_names") def test_warn_missing(testdir): + testdir.makepyfile("") + + warning_output = [ + "warning :*PytestConfigWarning:*assert statements are not executed*" + ] result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h") - result.stderr.fnmatch_lines(["*WARNING*assert statements are not executed*"]) + result.stdout.fnmatch_lines(warning_output) + + warning_output = [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*assert statements are not executed*", + ] result = testdir.run(sys.executable, "-OO", "-m", "pytest") - result.stderr.fnmatch_lines(["*WARNING*assert statements are not executed*"]) + result.stdout.fnmatch_lines(warning_output) def test_recursion_source_decode(testdir): diff --git a/testing/test_config.py b/testing/test_config.py index c9eea7a16..930366232 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -163,7 +163,7 @@ class TestParseIni: assert result.ret == 0 @pytest.mark.parametrize( - "ini_file_text, invalid_keys, stderr_output, exception_text", + "ini_file_text, invalid_keys, warning_output, exception_text", [ ( """ @@ -173,8 +173,9 @@ class TestParseIni: """, ["unknown_ini", "another_unknown_ini"], [ - "WARNING: Unknown config ini key: another_unknown_ini", - "WARNING: Unknown config ini key: unknown_ini", + "=*= warnings summary =*=", + "*PytestConfigWarning:*Unknown config ini key: another_unknown_ini", + "*PytestConfigWarning:*Unknown config ini key: unknown_ini", ], "Unknown config ini key: another_unknown_ini", ), @@ -185,7 +186,10 @@ class TestParseIni: minversion = 5.0.0 """, ["unknown_ini"], - ["WARNING: Unknown config ini key: unknown_ini"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*Unknown config ini key: unknown_ini", + ], "Unknown config ini key: unknown_ini", ), ( @@ -220,7 +224,7 @@ class TestParseIni: ], ) def test_invalid_ini_keys( - self, testdir, ini_file_text, invalid_keys, stderr_output, exception_text + self, testdir, ini_file_text, invalid_keys, warning_output, exception_text ): testdir.makeconftest( """ @@ -234,7 +238,7 @@ class TestParseIni: assert sorted(config._get_unknown_ini_keys()) == sorted(invalid_keys) result = testdir.runpytest() - result.stderr.fnmatch_lines(stderr_output) + result.stdout.fnmatch_lines(warning_output) if exception_text: with pytest.raises(pytest.fail.Exception, match=exception_text): From a9d50aeab671bd67d58abb19a1839736cb4a7966 Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sat, 20 Jun 2020 12:18:55 -0400 Subject: [PATCH 2/6] remove extra whitespace --- testing/test_assertion.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 5dbae96a0..f28a51f96 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1385,7 +1385,6 @@ def test_exception_handling_no_traceback(testdir): @pytest.mark.skipif("'__pypy__' in sys.builtin_module_names") def test_warn_missing(testdir): - testdir.makepyfile("") warning_output = [ From fe68c5869866149b1c00d6280f3e491883d0b7e9 Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sat, 20 Jun 2020 13:06:41 -0400 Subject: [PATCH 3/6] add test_warn_missing case for --assert=plain --- testing/test_assertion.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index f28a51f96..64a94941a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1400,6 +1400,13 @@ def test_warn_missing(testdir): result = testdir.run(sys.executable, "-OO", "-m", "pytest") result.stdout.fnmatch_lines(warning_output) + warning_output = [ + "=*= warnings summary =*=", + "*PytestConfigWarning: ASSERTIONS ARE NOT EXECUTED and FAILING TESTS WILL PASS. Are you using python -O?", + ] + result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--assert=plain") + result.stdout.fnmatch_lines(warning_output) + def test_recursion_source_decode(testdir): testdir.makepyfile( From 33de350619cf541476d1ab987377f9bc2f06179f Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sun, 21 Jun 2020 10:26:36 -0400 Subject: [PATCH 4/6] parametrize test_warn_missing for a cleaner test --- testing/test_assertion.py | 45 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 64a94941a..5c9bb35fa 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1384,27 +1384,34 @@ def test_exception_handling_no_traceback(testdir): @pytest.mark.skipif("'__pypy__' in sys.builtin_module_names") -def test_warn_missing(testdir): +@pytest.mark.parametrize( + "cmdline_args, warning_output", + [ + ( + ["-OO", "-m", "pytest", "-h"], + ["warning :*PytestConfigWarning:*assert statements are not executed*"], + ), + ( + ["-OO", "-m", "pytest"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*assert statements are not executed*", + ], + ), + ( + ["-OO", "-m", "pytest", "--assert=plain"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning: ASSERTIONS ARE NOT EXECUTED and FAILING TESTS WILL PASS. " + "Are you using python -O?", + ], + ), + ], +) +def test_warn_missing(testdir, cmdline_args, warning_output): testdir.makepyfile("") - warning_output = [ - "warning :*PytestConfigWarning:*assert statements are not executed*" - ] - result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h") - result.stdout.fnmatch_lines(warning_output) - - warning_output = [ - "=*= warnings summary =*=", - "*PytestConfigWarning:*assert statements are not executed*", - ] - result = testdir.run(sys.executable, "-OO", "-m", "pytest") - result.stdout.fnmatch_lines(warning_output) - - warning_output = [ - "=*= warnings summary =*=", - "*PytestConfigWarning: ASSERTIONS ARE NOT EXECUTED and FAILING TESTS WILL PASS. Are you using python -O?", - ] - result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--assert=plain") + result = testdir.run(sys.executable, *cmdline_args) result.stdout.fnmatch_lines(warning_output) From c39655725a086b07432995149e43f1884ab1d754 Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sat, 27 Jun 2020 19:49:19 -0400 Subject: [PATCH 5/6] change if else structure of _warn_bout_missing_assertion --- src/_pytest/config/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 98c5fd0b4..9ed372959 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1360,19 +1360,20 @@ class Config: if not _assertion_supported(): from _pytest.warnings import _issue_warning_captured - warning_text = ( - "assertions not in test modules or" - " plugins will be ignored" - " because assert statements are not executed " - "by the underlying Python interpreter " - "(are you using python -O?)\n" - ) if mode == "plain": warning_text = ( "ASSERTIONS ARE NOT EXECUTED" " and FAILING TESTS WILL PASS. Are you" " using python -O?" ) + else: + warning_text = ( + "assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n" + ) _issue_warning_captured( PytestConfigWarning(warning_text), self.hook, stacklevel=2, ) From 49ec2aed0f326fc5fa25dbdd7c47bf0a888dbd97 Mon Sep 17 00:00:00 2001 From: Gleb Nikonorov Date: Sun, 28 Jun 2020 10:48:33 -0400 Subject: [PATCH 6/6] change stacklevel in warnings from 2 to 3 --- src/_pytest/config/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 9ed372959..b5cff7301 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1186,7 +1186,7 @@ class Config: from _pytest.warnings import _issue_warning_captured _issue_warning_captured( - PytestConfigWarning(message), self.hook, stacklevel=2, + PytestConfigWarning(message), self.hook, stacklevel=3, ) def _get_unknown_ini_keys(self) -> List[str]: @@ -1375,7 +1375,7 @@ class Config: "(are you using python -O?)\n" ) _issue_warning_captured( - PytestConfigWarning(warning_text), self.hook, stacklevel=2, + PytestConfigWarning(warning_text), self.hook, stacklevel=3, )