From 499af13b94d07a4f3428fa47b4cc078fee46d44d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 14 Jun 2024 13:28:14 +0200 Subject: [PATCH 1/8] gh-120057: Add os.get_user_default_environ() function --- Doc/library/os.rst | 22 +++++++ Doc/whatsnew/3.14.rst | 5 ++ Lib/os.py | 18 +++++ Lib/test/test_os.py | 11 ++++ ...-06-14-13-28-09.gh-issue-120057.GN0-d0.rst | 4 ++ Modules/clinic/posixmodule.c.h | 28 +++++++- Modules/posixmodule.c | 65 +++++++++++++++++++ 7 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 360d71e70960c7..377b63a7121c00 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -197,6 +197,10 @@ process and user. changes to the environment made by :func:`os.putenv`, by :func:`os.unsetenv`, or made outside Python in the same process. + On Windows, :func:`get_user_default_environ` can be used to update + :data:`os.environ` to the latest system environment variables, such as the + ``PATH`` variable. + This mapping may be used to modify the environment as well as query the environment. :func:`putenv` will be called automatically when the mapping is modified. @@ -326,6 +330,8 @@ process and user. and ``'surrogateescape'`` error handler. Use :func:`os.getenvb` if you would like to use a different encoding. + See also the :data:`os.environ.refresh() ` method. + .. availability:: Unix, Windows. @@ -357,6 +363,22 @@ process and user. .. versionadded:: 3.2 +.. function:: get_user_default_environ() + + Get the default environment of the current process user as a dictionary. + + It can be used to update :data:`os.environ` to the latest system environment + variables, such as the ``PATH`` variable. Example:: + + os.environ.update(os.get_user_default_environ()) + + See also the :data:`os.environ.refresh() ` method. + + .. availability:: Windows. + + .. versionadded:: 3.14 + + .. function:: getegid() Return the effective group id of the current process. This corresponds to the diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b357553735e8bb..05628b1638cbab 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -100,6 +100,11 @@ os by :func:`os.unsetenv`, or made outside Python in the same process. (Contributed by Victor Stinner in :gh:`120057`.) +* Added the :func:`os.get_user_default_environ` function to get the user + default environment. It can be used to update :data:`os.environ` to the + latest system environment variables, such as the ``PATH`` variable. + (Contributed by Victor Stinner in :gh:`120057`.) + symtable -------- diff --git a/Lib/os.py b/Lib/os.py index 4b48afb040e565..b434e2ad5ca9a9 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -96,6 +96,10 @@ def _get_exports_list(module): from nt import _create_environ except ImportError: pass + try: + from nt import _get_user_default_environ + except ImportError: + pass else: raise ImportError('no os specific module found') @@ -826,6 +830,20 @@ def decode(value): del _create_environ_mapping +if _exists("_get_user_default_environ"): + def get_user_default_environ(): + env = {} + env_str = _get_user_default_environ() + for entry in env_str.split('\0'): + parts = entry.split('=', 1) + if len(parts) != 2: + # Skip names that begin with '=' + continue + name, value = parts + env[name] = value + return env + + def getenv(key, default=None): """Get an environment variable, return None if it doesn't exist. The optional second argument can specify an alternate default. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index f93937fb587386..6905fe9ed94fd5 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1345,6 +1345,17 @@ def test_refresh(self): self.assertNotIn(b'test_env', os.environb) self.assertNotIn('test_env', os.environ) + @unittest.skipUnless(hasattr(os, 'get_user_default_environ'), + 'need os.get_user_default_environ()') + def test_get_user_default_environ(self): + env = os.get_user_default_environ() + self.assertIsInstance(env, dict) + for name, value in env.items(): + self.assertIsInstance(name, str) + self.assertIsInstance(value, str) + self.assertTrue(bool(name), name) # must be not empty + + class WalkTests(unittest.TestCase): """Tests for os.walk().""" is_fwalk = False diff --git a/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst new file mode 100644 index 00000000000000..dc1053a6fd35c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst @@ -0,0 +1,4 @@ +Added the :func:`os.get_user_default_environ` function to get the user +default environment. It can be used to update :data:`os.environ` to the +latest system environment variables, such as the ``PATH`` variable. Patch by +Victor Stinner. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 07b28fef3a57ea..038f37d8dd51e5 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -12170,6 +12170,28 @@ os__create_environ(PyObject *module, PyObject *Py_UNUSED(ignored)) return os__create_environ_impl(module); } +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__get_user_default_environ__doc__, +"_get_user_default_environ($module, /)\n" +"--\n" +"\n" +"Get user default environment string."); + +#define OS__GET_USER_DEFAULT_ENVIRON_METHODDEF \ + {"_get_user_default_environ", (PyCFunction)os__get_user_default_environ, METH_NOARGS, os__get_user_default_environ__doc__}, + +static PyObject * +os__get_user_default_environ_impl(PyObject *module); + +static PyObject * +os__get_user_default_environ(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return os__get_user_default_environ_impl(module); +} + +#endif /* defined(MS_WINDOWS) */ + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -12837,4 +12859,8 @@ os__create_environ(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=5ae2e5ffcd9c8a84 input=a9049054013a1b77]*/ + +#ifndef OS__GET_USER_DEFAULT_ENVIRON_METHODDEF + #define OS__GET_USER_DEFAULT_ENVIRON_METHODDEF +#endif /* !defined(OS__GET_USER_DEFAULT_ENVIRON_METHODDEF) */ +/*[clinic end generated code: output=66014c7643fb6634 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a8fd5c494769b5..c7a4494647a639 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16823,6 +16823,70 @@ os__create_environ_impl(PyObject *module) } +#ifdef MS_WINDOWS +/*[clinic input] +os._get_user_default_environ + +Get user default environment string. +[clinic start generated code]*/ + +static PyObject * +os__get_user_default_environ_impl(PyObject *module) +/*[clinic end generated code: output=6cce8c186a556ef0 input=e15b3d87fce12734]*/ +{ + HINSTANCE hUserEnv = LoadLibraryW(L"USERENV"); + if (!hUserEnv) { + goto error; + } + + HINSTANCE (CALLBACK *pCreateEnvironmentBlock) (LPVOID, HANDLE, BOOL); + HINSTANCE (CALLBACK *pDestroyEnvironmentBlock) (LPVOID); + + *(FARPROC*)&pCreateEnvironmentBlock = GetProcAddress(hUserEnv, + "CreateEnvironmentBlock"); + if (!pCreateEnvironmentBlock) { + goto error; + } + + *(FARPROC*)&pDestroyEnvironmentBlock = GetProcAddress(hUserEnv, + "DestroyEnvironmentBlock"); + if (!pDestroyEnvironmentBlock) { + goto error; + } + + HANDLE htoken; + if (!OpenProcessToken(GetCurrentProcess(), + TOKEN_DUPLICATE|TOKEN_QUERY, + &htoken)) { + goto error; + } + + PWCHAR env_str; + if (!pCreateEnvironmentBlock(&env_str, htoken, 0)) { + CloseHandle(htoken); + goto error; + } + CloseHandle(htoken); + + Py_ssize_t len = wcslen(env_str); + while (!env_str[len+1] != 0) { + len++; + len += wcslen(env_str + len); + } + + PyObject *str = PyUnicode_FromWideChar(env_str, len); + if (!pDestroyEnvironmentBlock(env_str)) { + Py_XDECREF(str); + goto error; + } + return str; + +error: + return PyErr_SetFromWindowsErr(0); +} +#endif // MS_WINDOWS + + static PyMethodDef posix_methods[] = { OS_STAT_METHODDEF @@ -17038,6 +17102,7 @@ static PyMethodDef posix_methods[] = { OS__INPUTHOOK_METHODDEF OS__IS_INPUTHOOK_INSTALLED_METHODDEF OS__CREATE_ENVIRON_METHODDEF + OS__GET_USER_DEFAULT_ENVIRON_METHODDEF {NULL, NULL} /* Sentinel */ }; From 576e2b93aa886dfd27c481f43ce8c705527ccc28 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 14 Jun 2024 13:53:36 +0200 Subject: [PATCH 2/8] Fix typo --- Modules/posixmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c7a4494647a639..a03639395d7bbb 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16869,7 +16869,7 @@ os__get_user_default_environ_impl(PyObject *module) CloseHandle(htoken); Py_ssize_t len = wcslen(env_str); - while (!env_str[len+1] != 0) { + while (env_str[len+1] != 0) { len++; len += wcslen(env_str + len); } From e76c37927c3369bb040dfe73fb4d13c8d77550d5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 14 Jun 2024 13:54:51 +0200 Subject: [PATCH 3/8] user *and* system environment variables --- Doc/library/os.rst | 8 ++++---- Doc/whatsnew/3.14.rst | 2 +- .../2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 377b63a7121c00..0ee5c5fa2ae992 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -198,8 +198,8 @@ process and user. :func:`os.unsetenv`, or made outside Python in the same process. On Windows, :func:`get_user_default_environ` can be used to update - :data:`os.environ` to the latest system environment variables, such as the - ``PATH`` variable. + :data:`os.environ` to the latest user and system environment variables, such + as the ``PATH`` variable. This mapping may be used to modify the environment as well as query the environment. :func:`putenv` will be called automatically when the mapping @@ -367,8 +367,8 @@ process and user. Get the default environment of the current process user as a dictionary. - It can be used to update :data:`os.environ` to the latest system environment - variables, such as the ``PATH`` variable. Example:: + It can be used to update :data:`os.environ` to the latest user and system + environment variables, such as the ``PATH`` variable. Example:: os.environ.update(os.get_user_default_environ()) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 05628b1638cbab..6b69e07183f23f 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -102,7 +102,7 @@ os * Added the :func:`os.get_user_default_environ` function to get the user default environment. It can be used to update :data:`os.environ` to the - latest system environment variables, such as the ``PATH`` variable. + latest user and system environment variables, such as the ``PATH`` variable. (Contributed by Victor Stinner in :gh:`120057`.) symtable diff --git a/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst index dc1053a6fd35c7..32f91e6e62ac42 100644 --- a/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst +++ b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst @@ -1,4 +1,4 @@ Added the :func:`os.get_user_default_environ` function to get the user default environment. It can be used to update :data:`os.environ` to the -latest system environment variables, such as the ``PATH`` variable. Patch by -Victor Stinner. +latest user and system environment variables, such as the ``PATH`` variable. +Patch by Victor Stinner. From 337b22ba60631c04594783c80be6679bb5cdb11c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jun 2024 11:41:49 +0200 Subject: [PATCH 4/8] PATH => Path --- Doc/library/os.rst | 4 ++-- Doc/whatsnew/3.14.rst | 2 +- .../Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 0ee5c5fa2ae992..815400f7ec3942 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -199,7 +199,7 @@ process and user. On Windows, :func:`get_user_default_environ` can be used to update :data:`os.environ` to the latest user and system environment variables, such - as the ``PATH`` variable. + as the ``Path`` variable. This mapping may be used to modify the environment as well as query the environment. :func:`putenv` will be called automatically when the mapping @@ -368,7 +368,7 @@ process and user. Get the default environment of the current process user as a dictionary. It can be used to update :data:`os.environ` to the latest user and system - environment variables, such as the ``PATH`` variable. Example:: + environment variables, such as the ``Path`` variable. Example:: os.environ.update(os.get_user_default_environ()) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 6b69e07183f23f..64e9b3ed5a8045 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -102,7 +102,7 @@ os * Added the :func:`os.get_user_default_environ` function to get the user default environment. It can be used to update :data:`os.environ` to the - latest user and system environment variables, such as the ``PATH`` variable. + latest user and system environment variables, such as the ``Path`` variable. (Contributed by Victor Stinner in :gh:`120057`.) symtable diff --git a/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst index 32f91e6e62ac42..98ee78e450d675 100644 --- a/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst +++ b/Misc/NEWS.d/next/Library/2024-06-14-13-28-09.gh-issue-120057.GN0-d0.rst @@ -1,4 +1,4 @@ Added the :func:`os.get_user_default_environ` function to get the user default environment. It can be used to update :data:`os.environ` to the -latest user and system environment variables, such as the ``PATH`` variable. +latest user and system environment variables, such as the ``Path`` variable. Patch by Victor Stinner. From f79fd793a3960dda2bc446d7df272387ebcfae1e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jun 2024 11:43:50 +0200 Subject: [PATCH 5/8] Add docstring --- Lib/os.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/os.py b/Lib/os.py index b434e2ad5ca9a9..fe2642d400b7fe 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -832,6 +832,9 @@ def decode(value): if _exists("_get_user_default_environ"): def get_user_default_environ(): + """ + Get the default environment of the current process user as a dictionary. + """ env = {} env_str = _get_user_default_environ() for entry in env_str.split('\0'): From 81ea5b02ee2f366425528f3772e1a859551d58f1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jun 2024 14:04:13 +0200 Subject: [PATCH 6/8] sync with convertenviron(); add comments --- Lib/os.py | 10 +++++++--- Modules/posixmodule.c | 8 +++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index fe2642d400b7fe..75071a467a0913 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -835,15 +835,19 @@ def get_user_default_environ(): """ Get the default environment of the current process user as a dictionary. """ - env = {} env_str = _get_user_default_environ() + env = {} for entry in env_str.split('\0'): parts = entry.split('=', 1) if len(parts) != 2: - # Skip names that begin with '=' + # Silently skip variable with no name continue + + # Note: Allow empty variable name name, value = parts - env[name] = value + + # If a variable is set twice, use the first value + env.setdefault(name, value) return env diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a03639395d7bbb..a862fef0845ba1 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1682,8 +1682,12 @@ convertenviron(void) #else const char *p = strchr(*e, '='); #endif - if (p == NULL) + if (p == NULL) { + // Silently skip variable with no name continue; + } + + // Note: Allow empty variable name #ifdef MS_WINDOWS k = PyUnicode_FromWideChar(*e, (Py_ssize_t)(p-*e)); #else @@ -1703,6 +1707,8 @@ convertenviron(void) Py_DECREF(d); return NULL; } + + // If a variable is set twice, use the first value if (PyDict_SetDefaultRef(d, k, v, NULL) < 0) { Py_DECREF(v); Py_DECREF(k); From 6d1c3b1bcaf3926f6cd5a0ce65d7f392a0f032a2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jun 2024 14:12:06 +0200 Subject: [PATCH 7/8] Add userenv.lib dependency --- Modules/posixmodule.c | 25 +++---------------------- PCbuild/_freeze_module.vcxproj | 2 +- PCbuild/pythoncore.vcxproj | 2 +- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a862fef0845ba1..ff9879f400fb6f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -39,6 +39,7 @@ # include "osdefs.h" // SEP # include // SetEntriesInAcl # include // SDDL_REVISION_1 +# include // CreateEnvironmentBlock() # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM) # define HAVE_SYMLINK # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_SYSTEM */ @@ -16840,26 +16841,6 @@ static PyObject * os__get_user_default_environ_impl(PyObject *module) /*[clinic end generated code: output=6cce8c186a556ef0 input=e15b3d87fce12734]*/ { - HINSTANCE hUserEnv = LoadLibraryW(L"USERENV"); - if (!hUserEnv) { - goto error; - } - - HINSTANCE (CALLBACK *pCreateEnvironmentBlock) (LPVOID, HANDLE, BOOL); - HINSTANCE (CALLBACK *pDestroyEnvironmentBlock) (LPVOID); - - *(FARPROC*)&pCreateEnvironmentBlock = GetProcAddress(hUserEnv, - "CreateEnvironmentBlock"); - if (!pCreateEnvironmentBlock) { - goto error; - } - - *(FARPROC*)&pDestroyEnvironmentBlock = GetProcAddress(hUserEnv, - "DestroyEnvironmentBlock"); - if (!pDestroyEnvironmentBlock) { - goto error; - } - HANDLE htoken; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, @@ -16868,7 +16849,7 @@ os__get_user_default_environ_impl(PyObject *module) } PWCHAR env_str; - if (!pCreateEnvironmentBlock(&env_str, htoken, 0)) { + if (!CreateEnvironmentBlock(&env_str, htoken, 0)) { CloseHandle(htoken); goto error; } @@ -16881,7 +16862,7 @@ os__get_user_default_environ_impl(PyObject *module) } PyObject *str = PyUnicode_FromWideChar(env_str, len); - if (!pDestroyEnvironmentBlock(env_str)) { + if (!DestroyEnvironmentBlock(env_str)) { Py_XDECREF(str); goto error; } diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index e5e18de60ec349..4d4c9a9665f203 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -95,7 +95,7 @@ Console - version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;%(AdditionalDependencies) + version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;userenv.lib;%(AdditionalDependencies) Default diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 96960f0579a936..7b979538879efe 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -108,7 +108,7 @@ _Py_TIER2=$(UseTIER2);%(PreprocessorDefinitions) - version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;%(AdditionalDependencies) + version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;userenv.lib;%(AdditionalDependencies) From d6edf6423d57be43eda88b03f80582773e7cc6f9 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jun 2024 15:57:19 +0200 Subject: [PATCH 8/8] Ignore empty name; add tests --- Lib/os.py | 4 +++- Lib/test/test_os.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Lib/os.py b/Lib/os.py index 75071a467a0913..9e4988b315fd2f 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -843,8 +843,10 @@ def get_user_default_environ(): # Silently skip variable with no name continue - # Note: Allow empty variable name name, value = parts + if not name: + # Silently skip variable with empty name + continue # If a variable is set twice, use the first value env.setdefault(name, value) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 6905fe9ed94fd5..293dae9cecd938 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -31,6 +31,7 @@ import unittest import uuid import warnings +from unittest import mock from test import support from test.support import import_helper from test.support import os_helper @@ -1355,6 +1356,24 @@ def test_get_user_default_environ(self): self.assertIsInstance(value, str) self.assertTrue(bool(name), name) # must be not empty + # test variable defined twice + env_str = 'A=1\0B=2\0A=3\0' + with mock.patch('os._get_user_default_environ', return_value=env_str): + env = os.get_user_default_environ() + self.assertEqual(env, {'A': '1', 'B': '2'}) + + # variable name with empty names are silently ignored + env_str = '=1\0A=2\0' + with mock.patch('os._get_user_default_environ', return_value=env_str): + env = os.get_user_default_environ() + self.assertEqual(env, {'A': '2'}) + + # variable with no name + env_str = 'ABC\0A=2\0' + with mock.patch('os._get_user_default_environ', return_value=env_str): + env = os.get_user_default_environ() + self.assertEqual(env, {'A': '2'}) + class WalkTests(unittest.TestCase): """Tests for os.walk()."""