Skip to content

Commit 09f3a8a

Browse files
authored
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.
1 parent f39b674 commit 09f3a8a

File tree

7 files changed

+97
-39
lines changed

7 files changed

+97
-39
lines changed

Doc/using/cmdline.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,13 @@ Miscellaneous options
412412
application. Typical usage is ``python3 -X importtime -c 'import
413413
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
414414
* ``-X dev`` enables the "developer mode": enable debug checks at runtime.
415-
In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3
416-
-W default -X faulthandler ...``, except that the :envvar:`PYTHONMALLOC`
417-
and :envvar:`PYTHONASYNCIODEBUG` environment variables are not set in
418-
practice. Developer mode:
415+
Developer mode:
419416

420-
* Add ``default`` warnings option. For example, display
421-
:exc:`DeprecationWarning` and :exc:`ResourceWarning` warnings.
417+
* Warning filters: add a filter to display all warnings (``"default"``
418+
action), except of :exc:`BytesWarning` which still depends on the
419+
:option:`-b` option, and use ``"always"`` action for
420+
:exc:`ResourceWarning` warnings. For example, display
421+
:exc:`DeprecationWarning` warnings.
422422
* Install debug hooks on memory allocators: see the
423423
:c:func:`PyMem_SetupDebugHooks` C function.
424424
* Enable the :mod:`faulthandler` module to dump the Python traceback

Include/pystate.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ typedef struct {
3333
int faulthandler;
3434
int tracemalloc; /* Number of saved frames, 0=don't trace */
3535
int importtime; /* -X importtime */
36+
int dev_mode; /* -X dev */
3637
} _PyCoreConfig;
3738

3839
#define _PyCoreConfig_INIT \
@@ -43,7 +44,8 @@ typedef struct {
4344
.allocator = NULL, \
4445
.faulthandler = 0, \
4546
.tracemalloc = 0, \
46-
.importtime = 0}
47+
.importtime = 0, \
48+
.dev_mode = 0}
4749

