# bpo-42260: Test _PyInterpreterState_GetConfigCopy()
# and _PyInterpreterState_SetConfig().
#
# Test run in a subprocess since set_config(get_config())
# does reset sys attributes to their state of the Python startup
# (before the site module is run).

import _testinternalcapi
import os
import sys
import unittest
from test import support
from test.support import MS_WINDOWS


MAX_HASH_SEED = 4294967295


BOOL_OPTIONS = [
    'isolated',
    'use_environment',
    'dev_mode',
    'install_signal_handlers',
    'use_hash_seed',
    'faulthandler',
    'import_time',
    'code_debug_ranges',
    'show_ref_count',
    'dump_refs',
    'malloc_stats',
    'parse_argv',
    'site_import',
    'warn_default_encoding',
    'inspect',
    'interactive',
    'parser_debug',
    'write_bytecode',
    'quiet',
    'user_site_directory',
    'configure_c_stdio',
    'buffered_stdio',
    'use_frozen_modules',
    'safe_path',
    'pathconfig_warnings',
    'module_search_paths_set',
    'skip_source_first_line',
    '_install_importlib',
    '_init_main',
    '_is_python_build',
]
if MS_WINDOWS:
    BOOL_OPTIONS.append('legacy_windows_stdio')


class SetConfigTests(unittest.TestCase):
    def setUp(self):
        self.old_config = _testinternalcapi.get_config()
        self.sys_copy = dict(sys.__dict__)

    def tearDown(self):
        _testinternalcapi.reset_path_config()
        _testinternalcapi.set_config(self.old_config)
        sys.__dict__.clear()
        sys.__dict__.update(self.sys_copy)

    def set_config(self, **kwargs):
        _testinternalcapi.set_config(self.old_config | kwargs)

    def check(self, **kwargs):
        self.set_config(**kwargs)
        for key, value in kwargs.items():
            self.assertEqual(getattr(sys, key), value,
                             (key, value))

    def test_set_invalid(self):
        invalid_uint = -1
        NULL = None
        invalid_wstr = NULL
        # PyWideStringList strings must be non-NULL
        invalid_wstrlist = ["abc", NULL, "def"]

        type_tests = []
        value_tests = [
            # enum
            ('_config_init', 0),
            ('_config_init', 4),
            # unsigned long
            ("hash_seed", -1),
            ("hash_seed", MAX_HASH_SEED + 1),
        ]

        # int (unsigned)
        int_options = [
            '_config_init',
            'bytes_warning',
            'optimization_level',
            'tracemalloc',
            'verbose',
        ]
        int_options.extend(BOOL_OPTIONS)
        for key in int_options:
            value_tests.append((key, invalid_uint))
            type_tests.append((key, "abc"))
            type_tests.append((key, 2.0))

        # wchar_t*
        for key in (
            'filesystem_encoding',
            'filesystem_errors',
            'stdio_encoding',
            'stdio_errors',
            'check_hash_pycs_mode',
            'program_name',
            'platlibdir',
            # optional wstr:
            # 'pythonpath_env'
            # 'home'
            # 'pycache_prefix'
            # 'run_command'
            # 'run_module'
            # 'run_filename'
            # 'executable'
            # 'prefix'
            # 'exec_prefix'
            # 'base_executable'
            # 'base_prefix'
            # 'base_exec_prefix'
        ):
            value_tests.append((key, invalid_wstr))
            type_tests.append((key, b'bytes'))
            type_tests.append((key, 123))

        # PyWideStringList
        for key in (
            'orig_argv',
            'argv',
            'xoptions',
            'warnoptions',
            'module_search_paths',
        ):
            value_tests.append((key, invalid_wstrlist))
            type_tests.append((key, 123))
            type_tests.append((key, "abc"))
            type_tests.append((key, [123]))
            type_tests.append((key, [b"bytes"]))


        if MS_WINDOWS:
            value_tests.append(('legacy_windows_stdio', invalid_uint))

        for exc_type, tests in (
            (ValueError, value_tests),
            (TypeError, type_tests),
        ):
            for key, value in tests:
                config = self.old_config | {key: value}
                with self.subTest(key=key, value=value, exc_type=exc_type):
                    with self.assertRaises(exc_type):
                        _testinternalcapi.set_config(config)

    def test_flags(self):
        bool_options = set(BOOL_OPTIONS)
        for sys_attr, key, value in (
            ("debug", "parser_debug", 1),
            ("inspect", "inspect", 2),
            ("interactive", "interactive", 3),
            ("optimize", "optimization_level", 4),
            ("verbose", "verbose", 1),
            ("bytes_warning", "bytes_warning", 10),
            ("quiet", "quiet", 11),
            ("isolated", "isolated", 12),
        ):
            with self.subTest(sys=sys_attr, key=key, value=value):
                self.set_config(**{key: value, 'parse_argv': 0})
                if key in bool_options:
                    self.assertEqual(getattr(sys.flags, sys_attr), int(bool(value)))
                else:
                    self.assertEqual(getattr(sys.flags, sys_attr), value)

        self.set_config(write_bytecode=0)
        self.assertEqual(sys.flags.dont_write_bytecode, True)
        self.assertEqual(sys.dont_write_bytecode, True)

        self.set_config(write_bytecode=1)
        self.assertEqual(sys.flags.dont_write_bytecode, False)
        self.assertEqual(sys.dont_write_bytecode, False)

        self.set_config(user_site_directory=0, isolated=0)
        self.assertEqual(sys.flags.no_user_site, 1)
        self.set_config(user_site_directory=1, isolated=0)
        self.assertEqual(sys.flags.no_user_site, 0)

        self.set_config(site_import=0)
        self.assertEqual(sys.flags.no_site, 1)
        self.set_config(site_import=1)
        self.assertEqual(sys.flags.no_site, 0)

        self.set_config(dev_mode=0)
        self.assertEqual(sys.flags.dev_mode, False)
        self.set_config(dev_mode=1)
        self.assertEqual(sys.flags.dev_mode, True)

        self.set_config(use_environment=0, isolated=0)
        self.assertEqual(sys.flags.ignore_environment, 1)
        self.set_config(use_environment=1, isolated=0)
        self.assertEqual(sys.flags.ignore_environment, 0)

        self.set_config(use_hash_seed=1, hash_seed=0)
        self.assertEqual(sys.flags.hash_randomization, 0)
        self.set_config(use_hash_seed=0, hash_seed=0)
        self.assertEqual(sys.flags.hash_randomization, 1)
        self.set_config(use_hash_seed=1, hash_seed=123)
        self.assertEqual(sys.flags.hash_randomization, 1)

        if support.Py_GIL_DISABLED:
            self.set_config(enable_gil=-1)
            self.assertEqual(sys.flags.gil, None)
            self.set_config(enable_gil=0)
            self.assertEqual(sys.flags.gil, 0)
            self.set_config(enable_gil=1)
            self.assertEqual(sys.flags.gil, 1)
        else:
            # Builds without Py_GIL_DISABLED don't have
            # PyConfig.enable_gil. sys.flags.gil is always defined to 1, for
            # consistency.
            self.assertEqual(sys.flags.gil, 1)

    def test_options(self):
        self.check(warnoptions=[])
        self.check(warnoptions=["default", "ignore"])

        self.set_config(xoptions=[])
        self.assertEqual(sys._xoptions, {})
        self.set_config(xoptions=["dev", "tracemalloc=5"])
        self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})

    def test_pathconfig(self):
        self.check(
            executable='executable',
            prefix="prefix",
            base_prefix="base_prefix",
            exec_prefix="exec_prefix",
            base_exec_prefix="base_exec_prefix",
            platlibdir="platlibdir")

        self.set_config(base_executable="base_executable")
        self.assertEqual(sys._base_executable, "base_executable")

        # When base_xxx is NULL, value is copied from xxxx
        self.set_config(
            executable='executable',
            prefix="prefix",
            exec_prefix="exec_prefix",
            base_executable=None,
            base_prefix=None,
            base_exec_prefix=None)
        self.assertEqual(sys._base_executable, "executable")
        self.assertEqual(sys.base_prefix, "prefix")
        self.assertEqual(sys.base_exec_prefix, "exec_prefix")

    def test_path(self):
        self.set_config(module_search_paths_set=1,
                        module_search_paths=['a', 'b', 'c'])
        self.assertEqual(sys.path, ['a', 'b', 'c'])

        # sys.path is reset if module_search_paths_set=0
        self.set_config(module_search_paths_set=0,
                        module_search_paths=['new_path'])
        self.assertNotEqual(sys.path, ['a', 'b', 'c'])
        self.assertNotEqual(sys.path, ['new_path'])

    def test_argv(self):
        self.set_config(parse_argv=0,
                        argv=['python_program', 'args'],
                        orig_argv=['orig', 'orig_args'])
        self.assertEqual(sys.argv, ['python_program', 'args'])
        self.assertEqual(sys.orig_argv, ['orig', 'orig_args'])

        self.set_config(parse_argv=0,
                        argv=[],
                        orig_argv=[])
        self.assertEqual(sys.argv, [''])
        self.assertEqual(sys.orig_argv, [])

    def test_pycache_prefix(self):
        self.check(pycache_prefix=None)
        self.check(pycache_prefix="pycache_prefix")


if __name__ == "__main__":
    unittest.main()
