summaryrefslogtreecommitdiff
path: root/lib/spack/spack/test/cmd/diff.py
blob: 9a901b9cbc5cb4a4edf2620399d813274b976209 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# 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"]