Skip to content

Commit e20a95c

Browse files
Add a new pytest fixture to isolate test environment from user configs
Add a new automatically applied pytest fixture to prevent tests from using or modifying the user's actual global or system configuration. To support this, two context manager helpers are added: - `update_env(env)` — temporarily updates environment variables within a `with` block - `chdir(path)` — temporarily changes the current working directory within a `with` block
1 parent 594a789 commit e20a95c

File tree

1 file changed

+87
-55
lines changed

1 file changed

+87
-55
lines changed

tests/conftest.py

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
import contextlib
56
import os
67
import platform
78
import shutil
@@ -13,8 +14,6 @@
1314

1415
import pytest
1516

16-
from west import configuration as config
17-
1817
GIT = shutil.which('git')
1918

2019
# Git capabilities are discovered at runtime in
@@ -62,7 +61,56 @@
6261
WINDOWS = (platform.system() == 'Windows')
6362

6463
#
65-
# Test fixtures
64+
# Contextmanager
65+
#
66+
67+
@contextlib.contextmanager
68+
def update_env(env: dict[str, str | None]):
69+
"""
70+
Temporarily update the process environment variables.
71+
This context manager updates `os.environ` with the key-value pairs
72+
provided in the `env` dictionary for the duration of the `with` block.
73+
Any existing environment variables are preserved and restored when
74+
the block exits.
75+
If the value is set to None, the environment variable is unset.
76+
"""
77+
env_bak = dict(os.environ)
78+
env_vars = {}
79+
for k, v in env.items():
80+
# unset if value is None
81+
if v is None and k in os.environ:
82+
del os.environ[k]
83+
# set env variable to new value only if v is not None
84+
elif v is not None:
85+
env_vars[k] = v
86+
# apply the new environment
87+
os.environ.update(env_vars)
88+
try:
89+
yield
90+
finally:
91+
# reset to previous environment
92+
os.environ.clear()
93+
os.environ.update(env_bak)
94+
95+
96+
@contextlib.contextmanager
97+
def chdir(path):
98+
"""
99+
Temporarily change the current working directory.
100+
This context manager changes the current working directory to `path`
101+
for the duration of the `with` block. After the block exits, the
102+
working directory is restored to its original value.
103+
"""
104+
oldpwd = os.getcwd()
105+
os.chdir(path)
106+
try:
107+
yield
108+
finally:
109+
os.chdir(oldpwd)
110+
111+
112+
#
113+
# Test fixtures (autouse=True)
66114
#
67115

68116
@pytest.fixture(scope='session', autouse=True)
@@ -86,6 +134,36 @@ def _check_git_capabilities(tmpdir_factory):
86134
except subprocess.CalledProcessError:
87135
pass
88136

137+
@pytest.fixture(autouse=True)
138+
def independent_global_and_system_config(tmpdir_factory):
139+
# Fixture to ensure that the user's actual global and system configuration
140+
# files are neither used nor touched during test.
141+
#
142+
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
143+
# stderr), but to a spurious location (so that attempts to read
144+
# from inside of it are caught here).
145+
#
146+
tmpdir = Path(tmpdir_factory.mktemp("test-configs"))
147+
148+
# config paths
149+
system = tmpdir / 'config.system'
150+
glbl = tmpdir / 'config.global'
151+
152+
# run with environment variables set
153+
with update_env(
154+
{
155+
'WEST_CONFIG_SYSTEM': str(system),
156+
'WEST_CONFIG_GLOBAL': str(glbl),
157+
'ZEPHYR_BASE': str(tmpdir / 'no-zephyr-here'),
158+
}
159+
):
160+
yield
161+
162+
163+
#
164+
# Test fixtures
165+
#
166+
89167
@pytest.fixture(scope='session')
90168
def _session_repos(tmp_path_factory):
91169
'''Just a helper, do not use directly.'''
@@ -237,66 +315,20 @@ def west_init_tmpdir(repos_tmpdir):
237315
@pytest.fixture
238316
def config_tmpdir(tmpdir):
239317
# Fixture for running from a temporary directory with
240-
# environmental overrides in place so all configuration files
241-
# live inside of it. This makes sure we don't touch
242-
# the user's actual files.
243-
#
244-
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
245-
# stderr), but to a spurious location (so that attempts to read
246-
# from inside of it are caught here).
318+
# environmental overrides in place so local configuration file
319+
# live inside of it. This ensures we don't touch the user's local config.
247320
#
248321
# Using this makes the tests run faster than if we used
249322
# west_init_tmpdir from conftest.py, and also ensures that the
250323
# configuration code doesn't depend on features like the existence
251324
# of a manifest file, helping separate concerns.
252-
system = tmpdir / 'config.system'
253-
glbl = tmpdir / 'config.global'
254-
local = tmpdir / 'config.local'
255-
256-
os.environ['ZEPHYR_BASE'] = str(tmpdir.join('no-zephyr-here'))
257-
os.environ['WEST_CONFIG_SYSTEM'] = str(system)
258-
os.environ['WEST_CONFIG_GLOBAL'] = str(glbl)
259-
os.environ['WEST_CONFIG_LOCAL'] = str(local)
260325

261-
# Make sure our environment variables (as well as other topdirs)
262-
# are respected from tmpdir, and we aren't going to touch the
263-
# user's real files.
264-
start_dir = os.getcwd()
265-
tmpdir.chdir()
326+
# determine a local config in tmp dir
327+
local_config = tmpdir / 'config.local'
266328

267-
try:
268-
assert config._location(config.ConfigFile.SYSTEM) == str(system)
269-
assert config._location(config.ConfigFile.GLOBAL) == str(glbl)
270-
td = tmpdir / 'test-topdir'
271-
td.ensure(dir=True)
272-
(td / '.west').ensure(dir=True)
273-
(td / '.west' / 'config').ensure(file=True)
274-
assert config._location(config.ConfigFile.LOCAL) == str(local)
275-
assert (config._location(config.ConfigFile.LOCAL,
276-
topdir=str(td)) ==
277-
str(local))
278-
td.remove(rec=1)
279-
assert not td.exists()
280-
281-
assert not local.exists()
282-
283-
# All clear: switch to the temporary directory and run the test.
329+
# run the test within tmpdir and with env variable set
330+
with chdir(tmpdir), update_env({'WEST_CONFIG_LOCAL': str(local_config)}):
284331
yield tmpdir
285-
finally:
286-
# Go back to where we started, for repeatability of results.
287-
os.chdir(start_dir)
288-
289-
# Clean up after ourselves so other test cases don't know
290-
# about this tmpdir. It's OK if test cases deleted these
291-
# settings already.
292-
if 'ZEPHYR_BASE' in os.environ:
293-
del os.environ['ZEPHYR_BASE']
294-
if 'WEST_CONFIG_SYSTEM' in os.environ:
295-
del os.environ['WEST_CONFIG_SYSTEM']
296-
if 'WEST_CONFIG_GLOBAL' in os.environ:
297-
del os.environ['WEST_CONFIG_GLOBAL']
298-
if 'WEST_CONFIG_LOCAL' in os.environ:
299-
del os.environ['WEST_CONFIG_LOCAL']
300332

301333
#
302334
# Helper functions

0 commit comments

Comments
 (0)