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.
This commit is contained in:
parent
f39b674876
commit
09f3a8a124
|
@ -412,13 +412,13 @@ Miscellaneous options
|
||||||
application. Typical usage is ``python3 -X importtime -c 'import
|
application. Typical usage is ``python3 -X importtime -c 'import
|
||||||
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
|
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
|
||||||
* ``-X dev`` enables the "developer mode": enable debug checks at runtime.
|
* ``-X dev`` enables the "developer mode": enable debug checks at runtime.
|
||||||
In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3
|
Developer mode:
|
||||||
-W default -X faulthandler ...``, except that the :envvar:`PYTHONMALLOC`
|
|
||||||
and :envvar:`PYTHONASYNCIODEBUG` environment variables are not set in
|
|
||||||
practice. Developer mode:
|
|
||||||
|
|
||||||
* Add ``default`` warnings option. For example, display
|
* Warning filters: add a filter to display all warnings (``"default"``
|
||||||
:exc:`DeprecationWarning` and :exc:`ResourceWarning` warnings.
|
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
|
* Install debug hooks on memory allocators: see the
|
||||||
:c:func:`PyMem_SetupDebugHooks` C function.
|
:c:func:`PyMem_SetupDebugHooks` C function.
|
||||||
* Enable the :mod:`faulthandler` module to dump the Python traceback
|
* Enable the :mod:`faulthandler` module to dump the Python traceback
|
||||||
|
|
|
@ -33,6 +33,7 @@ typedef struct {
|
||||||
int faulthandler;
|
int faulthandler;
|
||||||
int tracemalloc; /* Number of saved frames, 0=don't trace */
|
int tracemalloc; /* Number of saved frames, 0=don't trace */
|
||||||
int importtime; /* -X importtime */
|
int importtime; /* -X importtime */
|
||||||
|
int dev_mode; /* -X dev */
|
||||||
} _PyCoreConfig;
|
} _PyCoreConfig;
|
||||||
|
|
||||||
#define _PyCoreConfig_INIT \
|
#define _PyCoreConfig_INIT \
|
||||||
|
@ -43,7 +44,8 @@ typedef struct {
|
||||||
.allocator = NULL, \
|
.allocator = NULL, \
|
||||||
.faulthandler = 0, \
|
.faulthandler = 0, \
|
||||||
.tracemalloc = 0, \
|
.tracemalloc = 0, \
|
||||||
.importtime = 0}
|
.importtime = 0, \
|
||||||
|
.dev_mode = 0}
|
||||||
|
|
||||||
/* Placeholders while working on the new configuration API
|
/* Placeholders while working on the new configuration API
|
||||||
*
|
*
|
||||||
|
|
|
@ -262,15 +262,11 @@ def _args_from_interpreter_flags():
|
||||||
args.append('-' + opt * v)
|
args.append('-' + opt * v)
|
||||||
|
|
||||||
# -W options
|
# -W options
|
||||||
warnoptions = sys.warnoptions
|
for opt in 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:
|
|
||||||
args.append('-W' + opt)
|
args.append('-W' + opt)
|
||||||
|
|
||||||
# -X options
|
# -X options
|
||||||
|
xoptions = getattr(sys, '_xoptions', {})
|
||||||
if 'dev' in xoptions:
|
if 'dev' in xoptions:
|
||||||
args.extend(('-X', 'dev'))
|
args.extend(('-X', 'dev'))
|
||||||
for opt in ('faulthandler', 'tracemalloc', 'importtime',
|
for opt in ('faulthandler', 'tracemalloc', 'importtime',
|
||||||
|
|
|
@ -507,14 +507,14 @@ class CmdLineTest(unittest.TestCase):
|
||||||
with self.subTest(envar_value=value):
|
with self.subTest(envar_value=value):
|
||||||
assert_python_ok('-c', code, **env_vars)
|
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 = dict(os.environ)
|
||||||
env.pop('PYTHONWARNINGS', None)
|
env.pop('PYTHONWARNINGS', None)
|
||||||
# Force malloc() to disable the debug hooks which are enabled
|
# Force malloc() to disable the debug hooks which are enabled
|
||||||
# by default for Python compiled in debug mode
|
# by default for Python compiled in debug mode
|
||||||
env['PYTHONMALLOC'] = 'malloc'
|
env['PYTHONMALLOC'] = 'malloc'
|
||||||
|
|
||||||
args = (sys.executable, '-X', 'dev', '-c', code)
|
args = (sys.executable, '-X', 'dev', *args)
|
||||||
proc = subprocess.run(args,
|
proc = subprocess.run(args,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
|
@ -525,8 +525,34 @@ class CmdLineTest(unittest.TestCase):
|
||||||
return proc.stdout.rstrip()
|
return proc.stdout.rstrip()
|
||||||
|
|
||||||
def test_xdev(self):
|
def test_xdev(self):
|
||||||
out = self.run_xdev("import sys; print(sys.warnoptions)")
|
code = ("import sys, warnings; "
|
||||||
self.assertEqual(out, "['default']")
|
"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:
|
try:
|
||||||
import _testcapi
|
import _testcapi
|
||||||
|
@ -535,7 +561,7 @@ class CmdLineTest(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
code = "import _testcapi; _testcapi.pymem_api_misuse()"
|
code = "import _testcapi; _testcapi.pymem_api_misuse()"
|
||||||
with support.SuppressCrashReport():
|
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)
|
self.assertIn("Debug memory block at address p=", out)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -544,9 +570,23 @@ class CmdLineTest(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
code = "import faulthandler; print(faulthandler.is_enabled())"
|
code = "import faulthandler; print(faulthandler.is_enabled())"
|
||||||
out = self.run_xdev(code)
|
out = self.run_xdev("-c", code)
|
||||||
self.assertEqual(out, "True")
|
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):
|
class IgnoreEnvironmentTest(unittest.TestCase):
|
||||||
|
|
||||||
def run_ignoring_vars(self, predicate, **env_vars):
|
def run_ignoring_vars(self, predicate, **env_vars):
|
||||||
|
|
|
@ -486,7 +486,6 @@ class catch_warnings(object):
|
||||||
# - a compiled regex that must match the module that is being warned
|
# - 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
|
# - a line number for the line being warning, or 0 to mean any line
|
||||||
# If either if the compiled regexs are None, match anything.
|
# If either if the compiled regexs are None, match anything.
|
||||||
_warnings_defaults = False
|
|
||||||
try:
|
try:
|
||||||
from _warnings import (filters, _defaultaction, _onceregistry,
|
from _warnings import (filters, _defaultaction, _onceregistry,
|
||||||
warn, warn_explicit, _filters_mutated)
|
warn, warn_explicit, _filters_mutated)
|
||||||
|
@ -504,12 +503,16 @@ except ImportError:
|
||||||
global _filters_version
|
global _filters_version
|
||||||
_filters_version += 1
|
_filters_version += 1
|
||||||
|
|
||||||
|
_warnings_defaults = False
|
||||||
|
|
||||||
|
|
||||||
# Module initialization
|
# Module initialization
|
||||||
_processoptions(sys.warnoptions)
|
_processoptions(sys.warnoptions)
|
||||||
if not _warnings_defaults:
|
if not _warnings_defaults:
|
||||||
|
dev_mode = ('dev' in getattr(sys, '_xoptions', {}))
|
||||||
py_debug = hasattr(sys, 'gettotalrefcount')
|
py_debug = hasattr(sys, 'gettotalrefcount')
|
||||||
if not py_debug:
|
|
||||||
|
if not(dev_mode or py_debug):
|
||||||
silence = [ImportWarning, PendingDeprecationWarning]
|
silence = [ImportWarning, PendingDeprecationWarning]
|
||||||
silence.append(DeprecationWarning)
|
silence.append(DeprecationWarning)
|
||||||
for cls in silence:
|
for cls in silence:
|
||||||
|
@ -525,10 +528,15 @@ if not _warnings_defaults:
|
||||||
simplefilter(bytes_action, category=BytesWarning, append=1)
|
simplefilter(bytes_action, category=BytesWarning, append=1)
|
||||||
|
|
||||||
# resource usage warnings are enabled by default in pydebug mode
|
# resource usage warnings are enabled by default in pydebug mode
|
||||||
if py_debug:
|
if dev_mode or py_debug:
|
||||||
resource_action = "always"
|
resource_action = "always"
|
||||||
else:
|
else:
|
||||||
resource_action = "ignore"
|
resource_action = "ignore"
|
||||||
simplefilter(resource_action, category=ResourceWarning, append=1)
|
simplefilter(resource_action, category=ResourceWarning, append=1)
|
||||||
|
|
||||||
|
if dev_mode:
|
||||||
|
simplefilter("default", category=Warning, append=1)
|
||||||
|
|
||||||
|
del py_debug, dev_mode
|
||||||
|
|
||||||
del _warnings_defaults
|
del _warnings_defaults
|
||||||
|
|
|
@ -1397,14 +1397,9 @@ pymain_parse_envvars(_PyMain *pymain)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (pymain_get_xoption(pymain, L"dev")) {
|
if (pymain_get_xoption(pymain, L"dev")) {
|
||||||
/* "python3 -X dev ..." behaves
|
core_config->dev_mode = 1;
|
||||||
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->faulthandler = 1;
|
core_config->faulthandler = 1;
|
||||||
|
core_config->allocator = "debug";
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1196,11 +1196,19 @@ create_filter(PyObject *category, const char *action)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
init_filters(void)
|
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
|
#ifndef Py_DEBUG
|
||||||
PyObject *filters = PyList_New(5);
|
if (!dev_mode) {
|
||||||
#else
|
count += 3;
|
||||||
PyObject *filters = PyList_New(2);
|
}
|
||||||
#endif
|
#endif
|
||||||
|
PyObject *filters = PyList_New(count);
|
||||||
unsigned int pos = 0; /* Post-incremented in each use. */
|
unsigned int pos = 0; /* Post-incremented in each use. */
|
||||||
unsigned int x;
|
unsigned int x;
|
||||||
const char *bytes_action, *resource_action;
|
const char *bytes_action, *resource_action;
|
||||||
|
@ -1209,12 +1217,14 @@ init_filters(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#ifndef Py_DEBUG
|
#ifndef Py_DEBUG
|
||||||
PyList_SET_ITEM(filters, pos++,
|
if (!dev_mode) {
|
||||||
create_filter(PyExc_DeprecationWarning, "ignore"));
|
PyList_SET_ITEM(filters, pos++,
|
||||||
PyList_SET_ITEM(filters, pos++,
|
create_filter(PyExc_DeprecationWarning, "ignore"));
|
||||||
create_filter(PyExc_PendingDeprecationWarning, "ignore"));
|
PyList_SET_ITEM(filters, pos++,
|
||||||
PyList_SET_ITEM(filters, pos++,
|
create_filter(PyExc_PendingDeprecationWarning, "ignore"));
|
||||||
create_filter(PyExc_ImportWarning, "ignore"));
|
PyList_SET_ITEM(filters, pos++,
|
||||||
|
create_filter(PyExc_ImportWarning, "ignore"));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (Py_BytesWarningFlag > 1)
|
if (Py_BytesWarningFlag > 1)
|
||||||
|
@ -1225,14 +1235,21 @@ init_filters(void)
|
||||||
bytes_action = "ignore";
|
bytes_action = "ignore";
|
||||||
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning,
|
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning,
|
||||||
bytes_action));
|
bytes_action));
|
||||||
|
|
||||||
/* resource usage warnings are enabled by default in pydebug mode */
|
/* resource usage warnings are enabled by default in pydebug mode */
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
resource_action = "always";
|
resource_action = "always";
|
||||||
#else
|
#else
|
||||||
resource_action = "ignore";
|
resource_action = (dev_mode ? "always" : "ignore");
|
||||||
#endif
|
#endif
|
||||||
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_ResourceWarning,
|
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_ResourceWarning,
|
||||||
resource_action));
|
resource_action));
|
||||||
|
|
||||||
|
if (dev_mode) {
|
||||||
|
PyList_SET_ITEM(filters, pos++,
|
||||||
|
create_filter(PyExc_Warning, "default"));
|
||||||
|
}
|
||||||
|
|
||||||
for (x = 0; x < pos; x += 1) {
|
for (x = 0; x < pos; x += 1) {
|
||||||
if (PyList_GET_ITEM(filters, x) == NULL) {
|
if (PyList_GET_ITEM(filters, x) == NULL) {
|
||||||
Py_DECREF(filters);
|
Py_DECREF(filters);
|
||||||
|
|
Loading…
Reference in New Issue