[Python-checkins] bpo-32089: Fix warnings filters in dev mode (#4482)
Victor Stinner
webhook-mailer at python.org
Mon Nov 20 20:32:42 EST 2017
https://github.com/python/cpython/commit/09f3a8a1249308a104a89041d82fe99e6c087043
commit: 09f3a8a1249308a104a89041d82fe99e6c087043
branch: master
author: Victor Stinner <victor.stinner at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-11-20T17:32:40-08:00
summary:
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.
files:
M Doc/using/cmdline.rst
M Include/pystate.h
M Lib/subprocess.py
M Lib/test/test_cmd_line.py
M Lib/warnings.py
M Modules/main.c
M Python/_warnings.c
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
index b2269302cb4..8e186b3366d 100644
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -412,13 +412,13 @@ Miscellaneous options
application. Typical usage is ``python3 -X importtime -c 'import
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
* ``-X dev`` enables the "developer mode": enable debug checks at runtime.
- In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3
- -W default -X faulthandler ...``, except that the :envvar:`PYTHONMALLOC`
- and :envvar:`PYTHONASYNCIODEBUG` environment variables are not set in
- practice. Developer mode:
+ Developer mode:
- * Add ``default`` warnings option. For example, display
- :exc:`DeprecationWarning` and :exc:`ResourceWarning` warnings.
+ * Warning filters: add a filter to display all warnings (``"default"``
+ action), except of :exc:`BytesWarning` which still depends on the
+ :option:`-b` option, and use ``"always"`` action for
+ :exc:`ResourceWarning` warnings. For example, display
+ :exc:`DeprecationWarning` warnings.
* Install debug hooks on memory allocators: see the
:c:func:`PyMem_SetupDebugHooks` C function.
* Enable the :mod:`faulthandler` module to dump the Python traceback
diff --git a/Include/pystate.h b/Include/pystate.h
index 440122588f4..2081ff58cc3 100644
--- a/Include/pystate.h
+++ b/Include/pystate.h
@@ -33,6 +33,7 @@ typedef struct {
int faulthandler;
int tracemalloc; /* Number of saved frames, 0=don't trace */
int importtime; /* -X importtime */
+ int dev_mode; /* -X dev */
} _PyCoreConfig;
#define _PyCoreConfig_INIT \
@@ -43,7 +44,8 @@ typedef struct {
.allocator = NULL, \
.faulthandler = 0, \
.tracemalloc = 0, \
- .importtime = 0}
+ .importtime = 0, \
+ .dev_mode = 0}
/* Placeholders while working on the new configuration API
*
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 97b449365ef..35bfddde4e9 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -262,15 +262,11 @@ def _args_from_interpreter_flags():
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 3dbe75f0a09..75f7d00b24a 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -507,14 +507,14 @@ def test_sys_flags_set(self):
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 @@ def run_xdev(self, code, check_exitcode=True):
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 @@ def test_xdev(self):
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 @@ def test_xdev(self):
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 48d5e163d13..b2605f84aec 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -486,7 +486,6 @@ def __exit__(self, *exc_info):
# - 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 @@ def _filters_mutated():
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 @@ def _filters_mutated():
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
diff --git a/Modules/main.c b/Modules/main.c
index 203abf414cc..70c1c3d9657 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -1397,14 +1397,9 @@ pymain_parse_envvars(_PyMain *pymain)
return -1;
}
if (pymain_get_xoption(pymain, L"dev")) {
- /* "python3 -X dev ..." behaves
- as "PYTHONMALLOC=debug python3 -Wd -X faulthandler ..." */
- core_config->allocator = "debug";
- if (pymain_optlist_append(pymain, &pymain->cmdline.warning_options,
- L"default") < 0) {
- return -1;
- }
+ core_config->dev_mode = 1;
core_config->faulthandler = 1;
+ core_config->allocator = "debug";
}
return 0;
}
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 8cfae76a50f..f2110edc52d 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -1196,11 +1196,19 @@ create_filter(PyObject *category, const char *action)
static PyObject *
init_filters(void)
{
+ PyInterpreterState *interp = PyThreadState_GET()->interp;
+ int dev_mode = interp->core_config.dev_mode;
+
+ Py_ssize_t count = 2;
+ if (dev_mode) {
+ count++;
+ }
#ifndef Py_DEBUG
- PyObject *filters = PyList_New(5);
-#else
- PyObject *filters = PyList_New(2);
+ if (!dev_mode) {
+ count += 3;
+ }
#endif
+ PyObject *filters = PyList_New(count);
unsigned int pos = 0; /* Post-incremented in each use. */
unsigned int x;
const char *bytes_action, *resource_action;
@@ -1209,12 +1217,14 @@ init_filters(void)
return NULL;
#ifndef Py_DEBUG
- PyList_SET_ITEM(filters, pos++,
- create_filter(PyExc_DeprecationWarning, "ignore"));
- PyList_SET_ITEM(filters, pos++,
- create_filter(PyExc_PendingDeprecationWarning, "ignore"));
- PyList_SET_ITEM(filters, pos++,
- create_filter(PyExc_ImportWarning, "ignore"));
+ if (!dev_mode) {
+ PyList_SET_ITEM(filters, pos++,
+ create_filter(PyExc_DeprecationWarning, "ignore"));
+ PyList_SET_ITEM(filters, pos++,
+ create_filter(PyExc_PendingDeprecationWarning, "ignore"));
+ PyList_SET_ITEM(filters, pos++,
+ create_filter(PyExc_ImportWarning, "ignore"));
+ }
#endif
if (Py_BytesWarningFlag > 1)
@@ -1225,14 +1235,21 @@ init_filters(void)
bytes_action = "ignore";
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning,
bytes_action));
+
/* resource usage warnings are enabled by default in pydebug mode */
#ifdef Py_DEBUG
resource_action = "always";
#else
- resource_action = "ignore";
+ resource_action = (dev_mode ? "always" : "ignore");
#endif
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_ResourceWarning,
resource_action));
+
+ if (dev_mode) {
+ PyList_SET_ITEM(filters, pos++,
+ create_filter(PyExc_Warning, "default"));
+ }
+
for (x = 0; x < pos; x += 1) {
if (PyList_GET_ITEM(filters, x) == NULL) {
Py_DECREF(filters);
More information about the Python-checkins
mailing list