summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2019-12-29 02:04:28 -0800
committerTodd Gamblin <tgamblin@llnl.gov>2019-12-30 13:01:31 -0800
commite7dc8a2bea23b013a8af4938a5eee298a1f59141 (patch)
tree78230f5c9d4da0692b6c3500c8e0b49e5d8d6a09 /lib
parente8394324720aa6bb041e3dddba46faa14626bd85 (diff)
downloadspack-e7dc8a2bea23b013a8af4938a5eee298a1f59141.tar.gz
spack-e7dc8a2bea23b013a8af4938a5eee298a1f59141.tar.bz2
spack-e7dc8a2bea23b013a8af4938a5eee298a1f59141.tar.xz
spack-e7dc8a2bea23b013a8af4938a5eee298a1f59141.zip
tests: refactor tests to avoid persistent global state
Previously, fixtures like `config`, `database`, and `store` were module-scoped, but frequently used as test function arguments. These fixtures swap out global on setup and restore them on teardown. As function arguments, they would do the right set-up, but they'd leave the global changes in place for the whole module the function lived in. This meant that if you use `config` once, other functions in the same module would inadvertently inherit the mock Spack configuration, as it would only be torn down once all tests in the module were complete. In general, we should module- or session-scope the *STATE* required for these global objects (as it's expensive to create0, but we shouldn't module-or session scope the activation/use of them, or things can get really confusing. - [x] Make generic context managers for global-modifying fixtures. - [x] Make session- and module-scoped fixtures that ONLY build filesystem state and create objects, but do not swap out any variables. - [x] Make seeparate function-scoped fixtures that *use* the session scoped fixtures and actually swap out (and back in) the global variables like `config`, `database`, and `store`. These changes make it so that global changes are *only* ever alive for a singlee test function, and we don't get weird dependencies because a global fixture hasn't been destroyed.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/test/cmd/env.py2
-rw-r--r--lib/spack/spack/test/cmd/module.py17
-rw-r--r--lib/spack/spack/test/cmd/spec.py2
-rw-r--r--lib/spack/spack/test/concretize.py2
-rw-r--r--lib/spack/spack/test/concretize_preferences.py4
-rw-r--r--lib/spack/spack/test/conftest.py138
-rw-r--r--lib/spack/spack/test/git_fetch.py6
-rw-r--r--lib/spack/spack/test/hg_fetch.py2
-rw-r--r--lib/spack/spack/test/install.py6
-rw-r--r--lib/spack/spack/test/mirror.py2
-rw-r--r--lib/spack/spack/test/packages.py2
-rw-r--r--lib/spack/spack/test/repo.py40
-rw-r--r--lib/spack/spack/test/spec_dag.py2
-rw-r--r--lib/spack/spack/test/spec_yaml.py2
-rw-r--r--lib/spack/spack/test/svn_fetch.py2
-rw-r--r--lib/spack/spack/test/url_fetch.py2
16 files changed, 141 insertions, 90 deletions
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index fa7f9a640e..9d6facfeeb 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -26,7 +26,7 @@ import spack.util.spack_json as sjson
# everything here uses the mock_env_path
pytestmark = pytest.mark.usefixtures(
- 'mutable_mock_env_path', 'config', 'mutable_mock_packages')
+ 'mutable_mock_env_path', 'config', 'mutable_mock_repo')
env = SpackCommand('env')
install = SpackCommand('install')
diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py
index 6f2ffcbf85..fa23d7d5aa 100644
--- a/lib/spack/spack/test/cmd/module.py
+++ b/lib/spack/spack/test/cmd/module.py
@@ -10,9 +10,21 @@ import pytest
import spack.main
import spack.modules
+from spack.test.conftest import use_store, use_configuration, use_repo
module = spack.main.SpackCommand('module')
+#: make sure module files are generated for all the tests here
+@pytest.fixture(scope='module', autouse=True)
+def ensure_module_files_are_there(
+ mock_repo_path, mock_store, mock_configuration):
+ """Generate module files for module tests."""
+ module = spack.main.SpackCommand('module')
+ with use_store(mock_store):
+ with use_configuration(mock_configuration):
+ with use_repo(mock_repo_path):
+ module('tcl', 'refresh', '-y')
+
def _module_files(module_type, *specs):
specs = [spack.spec.Spec(x).concretized() for x in specs]
@@ -20,11 +32,6 @@ def _module_files(module_type, *specs):
return [writer_cls(spec).layout.filename for spec in specs]
-@pytest.fixture(scope='module', autouse=True)
-def ensure_module_files_are_there(database):
- module('tcl', 'refresh', '-y')
-
-
@pytest.fixture(
params=[
['rm', 'doesnotexist'], # Try to remove a non existing module
diff --git a/lib/spack/spack/test/cmd/spec.py b/lib/spack/spack/test/cmd/spec.py
index 4637e14a1f..002e0aa1fe 100644
--- a/lib/spack/spack/test/cmd/spec.py
+++ b/lib/spack/spack/test/cmd/spec.py
@@ -9,7 +9,7 @@ import pytest
import spack.spec
from spack.main import SpackCommand
-pytestmark = pytest.mark.usefixtures('config', 'mutable_mock_packages')
+pytestmark = pytest.mark.usefixtures('config', 'mutable_mock_repo')
spec = SpackCommand('spec')
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 4b17e755ed..483cae7809 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -276,7 +276,7 @@ class TestConcretize(object):
Spec('hypre').concretize()
def test_concretize_two_virtuals_with_one_bound(
- self, mutable_mock_packages
+ self, mutable_mock_repo
):
"""Test a package with multiple virtual dependencies and one preset."""
Spec('hypre ^openblas').concretize()
diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py
index e3055192cd..13fc850c18 100644
--- a/lib/spack/spack/test/concretize_preferences.py
+++ b/lib/spack/spack/test/concretize_preferences.py
@@ -83,7 +83,7 @@ class TestConcretizePreferences(object):
'mpileaks', debug=True, opt=True, shared=False, static=False
)
- def test_preferred_compilers(self, mutable_mock_packages):
+ def test_preferred_compilers(self, mutable_mock_repo):
"""Test preferred compilers are applied correctly
"""
update_packages('mpileaks', 'compiler', ['clang@3.3'])
@@ -94,7 +94,7 @@ class TestConcretizePreferences(object):
spec = concretize('mpileaks')
assert spec.compiler == spack.spec.CompilerSpec('gcc@4.5.0')
- def test_preferred_target(self, mutable_mock_packages):
+ def test_preferred_target(self, mutable_mock_repo):
"""Test preferred compilers are applied correctly
"""
spec = concretize('mpich')
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index b23c53c20e..26cb0f453d 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -4,7 +4,7 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections
-import copy
+import contextlib
import errno
import inspect
import itertools
@@ -270,31 +270,75 @@ def _skip_if_missing_executables(request):
spack.architecture.real_platform = spack.architecture.platform
spack.architecture.platform = lambda: spack.platforms.test.Test()
-##########
-# Test-specific fixtures
-##########
+#
+# Context managers used by fixtures
+#
+# Because these context managers modify global state, they should really
+# ONLY be used persistently (i.e., around yield statements) in
+# function-scoped fixtures, OR in autouse session- or module-scoped
+# fixtures.
+#
+# If they're used in regular tests or in module-scoped fixtures that are
+# then injected as function arguments, weird things can happen, because
+# the original state won't be restored until *after* the fixture is
+# destroyed. This makes sense for an autouse fixture, where you know
+# everything in the module/session is going to need the modified
+# behavior, but modifying global state for one function in a way that
+# won't be restored until after the module or session is done essentially
+# leaves garbage behind for other tests.
+#
+# In general, we should module- or session-scope the *STATE* required for
+# these global objects, but we shouldn't module- or session-scope their
+# *USE*, or things can get really confusing.
+#
+
+@contextlib.contextmanager
+def use_configuration(config):
+ """Context manager to swap out the global Spack configuration."""
+ saved = spack.config.config
+ spack.config.config = config
+ yield
+ spack.config.config = saved
+
+@contextlib.contextmanager
+def use_store(store):
+ """Context manager to swap out the global Spack store."""
+ saved = spack.store.store
+ spack.store.store = store
+ yield
+ spack.store.store = saved
+
+
+@contextlib.contextmanager
+def use_repo(repo):
+ """Context manager to swap out the global Spack repo path."""
+ with spack.repo.swap(repo):
+ yield
+
+
+#
+# Test-specific fixtures
+#
@pytest.fixture(scope='session')
-def repo_path():
- """Session scoped RepoPath object pointing to the mock repository"""
- return spack.repo.RepoPath(spack.paths.mock_packages_path)
+def mock_repo_path():
+ yield spack.repo.RepoPath(spack.paths.mock_packages_path)
-@pytest.fixture(scope='module')
-def mock_packages(repo_path):
+@pytest.fixture(scope='function')
+def mock_packages(mock_repo_path):
"""Use the 'builtin.mock' repository instead of 'builtin'"""
- mock_repo = copy.deepcopy(repo_path)
- with spack.repo.swap(mock_repo):
- yield
+ with use_repo(mock_repo_path):
+ yield mock_repo_path
@pytest.fixture(scope='function')
-def mutable_mock_packages(mock_packages, repo_path):
+def mutable_mock_repo(mock_repo_path):
"""Function-scoped mock packages, for tests that need to modify them."""
- mock_repo = copy.deepcopy(repo_path)
- with spack.repo.swap(mock_repo):
- yield
+ mock_repo_path = spack.repo.RepoPath(spack.paths.mock_packages_path)
+ with use_repo(mock_repo_path):
+ yield mock_repo_path
@pytest.fixture(scope='session')
@@ -345,12 +389,9 @@ def configuration_dir(tmpdir_factory, linux_os):
shutil.rmtree(str(tmpdir))
-@pytest.fixture(scope='module')
-def config(configuration_dir):
- """Hooks the mock configuration files into spack.config"""
- # Set up a mock config scope
- real_configuration = spack.config.config
-
+@pytest.fixture(scope='session')
+def mock_configuration(configuration_dir):
+ """Create a persistent Configuration object from the configuration_dir."""
defaults = spack.config.InternalConfigScope(
'_builtin', spack.config.config_defaults
)
@@ -360,11 +401,14 @@ def config(configuration_dir):
for name in ['site', 'system', 'user']]
test_scopes.append(spack.config.InternalConfigScope('command_line'))
- spack.config.config = spack.config.Configuration(*test_scopes)
+ yield spack.config.Configuration(*test_scopes)
- yield spack.config.config
- spack.config.config = real_configuration
+@pytest.fixture(scope='function')
+def config(mock_configuration):
+ """This fixture activates/deactivates the mock configuration."""
+ with use_configuration(mock_configuration):
+ yield mock_configuration
@pytest.fixture(scope='function')
@@ -376,27 +420,24 @@ def mutable_config(tmpdir_factory, configuration_dir, monkeypatch):
cfg = spack.config.Configuration(
*[spack.config.ConfigScope(name, str(mutable_dir))
for name in ['site', 'system', 'user']])
- monkeypatch.setattr(spack.config, 'config', cfg)
# This is essential, otherwise the cache will create weird side effects
# that will compromise subsequent tests if compilers.yaml is modified
monkeypatch.setattr(spack.compilers, '_cache_config_file', [])
- yield spack.config.config
+ with use_configuration(cfg):
+ yield cfg
@pytest.fixture()
def mock_config(tmpdir):
"""Mocks two configuration scopes: 'low' and 'high'."""
- real_configuration = spack.config.config
-
- spack.config.config = spack.config.Configuration(
+ config = spack.config.Configuration(
*[spack.config.ConfigScope(name, str(tmpdir.join(name)))
for name in ['low', 'high']])
- yield spack.config.config
-
- spack.config.config = real_configuration
+ with use_configuration(config):
+ yield config
def _populate(mock_db):
@@ -443,30 +484,41 @@ def _store_dir_and_cache(tmpdir_factory):
return store, cache
-@pytest.fixture(scope='module')
-def database(tmpdir_factory, mock_packages, config, _store_dir_and_cache):
+@pytest.fixture(scope='session')
+def mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
+ _store_dir_and_cache):
"""Creates a read-only mock database with some packages installed note
that the ref count for dyninst here will be 3, as it's recycled
across each install.
+
+ This does not actually activate the store for use by Spack -- see the
+ ``database`` fixture for that.
+
"""
- real_store = spack.store.store
store_path, store_cache = _store_dir_and_cache
-
- mock_store = spack.store.Store(str(store_path))
- spack.store.store = mock_store
+ store = spack.store.Store(str(store_path))
# If the cache does not exist populate the store and create it
if not os.path.exists(str(store_cache.join('.spack-db'))):
- _populate(mock_store.db)
+ with use_configuration(mock_configuration):
+ with use_store(store):
+ with use_repo(mock_repo_path):
+ _populate(store.db)
store_path.copy(store_cache, mode=True, stat=True)
- # Make the database read-only to ensure we can't modify entries
+ # Make the DB filesystem read-only to ensure we can't modify entries
store_path.join('.spack-db').chmod(mode=0o555, rec=1)
- yield mock_store.db
+ yield store
store_path.join('.spack-db').chmod(mode=0o755, rec=1)
- spack.store.store = real_store
+
+
+@pytest.fixture(scope='function')
+def database(mock_store, mock_packages, config):
+ """This activates the mock store, packages, AND config."""
+ with use_store(mock_store):
+ yield mock_store.db
@pytest.fixture(scope='function')
diff --git a/lib/spack/spack/test/git_fetch.py b/lib/spack/spack/test/git_fetch.py
index ae1e027752..d0c2ca7e3d 100644
--- a/lib/spack/spack/test/git_fetch.py
+++ b/lib/spack/spack/test/git_fetch.py
@@ -88,7 +88,7 @@ def test_fetch(type_of_test,
secure,
mock_git_repository,
config,
- mutable_mock_packages,
+ mutable_mock_repo,
git_version):
"""Tries to:
@@ -137,7 +137,7 @@ def test_fetch(type_of_test,
@pytest.mark.parametrize("type_of_test", ['branch', 'commit'])
-def test_debug_fetch(type_of_test, mock_git_repository, config):
+def test_debug_fetch(mock_packages, type_of_test, mock_git_repository, config):
"""Fetch the repo with debug enabled."""
# Retrieve the right test parameters
t = mock_git_repository.checks[type_of_test]
@@ -176,7 +176,7 @@ def test_needs_stage():
@pytest.mark.parametrize("get_full_repo", [True, False])
def test_get_full_repo(get_full_repo, git_version, mock_git_repository,
- config, mutable_mock_packages):
+ config, mutable_mock_repo):
"""Ensure that we can clone a full repository."""
if git_version < ver('1.7.1'):
diff --git a/lib/spack/spack/test/hg_fetch.py b/lib/spack/spack/test/hg_fetch.py
index 109e3d9d59..5e4fde978b 100644
--- a/lib/spack/spack/test/hg_fetch.py
+++ b/lib/spack/spack/test/hg_fetch.py
@@ -29,7 +29,7 @@ def test_fetch(
secure,
mock_hg_repository,
config,
- mutable_mock_packages
+ mutable_mock_repo
):
"""Tries to:
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
index 4bed12456a..1fa892c760 100644
--- a/lib/spack/spack/test/install.py
+++ b/lib/spack/spack/test/install.py
@@ -128,7 +128,7 @@ def test_dont_add_patches_to_installed_package(install_mockery, mock_fetch):
def test_installed_dependency_request_conflicts(
- install_mockery, mock_fetch, mutable_mock_packages):
+ install_mockery, mock_fetch, mutable_mock_repo):
dependency = Spec('dependency-install')
dependency.concretize()
dependency.package.do_install()
@@ -141,7 +141,7 @@ def test_installed_dependency_request_conflicts(
def test_install_dependency_symlinks_pkg(
- install_mockery, mock_fetch, mutable_mock_packages):
+ install_mockery, mock_fetch, mutable_mock_repo):
"""Test dependency flattening/symlinks mock package."""
spec = Spec('flatten-deps')
spec.concretize()
@@ -154,7 +154,7 @@ def test_install_dependency_symlinks_pkg(
def test_flatten_deps(
- install_mockery, mock_fetch, mutable_mock_packages):
+ install_mockery, mock_fetch, mutable_mock_repo):
"""Explicitly test the flattening code for coverage purposes."""
# Unfortunately, executing the 'flatten-deps' spec's installation does
# not affect code coverage results, so be explicit here.
diff --git a/lib/spack/spack/test/mirror.py b/lib/spack/spack/test/mirror.py
index e1b31695e3..bc0c3ed0f7 100644
--- a/lib/spack/spack/test/mirror.py
+++ b/lib/spack/spack/test/mirror.py
@@ -16,7 +16,7 @@ from spack.util.executable import which
from llnl.util.filesystem import resolve_link_target_relative_to_the_link
-pytestmark = pytest.mark.usefixtures('config', 'mutable_mock_packages')
+pytestmark = pytest.mark.usefixtures('config', 'mutable_mock_repo')
# paths in repos that shouldn't be in the mirror tarballs.
exclude = ['.hg', '.git', '.svn']
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index bd4ba95053..4a6957cc5a 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -182,7 +182,7 @@ def test_urls_for_versions(mock_packages, config):
assert url == 'http://www.doesnotexist.org/url_override-0.8.1.tar.gz'
-def test_url_for_version_with_no_urls():
+def test_url_for_version_with_no_urls(mock_packages, config):
pkg = spack.repo.get('git-test')
with pytest.raises(spack.package.NoURLError):
pkg.url_for_version('1.0')
diff --git a/lib/spack/spack/test/repo.py b/lib/spack/spack/test/repo.py
index e171dbca68..35919f3462 100644
--- a/lib/spack/spack/test/repo.py
+++ b/lib/spack/spack/test/repo.py
@@ -10,14 +10,6 @@ import spack.repo
import spack.paths
-# Unlike the repo_path fixture defined in conftest, this has a test-level
-# scope rather than a session level scope, since we want to edit the
-# given RepoPath
-@pytest.fixture()
-def repo_for_test():
- return spack.repo.RepoPath(spack.paths.mock_packages_path)
-
-
@pytest.fixture()
def extra_repo(tmpdir_factory):
repo_namespace = 'extra_test_repo'
@@ -32,31 +24,31 @@ repo:
return spack.repo.Repo(str(repo_dir))
-def test_repo_getpkg(repo_for_test):
- repo_for_test.get('a')
- repo_for_test.get('builtin.mock.a')
+def test_repo_getpkg(mutable_mock_repo):
+ mutable_mock_repo.get('a')
+ mutable_mock_repo.get('builtin.mock.a')
-def test_repo_multi_getpkg(repo_for_test, extra_repo):
- repo_for_test.put_first(extra_repo)
- repo_for_test.get('a')
- repo_for_test.get('builtin.mock.a')
+def test_repo_multi_getpkg(mutable_mock_repo, extra_repo):
+ mutable_mock_repo.put_first(extra_repo)
+ mutable_mock_repo.get('a')
+ mutable_mock_repo.get('builtin.mock.a')
-def test_repo_multi_getpkgclass(repo_for_test, extra_repo):
- repo_for_test.put_first(extra_repo)
- repo_for_test.get_pkg_class('a')
- repo_for_test.get_pkg_class('builtin.mock.a')
+def test_repo_multi_getpkgclass(mutable_mock_repo, extra_repo):
+ mutable_mock_repo.put_first(extra_repo)
+ mutable_mock_repo.get_pkg_class('a')
+ mutable_mock_repo.get_pkg_class('builtin.mock.a')
-def test_repo_pkg_with_unknown_namespace(repo_for_test):
+def test_repo_pkg_with_unknown_namespace(mutable_mock_repo):
with pytest.raises(spack.repo.UnknownNamespaceError):
- repo_for_test.get('unknown.a')
+ mutable_mock_repo.get('unknown.a')
-def test_repo_unknown_pkg(repo_for_test):
+def test_repo_unknown_pkg(mutable_mock_repo):
with pytest.raises(spack.repo.UnknownPackageError):
- repo_for_test.get('builtin.mock.nonexistentpackage')
+ mutable_mock_repo.get('builtin.mock.nonexistentpackage')
@pytest.mark.maybeslow
@@ -66,7 +58,7 @@ def test_repo_last_mtime():
assert spack.repo.path.last_mtime() == latest_mtime
-def test_repo_invisibles(repo_for_test, extra_repo):
+def test_repo_invisibles(mutable_mock_repo, extra_repo):
with open(os.path.join(extra_repo.root, 'packages', '.invisible'), 'w'):
pass
extra_repo.all_package_names()
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 628f67948f..e42cd60189 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -189,7 +189,7 @@ def test_conditional_dep_with_user_constraints():
assert ('y@3' in spec)
-@pytest.mark.usefixtures('mutable_mock_packages')
+@pytest.mark.usefixtures('mutable_mock_repo')
class TestSpecDag(object):
def test_conflicting_package_constraints(self, set_dependency):
diff --git a/lib/spack/spack/test/spec_yaml.py b/lib/spack/spack/test/spec_yaml.py
index 7fd2a36469..4f723988a3 100644
--- a/lib/spack/spack/test/spec_yaml.py
+++ b/lib/spack/spack/test/spec_yaml.py
@@ -61,7 +61,7 @@ def test_concrete_spec(config, mock_packages):
check_yaml_round_trip(spec)
-def test_yaml_multivalue():
+def test_yaml_multivalue(config, mock_packages):
spec = Spec('multivalue_variant foo="bar,baz"')
spec.concretize()
check_yaml_round_trip(spec)
diff --git a/lib/spack/spack/test/svn_fetch.py b/lib/spack/spack/test/svn_fetch.py
index eb0d22ee7c..b065367cbe 100644
--- a/lib/spack/spack/test/svn_fetch.py
+++ b/lib/spack/spack/test/svn_fetch.py
@@ -30,7 +30,7 @@ def test_fetch(
secure,
mock_svn_repository,
config,
- mutable_mock_packages
+ mutable_mock_repo
):
"""Tries to:
diff --git a/lib/spack/spack/test/url_fetch.py b/lib/spack/spack/test/url_fetch.py
index b4df27336e..1b65be0fd7 100644
--- a/lib/spack/spack/test/url_fetch.py
+++ b/lib/spack/spack/test/url_fetch.py
@@ -75,7 +75,7 @@ def test_fetch(
secure,
checksum_type,
config,
- mutable_mock_packages
+ mutable_mock_repo
):
"""Fetch an archive and make sure we can checksum it."""
mock_archive.url