From cecd8ac6cf5f13d6d2bc83266d348469c7ab36f8 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 24 Oct 2017 21:41:37 +1000 Subject: [PATCH] bpo-31845: Fix reading flags from environment The startup refactoring means command line settings are now applied after settings are read from the environment. This updates the way command line settings are applied to account for that, ensures more settings are first read from the environment in _PyInitializeCore, and adds a simple test case covering the flags that are easy to check. --- Lib/test/test_cmd_line.py | 35 +++++++++++- .../2017-10-24-21-27-32.bpo-31845.8OS-k3.rst | 1 + Modules/main.c | 54 +++++++++---------- Python/pylifecycle.c | 37 ++++++++----- 4 files changed, 82 insertions(+), 45 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2017-10-24-21-27-32.bpo-31845.8OS-k3.rst diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 28ddb2ba1bf223..1b584ebb7c5ec3 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -12,7 +12,6 @@ spawn_python, kill_python, assert_python_ok, assert_python_failure ) - # XXX (ncoghlan): Move to script_helper and make consistent with run_python def _kill_python_and_exit_code(p): data = kill_python(p) @@ -486,6 +485,26 @@ def test_isolatedmode(self): cwd=tmpdir) self.assertEqual(out.strip(), b"ok") + def test_sys_flags_set(self): + # Issue 31845: a startup refactoring broke reading flags from env vars + for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)): + env_vars = dict( + PYTHONDEBUG=value, + PYTHONOPTIMIZE=value, + PYTHONDONTWRITEBYTECODE=value, + PYTHONVERBOSE=value, + ) + code = ( + "import sys; " + "sys.stderr.write(str(sys.flags)); " + f"""sys.exit(not ( + sys.flags.debug == sys.flags.optimize == + sys.flags.dont_write_bytecode == sys.flags.verbose == + {expected} + ))""" + ) + with self.subTest(envar_value=value): + assert_python_ok('-c', code, **env_vars) class IgnoreEnvironmentTest(unittest.TestCase): @@ -506,6 +525,20 @@ def test_ignore_PYTHONHASHSEED(self): self.run_ignoring_vars("sys.flags.hash_randomization == 1", PYTHONHASHSEED="0") + def test_sys_flags_not_set(self): + # Issue 31845: a startup refactoring broke reading flags from env vars + expected_outcome = """ + (sys.flags.debug == sys.flags.optimize == + sys.flags.dont_write_bytecode == sys.flags.verbose == 0) + """ + self.run_ignoring_vars( + expected_outcome, + PYTHONDEBUG="1", + PYTHONOPTIMIZE="1", + PYTHONDONTWRITEBYTECODE="1", + PYTHONVERBOSE="1", + ) + def test_main(): test.support.run_unittest(CmdLineTest, IgnoreEnvironmentTest) diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-10-24-21-27-32.bpo-31845.8OS-k3.rst b/Misc/NEWS.d/next/Core and Builtins/2017-10-24-21-27-32.bpo-31845.8OS-k3.rst new file mode 100644 index 00000000000000..ddaf6306386eff --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-10-24-21-27-32.bpo-31845.8OS-k3.rst @@ -0,0 +1 @@ +Environment variables are once more read correctly at interpreter startup. diff --git a/Modules/main.c b/Modules/main.c index e86211331047ee..846ecb6170d86e 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -522,39 +522,33 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline) return 0; } -static int -apply_command_line_and_environment(_Py_CommandLineDetails *cmdline) +static void +maybe_set_flag(int *flag, int value) { - char *p; - Py_BytesWarningFlag = cmdline->bytes_warning; - Py_DebugFlag = cmdline->debug; - Py_InspectFlag = cmdline->inspect; - Py_InteractiveFlag = cmdline->interactive; - Py_IsolatedFlag = cmdline->isolated; - Py_OptimizeFlag = cmdline->optimization_level; - Py_DontWriteBytecodeFlag = cmdline->dont_write_bytecode; - Py_NoUserSiteDirectory = cmdline->no_user_site_directory; - Py_NoSiteFlag = cmdline->no_site_import; - Py_UnbufferedStdioFlag = cmdline->use_unbuffered_io; - Py_VerboseFlag = cmdline->verbosity; - Py_QuietFlag = cmdline->quiet_flag; - - if (!Py_InspectFlag && - (p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') { - Py_InspectFlag = 1; - cmdline->inspect = 1; - } - if (!cmdline->use_unbuffered_io && - (p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') { - Py_UnbufferedStdioFlag = 1; - cmdline->use_unbuffered_io = 1; + /* Helper to set flag variables from command line options + * - uses the higher of the two values if they're both set + * - otherwise leaves the flag unset + */ + if (*flag < value) { + *flag = value; } +} - if (!Py_NoUserSiteDirectory && - (p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0') { - Py_NoUserSiteDirectory = 1; - cmdline->no_user_site_directory = 1; - } +static int +apply_command_line_and_environment(_Py_CommandLineDetails *cmdline) +{ + maybe_set_flag(&Py_BytesWarningFlag, cmdline->bytes_warning); + maybe_set_flag(&Py_DebugFlag, cmdline->debug); + maybe_set_flag(&Py_InspectFlag, cmdline->inspect); + maybe_set_flag(&Py_InteractiveFlag, cmdline->interactive); + maybe_set_flag(&Py_IsolatedFlag, cmdline->isolated); + maybe_set_flag(&Py_OptimizeFlag, cmdline->optimization_level); + maybe_set_flag(&Py_DontWriteBytecodeFlag, cmdline->dont_write_bytecode); + maybe_set_flag(&Py_NoUserSiteDirectory, cmdline->no_user_site_directory); + maybe_set_flag(&Py_NoSiteFlag, cmdline->no_site_import); + maybe_set_flag(&Py_UnbufferedStdioFlag, cmdline->use_unbuffered_io); + maybe_set_flag(&Py_VerboseFlag, cmdline->verbosity); + maybe_set_flag(&Py_QuietFlag, cmdline->quiet_flag); /* TODO: Apply PYTHONWARNINGS & -W options to sys module here */ /* TODO: Apply -X options to sys module here */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5b13bc45822b2a..4e8bbb662e971e 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -217,15 +217,18 @@ Py_SetStandardStreamEncoding(const char *encoding, const char *errors) */ -static int -add_flag(int flag, const char *envs) +static void +set_flag(int *flag, const char *envs) { + /* Helper to set flag variables from environment variables: + * - uses the higher of the two values if they're both set + * - otherwise sets the flag to 1 + */ int env = atoi(envs); - if (flag < env) - flag = env; - if (flag < 1) - flag = 1; - return flag; + if (*flag < env) + *flag = env; + if (*flag < 1) + *flag = 1; } static char* @@ -612,22 +615,28 @@ void _Py_InitializeCore(const _PyCoreConfig *config) #endif if ((p = Py_GETENV("PYTHONDEBUG")) && *p != '\0') - Py_DebugFlag = add_flag(Py_DebugFlag, p); + set_flag(&Py_DebugFlag, p); if ((p = Py_GETENV("PYTHONVERBOSE")) && *p != '\0') - Py_VerboseFlag = add_flag(Py_VerboseFlag, p); + set_flag(&Py_VerboseFlag, p); if ((p = Py_GETENV("PYTHONOPTIMIZE")) && *p != '\0') - Py_OptimizeFlag = add_flag(Py_OptimizeFlag, p); + set_flag(&Py_OptimizeFlag, p); + if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') + set_flag(&Py_InspectFlag, p); if ((p = Py_GETENV("PYTHONDONTWRITEBYTECODE")) && *p != '\0') - Py_DontWriteBytecodeFlag = add_flag(Py_DontWriteBytecodeFlag, p); + set_flag(&Py_DontWriteBytecodeFlag, p); + if ((p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0') + set_flag(&Py_NoUserSiteDirectory, p); + if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') + set_flag(&Py_UnbufferedStdioFlag, p); /* The variable is only tested for existence here; _Py_HashRandomization_Init will check its value further. */ if ((p = Py_GETENV("PYTHONHASHSEED")) && *p != '\0') - Py_HashRandomizationFlag = add_flag(Py_HashRandomizationFlag, p); + set_flag(&Py_HashRandomizationFlag, p); #ifdef MS_WINDOWS if ((p = Py_GETENV("PYTHONLEGACYWINDOWSFSENCODING")) && *p != '\0') - Py_LegacyWindowsFSEncodingFlag = add_flag(Py_LegacyWindowsFSEncodingFlag, p); + set_flag(&Py_LegacyWindowsFSEncodingFlag, p); if ((p = Py_GETENV("PYTHONLEGACYWINDOWSSTDIO")) && *p != '\0') - Py_LegacyWindowsStdioFlag = add_flag(Py_LegacyWindowsStdioFlag, p); + set_flag(&Py_LegacyWindowsStdioFlag, p); #endif _Py_HashRandomization_Init(&core_config);