4850
/* Placeholders while working on the new configuration API
4951
*

Lib/subprocess.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,11 @@ def _args_from_interpreter_flags():
262262
args.append('-' + opt * v)
263263

264264
# -W options
265-
warnoptions = sys.warnoptions
266-
xoptions = getattr(sys, '_xoptions', {})
267-
if 'dev' in xoptions and warnoptions and warnoptions[-1] == 'default':
268-
# special case: -X dev adds 'default' to sys.warnoptions
269-
warnoptions = warnoptions[:-1]
270-
for opt in warnoptions:
265+
for opt in sys.warnoptions:
271266
args.append('-W' + opt)
272267

273268
# -X options
269+
xoptions = getattr(sys, '_xoptions', {})
274270
if 'dev' in xoptions:
275271
args.extend(('-X', 'dev'))
276272
for opt in ('faulthandler', 'tracemalloc', 'importtime',

Lib/test/test_cmd_line.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,14 @@ def test_sys_flags_set(self):
507507
with self.subTest(envar_value=value):
508508
assert_python_ok('-c', code, **env_vars)
509509

510-
def run_xdev(self, code, check_exitcode=True):
510+
def run_xdev(self, *args, check_exitcode=True):
511511
env = dict(os.environ)
512512
env.pop('PYTHONWARNINGS', None)
513513
# Force malloc() to disable the debug hooks which are enabled
514514
# by default for Python compiled in debug mode
515515
env['PYTHONMALLOC'] = 'malloc'
516516

517-
args = (sys.executable, '-X', 'dev', '-c', code)
517+
args = (sys.executable, '-X', 'dev', *args)
518518
proc = subprocess.run(args,
519519
stdout=subprocess.PIPE,
520520
stderr=subprocess.STDOUT,
@@ -525,8 +525,34 @@ def run_xdev(self, code, check_exitcode=True):
525525
return proc.stdout.rstrip()
526526

527527
def test_xdev(self):
528-
out = self.run_xdev("import sys; print(sys.warnoptions)")
529-
self.assertEqual(out, "['default']")
528+
code = ("import sys, warnings; "
529+
"print(' '.join('%s::%s' % (f[0], f[2].__name__) "
530+
"for f in warnings.filters))")
531+
532+
out = self.run_xdev("-c", code)
533+
self.assertEqual(out,
534+
"ignore::BytesWarning "
535+
"always::ResourceWarning "
536+
"default::Warning")
537+
538+
out = self.run_xdev("-b", "-c", code)
539+
self.assertEqual(out,
540+
"default::BytesWarning "
541+
"always::ResourceWarning "
542+
"default::Warning")
543+
544+
out = self.run_xdev("-bb", "-c", code)
545+
self.assertEqual(out,
546+
"error::BytesWarning "
547+
"always::ResourceWarning "
548+
"default::Warning")
549+
550+
out = self.run_xdev("-Werror", "-c", code)
551+
self.assertEqual(out,
552+
"error::Warning "
553+
"ignore::BytesWarning "
554+
"always::ResourceWarning "
555+
"default::Warning")
530556

531557
try:
532558
import _testcapi
@@ -535,7 +561,7 @@ def test_xdev(self):
535561
else:
536562
code = "import _testcapi; _testcapi.pymem_api_misuse()"
537563
with support.SuppressCrashReport():
538-
out = self.run_xdev(code, check_exitcode=False)
564+
out = self.run_xdev("-c", code, check_exitcode=False)
539565
self.assertIn("Debug memory block at address p=", out)
540566

541567
try:
@@ -544,9 +570,23 @@ def test_xdev(self):
544570
pass
545571
else:
546572
code = "import faulthandler; print(faulthandler.is_enabled())"
547-
out = self.run_xdev(code)
573+
out = self.run_xdev("-c", code)
548574
self.assertEqual(out, "True")
549575

576+
# Make sure that ResourceWarning emitted twice at the same line number
577+
# is logged twice
578+
filename = support.TESTFN
579+
self.addCleanup(support.unlink, filename)
580+
with open(filename, "w", encoding="utf8") as fp:
581+
print("def func(): open(__file__)", file=fp)
582+
print("func()", file=fp)
583+
print("func()", file=fp)
584+
fp.flush()
585+
586+
out = self.run_xdev(filename)
587+
self.assertEqual(out.count(':1: ResourceWarning: '), 2, out)
588+
589+
550590
class IgnoreEnvironmentTest(unittest.TestCase):
551591

552592
def run_ignoring_vars(self, predicate, **env_vars):

Lib/warnings.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,6 @@ def __exit__(self, *exc_info):
486486
# - a compiled regex that must match the module that is being warned
487487
# - a line number for the line being warning, or 0 to mean any line
488488
# If either if the compiled regexs are None, match anything.
489-
_warnings_defaults = False
490489
try:
491490
from _warnings import (filters, _defaultaction, _onceregistry,
492491
warn, warn_explicit, _filters_mutated)
@@ -504,12 +503,16 @@ def _filters_mutated():
504503
global _filters_version
505504
_filters_version += 1
506505

506+
_warnings_defaults = False
507+
507508

508509
# Module initialization
509510
_processoptions(sys.warnoptions)
510511
if not _warnings_defaults:
512+
dev_mode = ('dev' in getattr(sys, '_xoptions', {}))
511513
py_debug = hasattr(sys, 'gettotalrefcount')
512-
if not py_debug:
514+
515+
if not(dev_mode or py_debug):
513516
silence = [ImportWarning, PendingDeprecationWarning]
514517
silence.append(DeprecationWarning)
515518
for cls in silence:
@@ -525,10 +528,15 @@ def _filters_mutated():
525528
simplefilter(bytes_action, category=BytesWarning, append=1)
526529

527530
# resource usage warnings are enabled by default in pydebug mode
528-
if py_debug:
531+
if dev_mode or py_debug:
529532
resource_action = "always"
530533
else:
531534
resource_action = "ignore"
532535
simplefilter(resource_action, category=ResourceWarning, append=1)
533536

537+
if dev_mode:
538+
simplefilter("default", category=Warning, append=1)
539+
540+
del py_debug, dev_mode
541+
534542
del _warnings_defaults

Modules/main.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,14 +1397,9 @@ pymain_parse_envvars(_PyMain *pymain)
13971397
return -1;
13981398
}
13991399
if (pymain_get_xoption(pymain, L"dev")) {
1400-
/* "python3 -X dev ..." behaves
1401-
as "PYTHONMALLOC=debug python3 -Wd -X faulthandler ..." */
1402-
core_config->allocator = "debug";
1403-
if (pymain_optlist_append(pymain, &pymain->cmdline.warning_options,
1404-
L"default") < 0) {
1405-
return -1;
1406-
}
1400+
core_config->dev_mode = 1;
14071401
core_config->faulthandler = 1;
1402+
core_config->allocator = "debug";
14081403
}
14091404
return 0;
14101405
}

