From 1c93fef160cdda53842a4b629eea1752a42db018 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Thu, 6 Feb 2025 09:55:27 +0100 Subject: [PATCH] spec.py: ensure == is false if true modulo precomputed dag hash (#48889) --- lib/spack/spack/spec.py | 7 +++++++ lib/spack/spack/test/spec_semantics.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index ff1fbb4d072..67bd7c57767 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -3855,6 +3855,13 @@ def _cmp_iter(self): for item in self._cmp_node(): yield item + # If there is ever a breaking change to hash computation, whether accidental or purposeful, + # two specs can be identical modulo DAG hash, depending on what time they were concretized + # From the perspective of many operation in Spack (database, build cache, etc) a different + # DAG hash means a different spec. Here we ensure that two otherwise identical specs, one + # serialized before the hash change and one after, are considered different. + yield self.dag_hash() if self.concrete else None + # This needs to be in _cmp_iter so that no specs with different process hashes # are considered the same by `__hash__` or `__eq__`. # diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py index 668677c44d9..a4b046fb443 100644 --- a/lib/spack/spack/test/spec_semantics.py +++ b/lib/spack/spack/test/spec_semantics.py @@ -1989,3 +1989,26 @@ def test_equality_discriminate_on_propagation(lhs, rhs): def test_comparison_multivalued_variants(): assert Spec("x=a") < Spec("x=a,b") < Spec("x==a,b") < Spec("x==a,b,c") + + +def test_comparison_after_breaking_hash_change(): + # We simulate a breaking change in DAG hash computation in Spack. We have two specs that are + # entirely equal modulo DAG hash. When deserializing these specs, we don't want them to compare + # as equal, because DAG hash is used throughout in Spack to distinguish between specs + # (e.g. database, build caches, install dir). + s = Spec("example@=1.0") + s._mark_concrete(True) + + # compute the dag hash and a change to it + dag_hash = s.dag_hash() + new_dag_hash = f"{'b' if dag_hash[0] == 'a' else 'a'}{dag_hash[1:]}" + + before_breakage = s.to_dict() + after_breakage = s.to_dict() + after_breakage["spec"]["nodes"][0]["hash"] = new_dag_hash + assert before_breakage != after_breakage + + x = Spec.from_dict(before_breakage) + y = Spec.from_dict(after_breakage) + assert x != y + assert len({x, y}) == 2