bpo-32089: Fix warnings filters in dev mode (#4482)
The developer mode (-X dev) now creates all default warnings filters
to order filters in the correct order to always show ResourceWarning
and make BytesWarning depend on the -b option.
Write a functional test to make sure that ResourceWarning is logged
twice at the same location in the developer mode.
Add a new 'dev_mode' field to _PyCoreConfig.
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 97b4493..35bfddd 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -262,15 +262,11 @@
args.append('-' + opt * v)
# -W options
- warnoptions = sys.warnoptions
- xoptions = getattr(sys, '_xoptions', {})
- if 'dev' in xoptions and warnoptions and warnoptions[-1] == 'default':
- # special case: -X dev adds 'default' to sys.warnoptions
- warnoptions = warnoptions[:-1]
- for opt in warnoptions:
+ for opt in sys.warnoptions:
args.append('-W' + opt)
# -X options
+ xoptions = getattr(sys, '_xoptions', {})
if 'dev' in xoptions:
args.extend(('-X', 'dev'))
for opt in ('faulthandler', 'tracemalloc', 'importtime',
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index 3dbe75f..75f7d00 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -507,14 +507,14 @@
with self.subTest(envar_value=value):
assert_python_ok('-c', code, **env_vars)
- def run_xdev(self, code, check_exitcode=True):
+ def run_xdev(self, *args, check_exitcode=True):
env = dict(os.environ)
env.pop('PYTHONWARNINGS', None)
# Force malloc() to disable the debug hooks which are enabled
# by default for Python compiled in debug mode
env['PYTHONMALLOC'] = 'malloc'
- args = (sys.executable, '-X', 'dev', '-c', code)
+ args = (sys.executable, '-X', 'dev', *args)
proc = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
@@ -525,8 +525,34 @@
return proc.stdout.rstrip()
def test_xdev(self):
- out = self.run_xdev("import sys; print(sys.warnoptions)")
- self.assertEqual(out, "['default']")
+ code = ("import sys, warnings; "
+ "print(' '.join('%s::%s' % (f[0], f[2].__name__) "
+ "for f in warnings.filters))")
+
+ out = self.run_xdev("-c", code)
+ self.assertEqual(out,
+ "ignore::BytesWarning "
+ "always::ResourceWarning "
+ "default::Warning")
+
+ out = self.run_xdev("-b", "-c", code)
+ self.assertEqual(out,
+ "default::BytesWarning "
+ "always::ResourceWarning "
+ "default::Warning")
+
+ out = self.run_xdev("-bb", "-c", code)
+ self.assertEqual(out,
+ "error::BytesWarning "
+ "always::ResourceWarning "
+ "default::Warning")
+
+ out = self.run_xdev("-Werror", "-c", code)
+ self.assertEqual(out,
+ "error::Warning "
+ "ignore::BytesWarning "
+ "always::ResourceWarning "
+ "default::Warning")
try:
import _testcapi
@@ -535,7 +561,7 @@
else:
code = "import _testcapi; _testcapi.pymem_api_misuse()"
with support.SuppressCrashReport():
- out = self.run_xdev(code, check_exitcode=False)
+ out = self.run_xdev("-c", code, check_exitcode=False)
self.assertIn("Debug memory block at address p=", out)
try:
@@ -544,9 +570,23 @@
pass
else:
code = "import faulthandler; print(faulthandler.is_enabled())"
- out = self.run_xdev(code)
+ out = self.run_xdev("-c", code)
self.assertEqual(out, "True")
+ # Make sure that ResourceWarning emitted twice at the same line number
+ # is logged twice
+ filename = support.TESTFN
+ self.addCleanup(support.unlink, filename)
+ with open(filename, "w", encoding="utf8") as fp:
+ print("def func(): open(__file__)", file=fp)
+ print("func()", file=fp)
+ print("func()", file=fp)
+ fp.flush()
+
+ out = self.run_xdev(filename)
+ self.assertEqual(out.count(':1: ResourceWarning: '), 2, out)
+
+
class IgnoreEnvironmentTest(unittest.TestCase):
def run_ignoring_vars(self, predicate, **env_vars):
diff --git a/Lib/warnings.py b/Lib/warnings.py
index 48d5e16..b2605f8 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -486,7 +486,6 @@
# - a compiled regex that must match the module that is being warned
# - a line number for the line being warning, or 0 to mean any line
# If either if the compiled regexs are None, match anything.
-_warnings_defaults = False
try:
from _warnings import (filters, _defaultaction, _onceregistry,
warn, warn_explicit, _filters_mutated)
@@ -504,12 +503,16 @@
global _filters_version
_filters_version += 1
+ _warnings_defaults = False
+
# Module initialization
_processoptions(sys.warnoptions)
if not _warnings_defaults:
+ dev_mode = ('dev' in getattr(sys, '_xoptions', {}))
py_debug = hasattr(sys, 'gettotalrefcount')
- if not py_debug:
+
+ if not(dev_mode or py_debug):
silence = [ImportWarning, PendingDeprecationWarning]
silence.append(DeprecationWarning)
for cls in silence:
@@ -525,10 +528,15 @@
simplefilter(bytes_action, category=BytesWarning, append=1)
# resource usage warnings are enabled by default in pydebug mode
- if py_debug:
+ if dev_mode or py_debug:
resource_action = "always"
else:
resource_action = "ignore"
simplefilter(resource_action, category=ResourceWarning, append=1)
+ if dev_mode:
+ simplefilter("default", category=Warning, append=1)
+
+ del py_debug, dev_mode
+
del _warnings_defaults