# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) import pytest import spack.cmd.diff import spack.config import spack.main import spack.store import spack.util.spack_json as sjson from spack.test.conftest import create_test_repo install_cmd = spack.main.SpackCommand("install") diff_cmd = spack.main.SpackCommand("diff") find_cmd = spack.main.SpackCommand("find") _p1 = ( "p1", """\ class P1(Package): version("1.0") variant("p1var", default=True) variant("usev1", default=True) depends_on("p2") depends_on("v1", when="+usev1") """, ) _p2 = ( "p2", """\ class P2(Package): version("1.0") variant("p2var", default=True) depends_on("p3") """, ) _p3 = ( "p3", """\ class P3(Package): version("1.0") variant("p3var", default=True) """, ) _i1 = ( "i1", """\ class I1(Package): version("1.0") provides("v1") variant("i1var", default=True) depends_on("p3") depends_on("p4") """, ) _i2 = ( "i2", """\ class I2(Package): version("1.0") provides("v1") variant("i2var", default=True) depends_on("p3") depends_on("p4") """, ) _p4 = ( "p4", """\ class P4(Package): version("1.0") variant("p4var", default=True) """, ) # Note that the hash of p1 will differ depending on the variant chosen # we probably always want to omit that from diffs @pytest.fixture def _create_test_repo(tmpdir, mutable_config): """ p1____ | \ p2 v1 | ____/ | p3 p4 i1 and i2 provide v1 (and both have the same dependencies) All packages have an associated variant """ yield create_test_repo(tmpdir, [_p1, _p2, _p3, _i1, _i2, _p4]) @pytest.fixture def test_repo(_create_test_repo, monkeypatch, mock_stage): with spack.repo.use_repositories(_create_test_repo) as mock_repo_path: yield mock_repo_path def test_diff_ignore(test_repo): specA = spack.spec.Spec("p1+usev1").concretized() specB = spack.spec.Spec("p1~usev1").concretized() c1 = spack.cmd.diff.compare_specs(specA, specB, to_string=False) def match(function, name, args): limit = len(args) return function.name == name and list(args[:limit]) == list(function.args[:limit]) def find(function_list, name, args): return any(match(f, name, args) for f in function_list) assert find(c1["a_not_b"], "node_os", ["p4"]) c2 = spack.cmd.diff.compare_specs(specA, specB, ignore_packages=["v1"], to_string=False) assert not find(c2["a_not_b"], "node_os", ["p4"]) assert find(c2["intersect"], "node_os", ["p3"]) # Check ignoring changes on multiple packages specA = spack.spec.Spec("p1+usev1 ^p3+p3var").concretized() specA = spack.spec.Spec("p1~usev1 ^p3~p3var").concretized() c3 = spack.cmd.diff.compare_specs(specA, specB, to_string=False) assert find(c3["a_not_b"], "variant_value", ["p3", "p3var"]) c4 = spack.cmd.diff.compare_specs(specA, specB, ignore_packages=["v1", "p3"], to_string=False) assert not find(c4["a_not_b"], "node_os", ["p4"]) assert not find(c4["a_not_b"], "variant_value", ["p3"]) def test_diff_cmd(install_mockery, mock_fetch, mock_archive, mock_packages): """Test that we can install two packages and diff them""" specA = spack.spec.Spec("mpileaks").concretized() specB = spack.spec.Spec("mpileaks+debug").concretized() # Specs should be the same as themselves c = spack.cmd.diff.compare_specs(specA, specA, to_string=True) assert len(c["a_not_b"]) == 0 assert len(c["b_not_a"]) == 0 # Calculate the comparison (c) c = spack.cmd.diff.compare_specs(specA, specB, to_string=True) # these particular diffs should have the same length b/c thre aren't # any node differences -- just value differences. assert len(c["a_not_b"]) == len(c["b_not_a"]) # ensure that variant diffs are in here the result assert ["variant_value", "mpileaks debug False"] in c["a_not_b"] assert ["variant_value", "mpileaks debug True"] in c["b_not_a"] # ensure that hash diffs are in here the result assert ["hash", "mpileaks %s" % specA.dag_hash()] in c["a_not_b"] assert ["hash", "mpileaks %s" % specB.dag_hash()] in c["b_not_a"] @pytest.mark.not_on_windows("Not supported on Windows (yet)") def test_load_first(install_mockery, mock_fetch, mock_archive, mock_packages): """Test with and without the --first option""" install_cmd("mpileaks") # Only one version of mpileaks will work diff_cmd("mpileaks", "mpileaks") # 2 specs are required for a diff with pytest.raises(spack.main.SpackCommandError): diff_cmd("mpileaks") with pytest.raises(spack.main.SpackCommandError): diff_cmd("mpileaks", "mpileaks", "mpileaks") # Ensure they are the same assert "No differences" in diff_cmd("mpileaks", "mpileaks") output = diff_cmd("--json", "mpileaks", "mpileaks") result = sjson.load(output) assert not result["a_not_b"] assert not result["b_not_a"] assert "mpileaks" in result["a_name"] assert "mpileaks" in result["b_name"] # spot check attributes in the intersection to ensure they describe the spec assert "intersect" in result assert all( ["node", dep] in result["intersect"] for dep in ("mpileaks", "callpath", "dyninst", "libelf", "libdwarf", "mpich") ) assert all( len([diff for diff in result["intersect"] if diff[0] == attr]) == 6 for attr in ( "version", "node_target", "node_platform", "node_os", "node_compiler", "node_compiler_version", "node", "package_hash", "hash", ) ) # After we install another version, it should ask us to disambiguate install_cmd("mpileaks+debug") # There are two versions of mpileaks with pytest.raises(spack.main.SpackCommandError): diff_cmd("mpileaks", "mpileaks+debug") # But if we tell it to use the first, it won't try to disambiguate assert "variant" in diff_cmd("--first", "mpileaks", "mpileaks+debug") # This matches them exactly debug_hash = find_cmd("--format", "{hash}", "mpileaks+debug").strip() no_debug_hashes = find_cmd("--format", "{hash}", "mpileaks~debug") no_debug_hash = no_debug_hashes.split()[0] output = diff_cmd( "--json", "mpileaks/{0}".format(debug_hash), "mpileaks/{0}".format(no_debug_hash) ) result = sjson.load(output) assert ["hash", "mpileaks %s" % debug_hash] in result["a_not_b"] assert ["variant_value", "mpileaks debug True"] in result["a_not_b"] assert ["hash", "mpileaks %s" % no_debug_hash] in result["b_not_a"] assert ["variant_value", "mpileaks debug False"] in result["b_not_a"]