Python/_warnings.c

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,11 +1196,19 @@ create_filter(PyObject *category, const char *action)
11961196
static PyObject *
11971197
init_filters(void)
11981198
{
1199+
PyInterpreterState *interp = PyThreadState_GET()->interp;
1200+
int dev_mode = interp->core_config.dev_mode;
1201+
1202+
Py_ssize_t count = 2;
1203+
if (dev_mode) {
1204+
count++;
1205+
}
11991206
#ifndef Py_DEBUG
1200-
PyObject *filters = PyList_New(5);
1201-
#else
1202-
PyObject *filters = PyList_New(2);
1207+
if (!dev_mode) {
1208+
count += 3;
1209+
}
12031210
#endif
1211+
PyObject *filters = PyList_New(count);
12041212
unsigned int pos = 0; /* Post-incremented in each use. */
12051213
unsigned int x;
12061214
const char *bytes_action, *resource_action;
@@ -1209,12 +1217,14 @@ init_filters(void)
12091217
return NULL;
12101218

12111219
#ifndef Py_DEBUG
1212-
PyList_SET_ITEM(filters, pos++,
1213-
create_filter(PyExc_DeprecationWarning, "ignore"));
1214-
PyList_SET_ITEM(filters, pos++,
1215-
create_filter(PyExc_PendingDeprecationWarning, "ignore"));
1216-
PyList_SET_ITEM(filters, pos++,
1217-
create_filter(PyExc_ImportWarning, "ignore"));
1220+
if (!dev_mode) {
1221+
PyList_SET_ITEM(filters, pos++,
1222+
create_filter(PyExc_DeprecationWarning, "ignore"));
1223+
PyList_SET_ITEM(filters, pos++,
1224+
create_filter(PyExc_PendingDeprecationWarning, "ignore"));
1225+
PyList_SET_ITEM(filters, pos++,
1226+
create_filter(PyExc_ImportWarning, "ignore"));
1227+
}
12181228
#endif
12191229

12201230
if (Py_BytesWarningFlag > 1)
@@ -1225,14 +1235,21 @@ init_filters(void)
12251235
bytes_action = "ignore";
12261236
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning,
12271237
bytes_action));
1238+
12281239
/* resource usage warnings are enabled by default in pydebug mode */
12291240
#ifdef Py_DEBUG
12301241
resource_action = "always";
12311242
#else
1232-
resource_action = "ignore";
1243+
resource_action = (dev_mode ? "always" : "ignore");
12331244
#endif
12341245
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_ResourceWarning,
12351246
resource_action));
1247+
1248+
if (dev_mode) {
1249+
PyList_SET_ITEM(filters, pos++,
1250+
create_filter(PyExc_Warning, "default"));
1251+
}
1252+
12361253
for (x = 0; x < pos; x += 1) {
12371254
if (PyList_GET_ITEM(filters, x) == NULL) {
12381255
Py_DECREF(filters);

0 commit comments

Comments
 (0)