Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 86 additions & 53 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

import contextlib
import os
import platform
import shutil
Expand All @@ -13,8 +14,6 @@

import pytest

from west import configuration as config

GIT = shutil.which('git')

# Git capabilities are discovered at runtime in
Expand Down Expand Up @@ -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)
#


Expand Down Expand Up @@ -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.'''
Expand Down Expand Up @@ -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']


#
Expand Down
27 changes: 13 additions & 14 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down
8 changes: 3 additions & 5 deletions tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
create_repo,
create_workspace,
rev_parse,
update_env,
)

from west.configuration import ConfigFile, Configuration, MalformedConfig
Expand Down Expand Up @@ -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():
Expand Down