summaryrefslogtreecommitdiff
path: root/lib/spack/spack/test/llnl/util/symlink.py
blob: bbc69473f86648f83af4d96d7f5d4a473db7af86 (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
# 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)

"""Tests for ``llnl/util/symlink.py``"""
import os
import sys
import tempfile

import pytest

from llnl.util import symlink


def test_symlink_file(tmpdir):
    """Test the symlink.symlink functionality on all operating systems for a file"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=test_dir)
        link_file = str(tmpdir.join("link.txt"))
        assert os.path.exists(link_file) is False
        symlink.symlink(source_path=real_file, link_path=link_file)
        assert os.path.exists(link_file)
        assert symlink.islink(link_file)


def test_symlink_dir(tmpdir):
    """Test the symlink.symlink functionality on all operating systems for a directory"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        real_dir = os.path.join(test_dir, "real_dir")
        link_dir = os.path.join(test_dir, "link_dir")
        os.mkdir(real_dir)
        symlink.symlink(source_path=real_dir, link_path=link_dir)
        assert os.path.exists(link_dir)
        assert symlink.islink(link_dir)


def test_symlink_source_not_exists(tmpdir):
    """Test the symlink.symlink method for the case where a source path does not exist"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        real_dir = os.path.join(test_dir, "real_dir")
        link_dir = os.path.join(test_dir, "link_dir")
        with pytest.raises(symlink.SymlinkError):
            symlink.symlink(source_path=real_dir, link_path=link_dir, allow_broken_symlinks=False)


def test_symlink_src_relative_to_link(tmpdir):
    """Test the symlink.symlink functionality where the source value exists relative to the link
    but not relative to the cwd"""
    with tmpdir.as_cwd():
        subdir_1 = tmpdir.join("a")
        subdir_2 = os.path.join(subdir_1, "b")
        link_dir = os.path.join(subdir_1, "c")

        os.mkdir(subdir_1)
        os.mkdir(subdir_2)

        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=subdir_2)
        link_file = os.path.join(subdir_1, "link.txt")

        symlink.symlink(
            source_path=f"b/{os.path.basename(real_file)}",
            link_path=f"a/{os.path.basename(link_file)}",
        )
        assert os.path.exists(link_file)
        assert symlink.islink(link_file)
        # Check dirs
        assert not os.path.lexists(link_dir)
        symlink.symlink(source_path="b", link_path="a/c")
        assert os.path.lexists(link_dir)


def test_symlink_src_not_relative_to_link(tmpdir):
    """Test the symlink.symlink functionality where the source value does not exist relative to
    the link and not relative to the cwd. NOTE that this symlink api call is EXPECTED to raise
    a symlink.SymlinkError exception that we catch."""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        subdir_1 = os.path.join(test_dir, "a")
        subdir_2 = os.path.join(subdir_1, "b")
        link_dir = os.path.join(subdir_1, "c")
        os.mkdir(subdir_1)
        os.mkdir(subdir_2)
        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=subdir_2)
        link_file = str(tmpdir.join("link.txt"))
        # Expected SymlinkError because source path does not exist relative to link path
        with pytest.raises(symlink.SymlinkError):
            symlink.symlink(
                source_path=f"d/{os.path.basename(real_file)}",
                link_path=f"a/{os.path.basename(link_file)}",
                allow_broken_symlinks=False,
            )
        assert not os.path.exists(link_file)
        # Check dirs
        assert not os.path.lexists(link_dir)
        with pytest.raises(symlink.SymlinkError):
            symlink.symlink(source_path="d", link_path="a/c", allow_broken_symlinks=False)
        assert not os.path.lexists(link_dir)


def test_symlink_link_already_exists(tmpdir):
    """Test the symlink.symlink method for the case where a link already exists"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        real_dir = os.path.join(test_dir, "real_dir")
        link_dir = os.path.join(test_dir, "link_dir")
        os.mkdir(real_dir)
        symlink.symlink(real_dir, link_dir, allow_broken_symlinks=False)
        assert os.path.exists(link_dir)
        with pytest.raises(symlink.SymlinkError):
            symlink.symlink(source_path=real_dir, link_path=link_dir, allow_broken_symlinks=False)


@pytest.mark.skipif(not symlink._windows_can_symlink(), reason="Test requires elevated privileges")
@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_symlink_win_file(tmpdir):
    """Check that symlink.symlink makes a symlink file when run with elevated permissions"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=test_dir)
        link_file = str(tmpdir.join("link.txt"))
        symlink.symlink(source_path=real_file, link_path=link_file)
        # Verify that all expected conditions are met
        assert os.path.exists(link_file)
        assert symlink.islink(link_file)
        assert os.path.islink(link_file)
        assert not symlink._windows_is_hardlink(link_file)
        assert not symlink._windows_is_junction(link_file)


@pytest.mark.skipif(not symlink._windows_can_symlink(), reason="Test requires elevated privileges")
@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_symlink_win_dir(tmpdir):
    """Check that symlink.symlink makes a symlink dir when run with elevated permissions"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        real_dir = os.path.join(test_dir, "real")
        link_dir = os.path.join(test_dir, "link")
        os.mkdir(real_dir)
        symlink.symlink(source_path=real_dir, link_path=link_dir)
        # Verify that all expected conditions are met
        assert os.path.exists(link_dir)
        assert symlink.islink(link_dir)
        assert os.path.islink(link_dir)
        assert not symlink._windows_is_hardlink(link_dir)
        assert not symlink._windows_is_junction(link_dir)


@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_windows_create_junction(tmpdir):
    """Test the symlink._windows_create_junction method"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        junction_real_dir = os.path.join(test_dir, "real_dir")
        junction_link_dir = os.path.join(test_dir, "link_dir")
        os.mkdir(junction_real_dir)
        symlink._windows_create_junction(junction_real_dir, junction_link_dir)
        # Verify that all expected conditions are met
        assert os.path.exists(junction_link_dir)
        assert symlink._windows_is_junction(junction_link_dir)
        assert symlink.islink(junction_link_dir)
        assert not os.path.islink(junction_link_dir)


@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_windows_create_hard_link(tmpdir):
    """Test the symlink._windows_create_hard_link method"""
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=test_dir)
        link_file = str(tmpdir.join("link.txt"))
        symlink._windows_create_hard_link(real_file, link_file)
        # Verify that all expected conditions are met
        assert os.path.exists(link_file)
        assert symlink._windows_is_hardlink(real_file)
        assert symlink._windows_is_hardlink(link_file)
        assert symlink.islink(link_file)
        assert not os.path.islink(link_file)


@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_windows_create_link_dir(tmpdir):
    """Test the functionality of the windows_create_link method with a directory
    which should result in making a junction.
    """
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        real_dir = os.path.join(test_dir, "real")
        link_dir = os.path.join(test_dir, "link")
        os.mkdir(real_dir)
        symlink._windows_create_link(real_dir, link_dir)
        # Verify that all expected conditions are met
        assert os.path.exists(link_dir)
        assert symlink.islink(link_dir)
        assert not symlink._windows_is_hardlink(link_dir)
        assert symlink._windows_is_junction(link_dir)
        assert not os.path.islink(link_dir)


@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_windows_create_link_file(tmpdir):
    """Test the functionality of the windows_create_link method with a file
    which should result in the creation of a hard link. It also tests the
    functionality of the symlink islink infrastructure.
    """
    with tmpdir.as_cwd():
        test_dir = str(tmpdir)
        fd, real_file = tempfile.mkstemp(prefix="real", suffix=".txt", dir=test_dir)
        link_file = str(tmpdir.join("link.txt"))
        symlink._windows_create_link(real_file, link_file)
        # Verify that all expected conditions are met
        assert symlink._windows_is_hardlink(link_file)
        assert symlink.islink(link_file)
        assert not symlink._windows_is_junction(link_file)


@pytest.mark.skipif(sys.platform != "win32", reason="Test is only for Windows")
def test_windows_read_link(tmpdir):
    """Makes sure symlink.readlink can read the link source for hard links and
    junctions on windows."""
    with tmpdir.as_cwd():
        real_dir_1 = "real_dir_1"
        real_dir_2 = "real_dir_2"
        link_dir_1 = "link_dir_1"
        link_dir_2 = "link_dir_2"
        os.mkdir(real_dir_1)
        os.mkdir(real_dir_2)

        # Create a file and a directory
        _, real_file_1 = tempfile.mkstemp(prefix="real_1", suffix=".txt", dir=".")
        _, real_file_2 = tempfile.mkstemp(prefix="real_2", suffix=".txt", dir=".")
        link_file_1 = "link_1.txt"
        link_file_2 = "link_2.txt"

        # Make hard link/junction
        symlink._windows_create_hard_link(real_file_1, link_file_1)
        symlink._windows_create_hard_link(real_file_2, link_file_2)
        symlink._windows_create_junction(real_dir_1, link_dir_1)
        symlink._windows_create_junction(real_dir_2, link_dir_2)

        assert symlink.readlink(link_file_1) == os.path.abspath(real_file_1)
        assert symlink.readlink(link_file_2) == os.path.abspath(real_file_2)
        assert symlink.readlink(link_dir_1) == os.path.abspath(real_dir_1)
        assert symlink.readlink(link_dir_2) == os.path.abspath(real_dir_2)