Skip to content

Commit e0cefc8

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 a3af732 commit e0cefc8

File tree

1 file changed

+87
-53
lines changed

1 file changed

+87
-53
lines changed

tests/conftest.py

Lines changed: 87 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,57 @@
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+
Any existing environment variables are preserved and restored when
75+
the block exits.
76+
If the value is set to None, the environment variable is unset.
77+
"""
78+
env_bak = dict(os.environ)
79+
env_vars = {}
80+
for k, v in env.items():
81+
# unset if value is None
82+
if v is None and k in os.environ:
83+
del os.environ[k]
84+
# set env variable to new value only if v is not None
85+
elif v is not None:
86+
env_vars[k] = v
87+
# apply the new environment
88+
os.environ.update(env_vars)
89+
try:
90+
yield
91+
finally:
92+
# reset to previous environment
93+
os.environ.clear()
94+
os.environ.update(env_bak)
95+
96+
97+
@contextlib.contextmanager
98+
def chdir(path):
99+
"""
100+
Temporarily change the current working directory.
101+
This context manager changes the current working directory to `path`
102+
for the duration of the `with` block. After the block exits, the
103+
working directory is restored to its original value.
104+
"""
105+
oldpwd = os.getcwd()
106+
os.chdir(path)
107+
try:
108+
yield
109+
finally:
110+
os.chdir(oldpwd)
111+
112+
113+
#
114+
# Test fixtures (autouse=True)
66115
#
67116

68117

@@ -90,6 +139,35 @@ def _check_git_capabilities(tmpdir_factory):
90139
pass
91140

92141

142+
@pytest.fixture(autouse=True)
143+
def independent_global_and_system_config(tmpdir_factory):
144+
# Fixture to ensure that the user's actual global and system configuration
145+
# files are neither used nor touched during test.
146+
#
147+
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
148+
# stderr), but to a spurious location (so that attempts to read
149+
# from inside of it are caught here).
150+
#
151+
tmpdir = Path(tmpdir_factory.mktemp("test-configs"))
152+
153+
# config paths
154+
system = tmpdir / 'config.system'
155+
glbl = tmpdir / 'config.global'
156+
157+
# run with environment variables set
158+
with update_env({
159+
'WEST_CONFIG_SYSTEM': str(system),
160+
'WEST_CONFIG_GLOBAL': str(glbl),
161+
'ZEPHYR_BASE': str(tmpdir / 'no-zephyr-here'),
162+
}):
163+
yield
164+
165+
166+
#
167+
# Test fixtures
168+
#
169+
170+
93171
@pytest.fixture(scope='session')
94172
def _session_repos(tmp_path_factory):
95173
'''Just a helper, do not use directly.'''
@@ -254,64 +332,20 @@ def west_init_tmpdir(repos_tmpdir):
254332
@pytest.fixture
255333
def config_tmpdir(tmpdir):
256334
# 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).
335+
# environmental overrides in place so local configuration file
336+
# live inside of it. This ensures we don't touch the user's local config.
264337
#
265338
# Using this makes the tests run faster than if we used
266339
# west_init_tmpdir from conftest.py, and also ensures that the
267340
# configuration code doesn't depend on features like the existence
268341
# 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)
277342

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()
343+
# determine a local config in tmp dir
344+
local_config = tmpdir / 'config.local'
283345

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.
346+
# run the test within tmpdir and with env variable set
347+
with chdir(tmpdir), update_env({'WEST_CONFIG_LOCAL': str(local_config)}):
299348
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']
315349

316350

317351
#

0 commit comments

Comments
 (0)