gh-131277: allow EnvironmentVarGuard to unset more than one environment variable at once (#131280)

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
Bénédikt Tran 2025-03-16 14:09:33 +01:00 committed by GitHub
parent 9558d22ac3
commit 3185e3115c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 38 additions and 45 deletions

View File

@ -1435,9 +1435,12 @@ The :mod:`test.support.os_helper` module provides support for os tests.
``value``. ``value``.
.. method:: EnvironmentVarGuard.unset(envvar) .. method:: EnvironmentVarGuard.unset(envvar, *others)
Temporarily unset the environment variable ``envvar``. Temporarily unset one or more environment variables.
.. versionchanged:: next
More than one environment variable can be unset.
.. function:: can_symlink() .. function:: can_symlink()

View File

@ -2855,8 +2855,7 @@ def no_color():
swap_attr(_colorize, "can_colorize", lambda file=None: False), swap_attr(_colorize, "can_colorize", lambda file=None: False),
EnvironmentVarGuard() as env, EnvironmentVarGuard() as env,
): ):
for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}: env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS")
env.unset(var)
env.set("NO_COLOR", "1") env.set("NO_COLOR", "1")
yield yield

View File

@ -720,9 +720,10 @@ else:
class EnvironmentVarGuard(collections.abc.MutableMapping): class EnvironmentVarGuard(collections.abc.MutableMapping):
"""Class to help protect the environment variable properly.
"""Class to help protect the environment variable properly. Can be used as Can be used as a context manager.
a context manager.""" """
def __init__(self): def __init__(self):
self._environ = os.environ self._environ = os.environ
@ -756,8 +757,10 @@ class EnvironmentVarGuard(collections.abc.MutableMapping):
def set(self, envvar, value): def set(self, envvar, value):
self[envvar] = value self[envvar] = value
def unset(self, envvar): def unset(self, envvar, /, *envvars):
del self[envvar] """Unset one or more environment variables."""
for ev in (envvar, *envvars):
del self[ev]
def copy(self): def copy(self):
# We do what os.environ.copy() does. # We do what os.environ.copy() does.

View File

@ -10,8 +10,7 @@ from test.support.os_helper import EnvironmentVarGuard
@contextlib.contextmanager @contextlib.contextmanager
def clear_env(): def clear_env():
with EnvironmentVarGuard() as mock_env: with EnvironmentVarGuard() as mock_env:
for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS", "TERM": mock_env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS", "TERM")
mock_env.unset(var)
yield mock_env yield mock_env

View File

@ -20,12 +20,13 @@ class Test_OSXSupport(unittest.TestCase):
self.prog_name = 'bogus_program_xxxx' self.prog_name = 'bogus_program_xxxx'
self.temp_path_dir = os.path.abspath(os.getcwd()) self.temp_path_dir = os.path.abspath(os.getcwd())
self.env = self.enterContext(os_helper.EnvironmentVarGuard()) self.env = self.enterContext(os_helper.EnvironmentVarGuard())
for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS',
self.env.unset(
'CFLAGS', 'LDFLAGS', 'CPPFLAGS',
'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC', 'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC',
'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', 'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS'): 'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS'
if cv in self.env: )
self.env.unset(cv)
def add_expected_saved_initial_values(self, config_vars, expected_vars): def add_expected_saved_initial_values(self, config_vars, expected_vars):
# Ensure that the initial values for all modified config vars # Ensure that the initial values for all modified config vars

View File

@ -1568,8 +1568,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
# try to get a user preferred encoding different than the current # try to get a user preferred encoding different than the current
# locale encoding to check that open() uses the current locale # locale encoding to check that open() uses the current locale
# encoding and not the user preferred encoding # encoding and not the user preferred encoding
for key in ('LC_ALL', 'LANG', 'LC_CTYPE'): env.unset('LC_ALL', 'LANG', 'LC_CTYPE')
env.unset(key)
self.write_testfile() self.write_testfile()
current_locale_encoding = locale.getencoding() current_locale_encoding = locale.getencoding()

View File

@ -13,7 +13,6 @@ sentinel = object()
class GetoptTests(unittest.TestCase): class GetoptTests(unittest.TestCase):
def setUp(self): def setUp(self):
self.env = self.enterContext(EnvironmentVarGuard()) self.env = self.enterContext(EnvironmentVarGuard())
if "POSIXLY_CORRECT" in self.env:
del self.env["POSIXLY_CORRECT"] del self.env["POSIXLY_CORRECT"]
def assertError(self, *args, **kwargs): def assertError(self, *args, **kwargs):

View File

@ -2896,8 +2896,7 @@ class TextIOWrapperTest(unittest.TestCase):
# try to get a user preferred encoding different than the current # try to get a user preferred encoding different than the current
# locale encoding to check that TextIOWrapper() uses the current # locale encoding to check that TextIOWrapper() uses the current
# locale encoding and not the user preferred encoding # locale encoding and not the user preferred encoding
for key in ('LC_ALL', 'LANG', 'LC_CTYPE'): env.unset('LC_ALL', 'LANG', 'LC_CTYPE')
env.unset(key)
current_locale_encoding = locale.getencoding() current_locale_encoding = locale.getencoding()
b = self.BytesIO() b = self.BytesIO()

View File

@ -500,9 +500,7 @@ class TestMiscellaneous(unittest.TestCase):
try: try:
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
for key in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'): env.unset('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE')
env.unset(key)
env.set('LC_CTYPE', 'UTF-8') env.set('LC_CTYPE', 'UTF-8')
with check_warnings(('', DeprecationWarning)): with check_warnings(('', DeprecationWarning)):

