[3.7] bpo-35233: Rewrite test_embed.InitConfigTests (GH-10524) (GH-10529)

* Add C functions:

  * _Py_GetGlobalVariablesAsDict()
  * _PyCoreConfig_AsDict()
  * _PyMainInterpreterConfig_AsDict()

* Add new _testcapi methods:

  * get_global_config()
  * get_core_config()
  * get_main_config()

* test.pythoninfo: get global, core and main configuration
* _testembed now serializes global, core and main configurations
  using JSON to reuse _Py_GetGlobalVariablesAsDict(),
  _PyCoreConfig_AsDict() and _PyMainInterpreterConfig_AsDict(),
  rather than duplicating code.
* test_embed.InitConfigTests now test much more configuration
  variables
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index 4a563e9..9257fdf 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -142,7 +142,7 @@
     info_add('platform.python_implementation',
              platform.python_implementation())
     info_add('platform.platform',
-             platform.platform(aliased=True, terse=True))
+             platform.platform(aliased=True))
 
 
 def collect_locale(info_add):
@@ -525,6 +525,33 @@
     info_add('CC.version', text)
 
 
+def collect_gdbm(info_add):
+    try:
+        from _gdbm import _GDBM_VERSION
+    except ImportError:
+        return
+
+    info_add('gdbm.GDBM_VERSION', '.'.join(map(str, _GDBM_VERSION)))
+
+
+def collect_get_config(info_add):
+    # Dump global configuration variables, _PyCoreConfig
+    # and _PyMainInterpreterConfig
+    try:
+        from _testcapi import get_global_config, get_core_config, get_main_config
+    except ImportError:
+        return
+
+    for prefix, get_config_func in (
+        ('global_config', get_global_config),
+        ('core_config', get_core_config),
+        ('main_config', get_main_config),
+    ):
+        config = get_config_func()
+        for key in sorted(config):
+            info_add('%s[%s]' % (prefix, key), repr(config[key]))
+
+
 def collect_info(info):
     error = False
     info_add = info.add
@@ -552,6 +579,8 @@
         collect_testcapi,
         collect_resource,
         collect_cc,
+        collect_gdbm,
+        collect_get_config,
 
         # Collecting from tests should be last as they have side effects.
         collect_test_socket,
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 2927458..62a20ab 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -3,6 +3,7 @@
 import unittest
 
 from collections import namedtuple
+import json
 import os
 import re
 import subprocess
@@ -251,12 +252,52 @@
 
 class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
     maxDiff = 4096
