diff --git a/tests/conftest.py b/tests/conftest.py index 9273c7de7..114d4d7fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 +import contextlib import os import platform import shutil @@ -13,8 +14,6 @@ import pytest -from west import configuration as config - GIT = shutil.which('git') # Git capabilities are discovered at runtime in @@ -62,7 +61,56 @@ WINDOWS = platform.system() == 'Windows' # -# Test fixtures +# Contextmanager +# + + +@contextlib.contextmanager +def update_env(env: dict[str, str | None]): + """ + Temporarily update the process environment variables. + This context manager updates `os.environ` with the key-value pairs + provided in the `env` dictionary for the duration of the `with` block. + The existing environment is preserved and fully restored when the block + exits. If the value is set to None, the environment variable is unset. + """ + env_bak = dict(os.environ) + env_vars = {} + for k, v in env.items(): + # unset if value is None + if v is None and k in os.environ: + del os.environ[k] + # set env variable to new value only if v is not None + elif v is not None: + env_vars[k] = v + # apply the new environment + os.environ.update(env_vars) + try: + yield + finally: + # reset to previous environment + os.environ.clear() + os.environ.update(env_bak) + + +@contextlib.contextmanager +def chdir(path): + """ + Temporarily change the current working directory. + This context manager changes the current working directory to `path` + for the duration of the `with` block. After the block exits, the + working directory is restored to its original value. + """ + oldpwd = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(oldpwd) + + +# +# Test fixtures (autouse=True) # @@ -90,6 +138,35 @@ def _check_git_capabilities(tmpdir_factory): pass +@pytest.fixture(autouse=True) +def independent_global_and_system_config(tmpdir_factory): + # Fixture to ensure that the user's actual global and system configuration + # files are neither used nor touched during test. + # + # We also set ZEPHYR_BASE (to avoid complaints in subcommand + # stderr), but to a spurious location (so that attempts to read + # from inside of it are caught here). + # + tmpdir = Path(tmpdir_factory.mktemp("test-configs")) + + # config paths + system = tmpdir / 'config.system' + glbl = tmpdir / 'config.global' + + # run with environment variables set + with update_env({ + 'WEST_CONFIG_SYSTEM': str(system), + 'WEST_CONFIG_GLOBAL': str(glbl), + 'ZEPHYR_BASE': str(tmpdir / 'no-zephyr-here'), + }): + yield + + +# +# Test fixtures +# + + @pytest.fixture(scope='session') def _session_repos(tmp_path_factory): '''Just a helper, do not use directly.''' @@ -254,64 +331,20 @@ def west_init_tmpdir(repos_tmpdir): @pytest.fixture def config_tmpdir(tmpdir): # Fixture for running from a temporary directory with - # environmental overrides in place so all configuration files - # live inside of it. This makes sure we don't touch - # the user's actual files. - # - # We also set ZEPHYR_BASE (to avoid complaints in subcommand - # stderr), but to a spurious location (so that attempts to read - # from inside of it are caught here). + # environmental overrides in place so local configuration file + # live inside of it. This ensures we don't touch the user's local config. # # Using this makes the tests run faster than if we used # west_init_tmpdir from conftest.py, and also ensures that the # configuration code doesn't depend on features like the existence # of a manifest file, helping separate concerns. - system = tmpdir / 'config.system' - glbl = tmpdir / 'config.global' - local = tmpdir / 'config.local' - - os.environ['ZEPHYR_BASE'] = str(tmpdir.join('no-zephyr-here')) - os.environ['WEST_CONFIG_SYSTEM'] = str(system) - os.environ['WEST_CONFIG_GLOBAL'] = str(glbl) - os.environ['WEST_CONFIG_LOCAL'] = str(local) - # Make sure our environment variables (as well as other topdirs) - # are respected from tmpdir, and we aren't going to touch the - # user's real files. - start_dir = os.getcwd() - tmpdir.chdir() + # determine a local config in tmp dir + local_config = tmpdir / 'config.local' - try: - assert config._location(config.ConfigFile.SYSTEM) == str(system) - assert config._location(config.ConfigFile.GLOBAL) == str(glbl) - td = tmpdir / 'test-topdir' - td.ensure(dir=True) - (td / '.west').ensure(dir=True) - (td / '.west' / 'config').ensure(file=True) - assert config._location(config.ConfigFile.LOCAL) == str(local) - assert config._location(config.ConfigFile.LOCAL, topdir=str(td)) == str(local) - td.remove(rec=1) - assert not td.exists() - - assert not local.exists() - - # All clear: switch to the temporary directory and run the test. + # run the test within tmpdir and with env variable set + with chdir(tmpdir), update_env({'WEST_CONFIG_LOCAL': str(local_config)}): yield tmpdir - finally: - # Go back to where we started, for repeatability of results. - os.chdir(start_dir) - - # Clean up after ourselves so other test cases don't know - # about this tmpdir. It's OK if test cases deleted these - # settings already. - if 'ZEPHYR_BASE' in os.environ: - del os.environ['ZEPHYR_BASE'] - if 'WEST_CONFIG_SYSTEM' in os.environ: - del os.environ['WEST_CONFIG_SYSTEM'] - if 'WEST_CONFIG_GLOBAL' in os.environ: - del os.environ['WEST_CONFIG_GLOBAL'] - if 'WEST_CONFIG_LOCAL' in os.environ: - del os.environ['WEST_CONFIG_LOCAL'] # diff --git a/tests/test_config.py b/tests/test_config.py index 3905bea88..8ff57d76a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,7 +9,7 @@ from typing import Any import pytest -from conftest import cmd, cmd_raises +from conftest import cmd, cmd_raises, update_env from west import configuration as config from west.util import PathType @@ -232,19 +232,18 @@ def test_local_creation_with_topdir(): # The autouse fixture at the top of this file has set up an # environment variable for our local config file. Disable it # to make sure the API works with a 'real' topdir. - del os.environ['WEST_CONFIG_LOCAL'] - - # We should be able to write into our topdir's config file now. - update_testcfg('pytest', 'key', 'val', configfile=LOCAL, topdir=str(topdir)) - assert not system.exists() - assert not glbl.exists() - assert not local.exists() - assert topdir_config.exists() - - assert cfg(f=ALL, topdir=str(topdir))['pytest']['key'] == 'val' - assert 'pytest' not in cfg(f=SYSTEM) - assert 'pytest' not in cfg(f=GLOBAL) - assert cfg(f=LOCAL, topdir=str(topdir))['pytest']['key'] == 'val' + with update_env({'WEST_CONFIG_LOCAL': None}): + # We should be able to write into our topdir's config file now. + update_testcfg('pytest', 'key', 'val', configfile=LOCAL, topdir=str(topdir)) + assert not system.exists() + assert not glbl.exists() + assert not local.exists() + assert topdir_config.exists() + + assert cfg(f=ALL, topdir=str(topdir))['pytest']['key'] == 'val' + assert 'pytest' not in cfg(f=SYSTEM) + assert 'pytest' not in cfg(f=GLOBAL) + assert cfg(f=LOCAL, topdir=str(topdir))['pytest']['key'] == 'val' def test_append(): diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 9e1d2ad7e..eceb72ad9 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -31,6 +31,7 @@ create_repo, create_workspace, rev_parse, + update_env, ) from west.configuration import ConfigFile, Configuration, MalformedConfig @@ -172,12 +173,9 @@ def test_manifest_from_file_with_fall_back(manifest_repo): ''') repo_abspath = Path(str(manifest_repo)) os.chdir(repo_abspath.parent.parent) # this is the tmp_workspace dir - try: - os.environ['ZEPHYR_BASE'] = os.fspath(manifest_repo) + with update_env({'ZEPHYR_BASE': os.fspath(manifest_repo)}): manifest = MF() - assert Path(manifest.repo_abspath) == repo_abspath - finally: - del os.environ['ZEPHYR_BASE'] + assert Path(manifest.repo_abspath) == repo_abspath def test_validate():