Skip to content

Commit 13052d0

Browse files
Rework autouse pytest fixtures to isolate tests from user configurations
Add a new automatically applied pytest fixture to prevent tests from using or modifying the user's actual global or system configuration.
1 parent 594a789 commit 13052d0

File tree

1 file changed

+94
-55
lines changed

1 file changed

+94
-55
lines changed

tests/conftest.py

Lines changed: 94 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,65 @@
6261
WINDOWS = (platform.system() == 'Windows')
6362

6463
#
65-
# Test fixtures
64+
# Contextmanager
65+
#
66+
67+
@contextlib.contextmanager
68+
def update_env(env: dict, append=False, prepend=False, sep=os.pathsep):
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+
The value can also be either appended (append=True) or prepended
77+
(prepend=True) to existing, using the given separator (sep).
78+
"""
79+
env_bak = dict(os.environ)
80+
env_vars = {}
81+
for k,v in env.items():
82+
current_v = os.getenv(k, None)
83+
# unset if value is None
84+
if v is None and k in os.environ:
85+
del os.environ[k]
86+
# append value to existing value
87+
elif append and current_v is not None:
88+
env_vars[k] = f'{current_v}{sep}{v}'
89+
# prepend value to existing value
90+
elif prepend and current_v is not None:
91+
env_vars[k] = f'{v}{sep}{current_v}'
92+
# set to value
93+
else:
94+
env_vars[k] = f'{v}'
95+
os.environ.update(env_vars)
96+
try:
97+
yield
98+
finally:
99+
os.environ.clear()
100+
os.environ.update(env_bak)
101+
102+
103+
104+
105+
@contextlib.contextmanager
106+
def chdir(path):
107+
"""
108+
Temporarily change the current working directory.
109+
This context manager changes the current working directory to `path`
110+
for the duration of the `with` block. After the block exits, the
111+
working directory is restored to its original value.
112+
"""
113+
oldpwd = os.getcwd()
114+
os.chdir(path)
115+
try:
116+
yield
117+
finally:
118+
os.chdir(oldpwd)
119+
120+
121+
#
122+
# Test fixtures (autouse=True)
66123
#
67124

68125
@pytest.fixture(scope='session', autouse=True)
@@ -86,6 +143,34 @@ def _check_git_capabilities(tmpdir_factory):
86143
except subprocess.CalledProcessError:
87144
pass
88145

146+
@pytest.fixture(autouse=True)
147+
def independent_global_and_system_config(tmpdir_factory):
148+
# Fixture to ensure that the user's actual global and system configuration
149+
# files are neither used nor touched during test.
150+
#
151+
# We also set ZEPHYR_BASE (to avoid complaints in subcommand
152+
# stderr), but to a spurious location (so that attempts to read
153+
# from inside of it are caught here).
154+
#
155+
tmpdir = Path(tmpdir_factory.mktemp("test-configs"))
156+
157+
# config paths
158+
system = tmpdir / 'config.system'
159+
glbl = tmpdir / 'config.global'
160+
161+
# run with environment variables set
162+
with update_env({
163+
'WEST_CONFIG_SYSTEM' : str(system),
164+
'WEST_CONFIG_GLOBAL' : str(glbl),
165+
'ZEPHYR_BASE' : str(tmpdir / 'no-zephyr-here'),
166+
}):
167+
yield
168+
169+
170+
#
171+
# Test fixtures
172+
#
173+
89174
@pytest.fixture(scope='session')
90175
def _session_repos(tmp_path_factory):
91176
'''Just a helper, do not use directly.'''
@@ -237,66 +322,20 @@ def west_init_tmpdir(repos_tmpdir):
237322
@pytest.fixture
238323
def config_tmpdir(tmpdir):
239324
# 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).
325+
# environmental overrides in place so local configuration file
326+
# live inside of it. This ensures we don't touch the user's local config.
247327
#
248328
# Using this makes the tests run faster than if we used
249329
# west_init_tmpdir from conftest.py, and also ensures that the
250330
# configuration code doesn't depend on features like the existence
251331
# 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)
260332

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

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.
336+
# run the test within tmpdir and with env variable set
337+
with chdir(tmpdir), update_env({'WEST_CONFIG_LOCAL' : local_config}):
284338
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']
300339

301340
#
302341
# Helper functions

0 commit comments

Comments
 (0)