Skip to content

Commit 4596dca

Browse files
thorsten-kleinpdgendt
authored andcommitted
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 a3af732 commit 4596dca

File tree

1 file changed

+86
-53
lines changed

1 file changed

+86
-53
lines changed

tests/conftest.py

Lines changed: 86 additions & 53 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+
68+
@contextlib.contextmanager
69+
def update_env(env: dict[str, str | None]):
70+
"""
71+
Temporarily update the process environment variables.
72+
This context manager updates `os.environ` with the key-value pairs
73+
provided in the `env` dictionary for the duration of the `with` block.
74+
The existing environment is preserved and fully restored when the block
75+
exits. 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

@@ -90,6 +138,35 @@ def _check_git_capabilities(tmpdir_factory):
90138
pass
91139

92140

141+
@pytest.fixture(autouse=True)
142+
def independent_global_and_system_config(tmpdir_factory):
143+
# Fixture to ensure that the user's actual global and system configuration
144+
# files are neither used nor touched during test.
145+
#
146+
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
147+
# stderr), but to a spurious location (so that attempts to read
148+
# from inside of it are caught here).
149+
#
150+
tmpdir = Path(tmpdir_factory.mktemp("test-configs"))
151+
152+
# config paths
153+
system = tmpdir / 'config.system'
154+
glbl = tmpdir / 'config.global'
155+
156+
# run with environment variables set
157+
with update_env({
158+
'WEST_CONFIG_SYSTEM': str(system),
159+
'WEST_CONFIG_GLOBAL': str(glbl),
160+
'ZEPHYR_BASE': str(tmpdir / 'no-zephyr-here'),
161+
}):
162+
yield
163+
164+
165+
#
166+
# Test fixtures
167+
#
168+
169+
93170
@pytest.fixture(scope='session')
94171
def _session_repos(tmp_path_factory):
95172
'''Just a helper, do not use directly.'''
@@ -254,64 +331,20 @@ def west_init_tmpdir(repos_tmpdir):
254331
@pytest.fixture
255332
def config_tmpdir(tmpdir):
256333
# Fixture for running from a temporary directory with
257-
# environmental overrides in place so all configuration files
258-
# live inside of it. This makes sure we don't touch
259-
# the user's actual files.
260-
#
261-
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
262-
# stderr), but to a spurious location (so that attempts to read
263-
# from inside of it are caught here).
334+
# environmental overrides in place so local configuration file
335+
# live inside of it. This ensures we don't touch the user's local config.
264336
#
265337
# Using this makes the tests run faster than if we used
266338
# west_init_tmpdir from conftest.py, and also ensures that the
267339
# configuration code doesn't depend on features like the existence
268340
# of a manifest file, helping separate concerns.
269-
system = tmpdir / 'config.system'
270-
glbl = tmpdir / 'config.global'
271-
local = tmpdir / 'config.local'
272-
273-
os.environ['ZEPHYR_BASE'] = str(tmpdir.join('no-zephyr-here'))
274-
os.environ['WEST_CONFIG_SYSTEM'] = str(system)
275-
os.environ['WEST_CONFIG_GLOBAL'] = str(glbl)
276-
os.environ['WEST_CONFIG_LOCAL'] = str(local)
277341

278-
# Make sure our environment variables (as well as other topdirs)
279-
# are respected from tmpdir, and we aren't going to touch the
280-
# user's real files.
281-
start_dir = os.getcwd()
282-
tmpdir.chdir()
342+
# determine a local config in tmp dir
343+
local_config = tmpdir / 'config.local'
283344

284-
try:
285-
assert config._location(config.ConfigFile.SYSTEM) == str(system)
286-
assert config._location(config.ConfigFile.GLOBAL) == str(glbl)
287-
td = tmpdir / 'test-topdir'
288-
td.ensure(dir=True)
289-
(td / '.west').ensure(dir=True)
290-
(td / '.west' / 'config').ensure(file=True)
291-
assert config._location(config.ConfigFile.LOCAL) == str(local)
292-
assert config._location(config.ConfigFile.LOCAL, topdir=str(td)) == str(local)
293-
td.remove(rec=1)
294-
assert not td.exists()
295-
296-
assert not local.exists()
297-
298-
# All clear: switch to the temporary directory and run the test.
345+
# run the test within tmpdir and with env variable set
346+
with chdir(tmpdir), update_env({'WEST_CONFIG_LOCAL': str(local_config)}):
299347
yield tmpdir
300-
finally:
301-
# Go back to where we started, for repeatability of results.
302-
os.chdir(start_dir)
303-
304-
# Clean up after ourselves so other test cases don't know
305-
# about this tmpdir. It's OK if test cases deleted these
306-
# settings already.
307-
if 'ZEPHYR_BASE' in os.environ:
308-
del os.environ['ZEPHYR_BASE']
309-
if 'WEST_CONFIG_SYSTEM' in os.environ:
310-
del os.environ['WEST_CONFIG_SYSTEM']
311-
if 'WEST_CONFIG_GLOBAL' in os.environ:
312-
del os.environ['WEST_CONFIG_GLOBAL']
313-
if 'WEST_CONFIG_LOCAL' in os.environ:
314-
del os.environ['WEST_CONFIG_LOCAL']
315348

316349

317350
#

0 commit comments

Comments
 (0)