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