-    DEFAULT_CONFIG = {
+    UTF8_MODE_ERRORS = ('surrogatepass' if sys.platform == 'win32'
+                        else 'surrogateescape')
+    # FIXME: untested core configuration variables
+    UNTESTED_CORE_CONFIG = (
+        'base_exec_prefix',
+        'base_prefix',
+        'exec_prefix',
+        'executable',
+        'home',
+        'module_search_path_env',
+        'module_search_paths',
+        'prefix',
+    )
+    # FIXME: untested main configuration variables
+    UNTESTED_MAIN_CONFIG = (
+        'install_signal_handlers',
+        'module_search_path',
+    )
+    DEFAULT_GLOBAL_CONFIG = {
+        'Py_BytesWarningFlag': 0,
+        'Py_DebugFlag': 0,
+        'Py_DontWriteBytecodeFlag': 0,
+        'Py_FrozenFlag': 0,
+        'Py_HasFileSystemDefaultEncoding': 0,
+        'Py_HashRandomizationFlag': 1,
+        'Py_InspectFlag': 0,
+        'Py_InteractiveFlag': 0,
+        'Py_IsolatedFlag': 0,
+        'Py_NoSiteFlag': 0,
+        'Py_NoUserSiteDirectory': 0,
+        'Py_OptimizeFlag': 0,
+        'Py_QuietFlag': 0,
+        'Py_UnbufferedStdioFlag': 0,
+        'Py_VerboseFlag': 0,
+    }
+    if os.name == 'nt':
+        DEFAULT_GLOBAL_CONFIG['Py_HasFileSystemDefaultEncoding'] = 1
+        DEFAULT_GLOBAL_CONFIG['Py_LegacyWindowsFSEncodingFlag'] = 0
+        DEFAULT_GLOBAL_CONFIG['Py_LegacyWindowsStdioFlag'] = 0
+
+    DEFAULT_CORE_CONFIG = {
         'install_signal_handlers': 1,
-        'Py_IgnoreEnvironmentFlag': 0,
+        'ignore_environment': 0,
         'use_hash_seed': 0,
         'hash_seed': 0,
-        'allocator': '(null)',
+        'allocator': None,
         'dev_mode': 0,
         'faulthandler': 0,
         'tracemalloc': 0,
@@ -265,34 +306,42 @@
         'show_alloc_count': 0,
         'dump_refs': 0,
         'malloc_stats': 0,
-        'utf8_mode': 0,
 
+        'utf8_mode': 0,
         'coerce_c_locale': 0,
         'coerce_c_locale_warn': 0,
 
         'program_name': './_testembed',
-        'argc': 0,
-        'argv': '[]',
-        'program': '(null)',
+        'argv': [],
+        'program': None,
 
-        'Py_IsolatedFlag': 0,
-        'Py_NoSiteFlag': 0,
-        'Py_BytesWarningFlag': 0,
-        'Py_InspectFlag': 0,
-        'Py_InteractiveFlag': 0,
-        'Py_OptimizeFlag': 0,
-        'Py_DebugFlag': 0,
-        'Py_DontWriteBytecodeFlag': 0,
-        'Py_VerboseFlag': 0,
-        'Py_QuietFlag': 0,
-        'Py_NoUserSiteDirectory': 0,
-        'Py_UnbufferedStdioFlag': 0,
+        'xoptions': [],
+        'warnoptions': [],
 
         '_disable_importlib': 0,
-        'Py_FrozenFlag': 0,
     }
 
-    def check_config(self, testname, expected):
+    def get_filesystem_encoding(self, isolated, env):
+        code = ('import codecs, locale, sys; '
+                'print(sys.getfilesystemencoding(), '
+                'sys.getfilesystemencodeerrors())')
+        args = (sys.executable, '-c', code)
+        env = dict(env)
+        if not isolated:
+            env['PYTHONCOERCECLOCALE'] = '0'
+            env['PYTHONUTF8'] = '0'
+        proc = subprocess.run(args, text=True, env=env,
+                              stdout=subprocess.PIPE,
+                              stderr=subprocess.PIPE)
+        if proc.returncode:
+            raise Exception(f"failed to get the locale encoding: "
+                            f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
+        out = proc.stdout.rstrip()
+        return out.split()
+
+    def check_config(self, testname, expected, expected_global):
+        expected = dict(self.DEFAULT_CORE_CONFIG, **expected)
+
         env = dict(os.environ)
         for key in list(env):
             if key.startswith('PYTHON'):
@@ -301,42 +350,78 @@
         # on the current locale
         env['PYTHONCOERCECLOCALE'] = '0'
         env['PYTHONUTF8'] = '0'
+
         out, err = self.run_embedded_interpreter(testname, env=env)
         # Ignore err
 
-        expected = dict(self.DEFAULT_CONFIG, **expected)
-        for key, value in expected.items():
-            expected[key] = str(value)
+        config = json.loads(out)
+        core_config = config['core_config']
+        executable = core_config['executable']
+        main_config = config['main_config']
 
-        config = {}
-        for line in out.splitlines():
-            key, value = line.split(' = ', 1)
-            config[key] = value
-        self.assertEqual(config, expected)
+        for key in self.UNTESTED_MAIN_CONFIG:
+            del main_config[key]
+
+        expected_main = {
+            'argv': [],
+            'prefix': sys.prefix,
+            'executable': core_config['executable'],
+            'base_prefix': sys.base_prefix,
+            'base_exec_prefix': sys.base_exec_prefix,
+            'warnoptions': core_config['warnoptions'],
+            'xoptions': {},
+            'exec_prefix': core_config['exec_prefix'],
+        }
+        self.assertEqual(main_config, expected_main)
+
+        expected_global = dict(self.DEFAULT_GLOBAL_CONFIG, **expected_global)
+
+        if 'Py_FileSystemDefaultEncoding' not in expected_global:
+            isolated = expected_global['Py_IsolatedFlag']
+            fs_encoding, fs_errors = self.get_filesystem_encoding(isolated, env)
+            expected_global['Py_FileSystemDefaultEncodeErrors'] = fs_errors
+            expected_global['Py_FileSystemDefaultEncoding'] = fs_encoding
+
+        for global_key, core_key in (
+            ('Py_UTF8Mode', 'utf8_mode'),
+            ('Py_IgnoreEnvironmentFlag', 'ignore_environment'),
+        ):
+            expected_global[global_key] = core_config[core_key]
+
+        self.assertEqual(config['global_config'], expected_global)
+
+        for key in self.UNTESTED_CORE_CONFIG:
+            core_config.pop(key, None)
+        self.assertEqual(core_config, expected)
 
     def test_init_default_config(self):
-        self.check_config("init_default_config", {})
+        self.check_config("init_default_config", {}, {})
 
     def test_init_global_config(self):
-        config = {
+        core_config = {
             'program_name': './globalvar',
-            'Py_NoSiteFlag': 1,
+            'utf8_mode': 1,
+        }
+        global_config = {
             'Py_BytesWarningFlag': 1,
+            'Py_DontWriteBytecodeFlag': 1,
+            'Py_HasFileSystemDefaultEncoding': 1,
+            'Py_FileSystemDefaultEncodeErrors': self.UTF8_MODE_ERRORS,
+            'Py_FileSystemDefaultEncoding': 'utf-8',
             'Py_InspectFlag': 1,
             'Py_InteractiveFlag': 1,
-            'Py_OptimizeFlag': 2,
-            'Py_DontWriteBytecodeFlag': 1,
-            'Py_VerboseFlag': 1,
-            'Py_QuietFlag': 1,
-            'Py_UnbufferedStdioFlag': 1,
-            'utf8_mode': 1,
+            'Py_NoSiteFlag': 1,
             'Py_NoUserSiteDirectory': 1,
+            'Py_OptimizeFlag': 2,
+            'Py_QuietFlag': 1,
+            'Py_VerboseFlag': 1,
             'Py_FrozenFlag': 1,
+            'Py_UnbufferedStdioFlag': 1,
         }
-        self.check_config("init_global_config", config)
+        self.check_config("init_global_config", core_config, global_config)
 
     def test_init_from_config(self):
-        config = {
+        core_config = {
             'install_signal_handlers': 0,
             'use_hash_seed': 1,
             'hash_seed': 123,
@@ -354,10 +439,14 @@
 
             'faulthandler': 1,
         }
-        self.check_config("init_from_config", config)
+        global_config = {
+            'Py_HasFileSystemDefaultEncoding': 1,
+            'Py_NoUserSiteDirectory': 0,
+        }
+        self.check_config("init_from_config", core_config, global_config)
 
     def test_init_env(self):
-        config = {
+        core_config = {
             'use_hash_seed': 1,
             'hash_seed': 42,
             'allocator': 'malloc_debug',
@@ -365,32 +454,39 @@
             'import_time': 1,
             'malloc_stats': 1,
             'utf8_mode': 1,
-            'Py_InspectFlag': 1,
-            'Py_OptimizeFlag': 2,
-            'Py_DontWriteBytecodeFlag': 1,
-            'Py_VerboseFlag': 1,
-            'Py_UnbufferedStdioFlag': 1,
-            'Py_NoUserSiteDirectory': 1,
             'faulthandler': 1,
             'dev_mode': 1,
         }
-        self.check_config("init_env", config)
+        global_config = {
+            'Py_DontWriteBytecodeFlag': 1,
+            'Py_HasFileSystemDefaultEncoding': 1,
+            'Py_InspectFlag': 1,
+            'Py_NoUserSiteDirectory': 1,
+            'Py_OptimizeFlag': 2,
+            'Py_UnbufferedStdioFlag': 1,
+            'Py_VerboseFlag': 1,
+            'Py_FileSystemDefaultEncoding': 'utf-8',
+            'Py_FileSystemDefaultEncodeErrors': self.UTF8_MODE_ERRORS,
+        }
+        self.check_config("init_env", core_config, global_config)
 
     def test_init_dev_mode(self):
-        config = {
+        core_config = {
             'dev_mode': 1,
             'faulthandler': 1,
             'allocator': 'debug',
         }
-        self.check_config("init_dev_mode", config)
+        self.check_config("init_dev_mode", core_config, {})
 
     def test_init_isolated(self):
-        config = {
+        core_config = {
+            'ignore_environment': 1,
+        }
+        global_config = {
             'Py_IsolatedFlag': 1,
-            'Py_IgnoreEnvironmentFlag': 1,
             'Py_NoUserSiteDirectory': 1,
         }
-        self.check_config("init_isolated", config)
+        self.check_config("init_isolated", core_config, global_config)
 
 
 if __name__ == "__main__":