bugfix: installed and installed_upstream should not assert (#30271)

Fix bug introduced in #30191. `Spec.installed` and `Spec.installed_upstream` should just return
`False` for abstract specs, as they can be called in that context.

- [x] `Spec.installed` returns `False` now instead of asserting that the `Spec`
      is concrete.
- [x] `Spec.installed_upstream` returns `False` now instead of asserting that the `Spec`
      is concrete.
- [x] `Spec.installed_upstream` no longer caches its result, as install status seems
      like a bad thing to cache -- it can easily be invalidated. Calling code should
      use transactions if there are peformance issues, as with other places in Spack.
- [x] add tests for `Spec.installed` and `Spec.installed_upstream`
This commit is contained in:
Todd Gamblin 2022-04-25 00:46:21 -07:00 committed by GitHub
parent 35a4c2325e
commit d729b4e72b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 12 deletions

View File

@ -1197,7 +1197,6 @@ def __init__(self, spec_like=None, normal=False,
self._package_hash = None
self._dunder_hash = None
self._package = None
self._installed_upstream = None
# Most of these are internal implementation details that can be
# set by internal Spack calls in the constructor.
@ -1562,8 +1561,9 @@ def installed(self):
Returns:
True if the package has been installed, False otherwise.
"""
msg = "a spec must be concrete to be queried for installation status"
assert self.concrete, msg
if not self.concrete:
return False
try:
# If the spec is in the DB, check the installed
# attribute of the record
@ -1575,12 +1575,16 @@ def installed(self):
@property
def installed_upstream(self):
msg = "a spec must be concrete to be queried for installation status"
assert self.concrete, msg
if getattr(self, '_installed_upstream', None) is None:
upstream, _ = spack.store.db.query_by_spec_hash(self.dag_hash())
self._installed_upstream = upstream
return self._installed_upstream
"""Whether the spec is installed in an upstream repository.
Returns:
True if the package is installed in an upstream, False otherwise.
"""
if not self.concrete:
return False
upstream, _ = spack.store.db.query_by_spec_hash(self.dag_hash())
return upstream
def traverse(self, **kwargs):
direction = kwargs.get('direction', 'children')

View File

@ -64,6 +64,36 @@ def upstream_and_downstream_db(tmpdir_factory, gen_mock_layout):
downstream_db, downstream_layout
@pytest.mark.skipif(sys.platform == 'win32',
reason="Upstreams currently unsupported on Windows")
def test_spec_installed_upstream(upstream_and_downstream_db, config, monkeypatch):
"""Test whether Spec.installed_upstream() works."""
upstream_write_db, upstream_db, upstream_layout, \
downstream_db, downstream_layout = upstream_and_downstream_db
# a known installed spec should say that it's installed
mock_repo = MockPackageMultiRepo()
mock_repo.add_package('x', [], [])
with spack.repo.use_repositories(mock_repo):
spec = spack.spec.Spec("x").concretized()
assert not spec.installed
assert not spec.installed_upstream
upstream_write_db.add(spec, upstream_layout)
upstream_db._read()
monkeypatch.setattr(spack.store, "db", downstream_db)
assert spec.installed
assert spec.installed_upstream
assert spec.copy().installed
# an abstract spec should say it's not installed
spec = spack.spec.Spec("not-a-real-package")
assert not spec.installed
assert not spec.installed_upstream
@pytest.mark.skipif(sys.platform == 'win32',
reason="Upstreams currently unsupported on Windows")
@pytest.mark.usefixtures('config')

View File

@ -284,7 +284,7 @@ def test_check_last_phase_error(install_mockery):
assert pkg.last_phase in err
def test_installer_ensure_ready_errors(install_mockery):
def test_installer_ensure_ready_errors(install_mockery, monkeypatch):
const_arg = installer_args(['trivial-install-test-package'], {})
installer = create_installer(const_arg)
spec = installer.build_requests[0].pkg.spec
@ -300,14 +300,14 @@ def test_installer_ensure_ready_errors(install_mockery):
# Force an upstream package error
spec.external_path, spec.external_modules = path, modules
spec._installed_upstream = True
monkeypatch.setattr(spack.spec.Spec, "installed_upstream", True)
msg = fmt.format('is upstream')
with pytest.raises(inst.UpstreamPackageError, match=msg):
installer._ensure_install_ready(spec.package)
# Force an install lock error, which should occur naturally since
# we are calling an internal method prior to any lock-related setup
spec._installed_upstream = False
monkeypatch.setattr(spack.spec.Spec, "installed_upstream", False)
assert len(installer.locks) == 0
with pytest.raises(inst.InstallLockError, match=fmt.format('not locked')):
installer._ensure_install_ready(spec.package)

View File

@ -1258,3 +1258,20 @@ def test_merge_anonymous_spec_with_named_spec(anonymous, named, expected):
changed = s.constrain(named)
assert changed
assert s == Spec(expected)
def test_spec_installed(install_mockery, database):
"""Test whether Spec.installed works."""
# a known installed spec should say that it's installed
specs = database.query()
spec = specs[0]
assert spec.installed
assert spec.copy().installed
# an abstract spec should say it's not installed
spec = Spec("not-a-real-package")
assert not spec.installed
# 'a' is not in the mock DB and is not installed
spec = Spec("a").concretized()
assert not spec.installed