View File

@ -3232,7 +3232,7 @@ class PathTest(PurePathTest):
p7 = P(f'~{fakename}/Documents') p7 = P(f'~{fakename}/Documents')
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
env.pop('HOME', None) env.unset('HOME')
self.assertEqual(p1.expanduser(), P(userhome) / 'Documents') self.assertEqual(p1.expanduser(), P(userhome) / 'Documents')
self.assertEqual(p2.expanduser(), P(userhome) / 'Documents') self.assertEqual(p2.expanduser(), P(userhome) / 'Documents')
@ -3345,10 +3345,7 @@ class PathTest(PurePathTest):
def test_expanduser_windows(self): def test_expanduser_windows(self):
P = self.cls P = self.cls
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
env.pop('HOME', None) env.unset('HOME', 'USERPROFILE', 'HOMEPATH', 'HOMEDRIVE')
env.pop('USERPROFILE', None)
env.pop('HOMEPATH', None)
env.pop('HOMEDRIVE', None)
env['USERNAME'] = 'alice' env['USERNAME'] = 'alice'
# test that the path returns unchanged # test that the path returns unchanged
@ -3386,8 +3383,7 @@ class PathTest(PurePathTest):
env['HOMEPATH'] = 'Users\\alice' env['HOMEPATH'] = 'Users\\alice'
check() check()
env.pop('HOMEDRIVE', None) env.unset('HOMEDRIVE', 'HOMEPATH')
env.pop('HOMEPATH', None)
env['USERPROFILE'] = 'C:\\Users\\alice' env['USERPROFILE'] = 'C:\\Users\\alice'
check() check()

View File

@ -368,7 +368,6 @@ class PlatformTest(unittest.TestCase):
with support.swap_attr(platform, '_wmi_query', raises_oserror): with support.swap_attr(platform, '_wmi_query', raises_oserror):
with os_helper.EnvironmentVarGuard() as environ: with os_helper.EnvironmentVarGuard() as environ:
try: try:
if 'PROCESSOR_ARCHITEW6432' in environ:
del environ['PROCESSOR_ARCHITEW6432'] del environ['PROCESSOR_ARCHITEW6432']
environ['PROCESSOR_ARCHITECTURE'] = 'foo' environ['PROCESSOR_ARCHITECTURE'] = 'foo'
platform._uname_cache = None platform._uname_cache = None

View File

@ -422,7 +422,6 @@ class ParseArgsTestCase(unittest.TestCase):
# which has an unclear API # which has an unclear API
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
# Ignore SOURCE_DATE_EPOCH env var if it's set # Ignore SOURCE_DATE_EPOCH env var if it's set
if 'SOURCE_DATE_EPOCH' in env:
del env['SOURCE_DATE_EPOCH'] del env['SOURCE_DATE_EPOCH']
regrtest = main.Regrtest(ns) regrtest = main.Regrtest(ns)

View File

@ -2458,7 +2458,7 @@ class TestWhich(BaseTest, unittest.TestCase):
def test_environ_path_missing(self): def test_environ_path_missing(self):
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
env.pop('PATH', None) del env['PATH']
# without confstr # without confstr
with unittest.mock.patch('os.confstr', side_effect=ValueError, \ with unittest.mock.patch('os.confstr', side_effect=ValueError, \
@ -2484,7 +2484,7 @@ class TestWhich(BaseTest, unittest.TestCase):
def test_empty_path_no_PATH(self): def test_empty_path_no_PATH(self):
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
env.pop('PATH', None) del env['PATH']
rv = shutil.which(self.file) rv = shutil.which(self.file)
self.assertIsNone(rv) self.assertIsNone(rv)
@ -3446,8 +3446,7 @@ class TestGetTerminalSize(unittest.TestCase):
expected = (int(size[1]), int(size[0])) # reversed order expected = (int(size[1]), int(size[0])) # reversed order
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
del env['LINES'] env.unset('LINES', 'COLUMNS')
del env['COLUMNS']
actual = shutil.get_terminal_size() actual = shutil.get_terminal_size()
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@ -3455,8 +3454,7 @@ class TestGetTerminalSize(unittest.TestCase):
@unittest.skipIf(support.is_wasi, "WASI has no /dev/null") @unittest.skipIf(support.is_wasi, "WASI has no /dev/null")
def test_fallback(self): def test_fallback(self):
with os_helper.EnvironmentVarGuard() as env: with os_helper.EnvironmentVarGuard() as env:
del env['LINES'] env.unset('LINES', 'COLUMNS')
del env['COLUMNS']
# sys.__stdout__ has no fileno() # sys.__stdout__ has no fileno()
with support.swap_attr(sys, '__stdout__', None): with support.swap_attr(sys, '__stdout__', None):

View File

@ -355,9 +355,7 @@ class HelperFunctionsTests(unittest.TestCase):
with EnvironmentVarGuard() as environ, \ with EnvironmentVarGuard() as environ, \
mock.patch('os.path.expanduser', lambda path: path): mock.patch('os.path.expanduser', lambda path: path):
environ.unset('PYTHONUSERBASE', 'APPDATA')
del environ['PYTHONUSERBASE']
del environ['APPDATA']
user_base = site.getuserbase() user_base = site.getuserbase()
self.assertTrue(user_base.startswith('~' + os.sep), self.assertTrue(user_base.startswith('~' + os.sep),

View File

@ -0,0 +1,3 @@
Allow to unset one or more environment variables at once via
:meth:`EnvironmentVarGuard.unset()
<test.support.os_helper.EnvironmentVarGuard.unset>`. Patch by Bénédikt Tran.