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
|
# Copyright 2013-2024 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)
"""Test Spack's FileCache."""
import os
import pytest
import llnl.util.filesystem as fs
from spack.util.file_cache import CacheError, FileCache
@pytest.fixture()
def file_cache(tmpdir):
"""Returns a properly initialized FileCache instance"""
return FileCache(str(tmpdir))
def test_write_and_read_cache_file(file_cache):
"""Test writing then reading a cached file."""
with file_cache.write_transaction("test.yaml") as (old, new):
assert old is None
assert new is not None
new.write("foobar\n")
with file_cache.read_transaction("test.yaml") as stream:
text = stream.read()
assert text == "foobar\n"
@pytest.mark.not_on_windows("Locks not supported on Windows")
def test_failed_write_and_read_cache_file(file_cache):
"""Test failing to write then attempting to read a cached file."""
with pytest.raises(RuntimeError, match=r"^foobar$"):
with file_cache.write_transaction("test.yaml") as (old, new):
assert old is None
assert new is not None
raise RuntimeError("foobar")
# Cache dir should have exactly one (lock) file
assert os.listdir(file_cache.root) == [".test.yaml.lock"]
# File does not exist
assert not file_cache.init_entry("test.yaml")
# Attempting to read will cause a FileNotFoundError
with pytest.raises(FileNotFoundError, match=r"test\.yaml"):
with file_cache.read_transaction("test.yaml"):
pass
def test_write_and_remove_cache_file(file_cache):
"""Test two write transactions on a cached file. Then try to remove an
entry from it.
"""
with file_cache.write_transaction("test.yaml") as (old, new):
assert old is None
assert new is not None
new.write("foobar\n")
with file_cache.write_transaction("test.yaml") as (old, new):
assert old is not None
text = old.read()
assert text == "foobar\n"
assert new is not None
new.write("barbaz\n")
with file_cache.read_transaction("test.yaml") as stream:
text = stream.read()
assert text == "barbaz\n"
file_cache.remove("test.yaml")
# After removal the file should not exist
assert not os.path.exists(file_cache.cache_path("test.yaml"))
# Whether the lock file exists is more of an implementation detail, on Linux they
# continue to exist, on Windows they don't.
# assert os.path.exists(file_cache._lock_path('test.yaml'))
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
def test_cache_init_entry_fails(file_cache):
"""Test init_entry failures."""
relpath = fs.join_path("test-dir", "read-only-file.txt")
cachefile = file_cache.cache_path(relpath)
fs.touchp(cachefile)
# Ensure directory causes exception
with pytest.raises(CacheError, match="not a file"):
file_cache.init_entry(os.path.dirname(relpath))
# Ensure non-readable file causes exception
os.chmod(cachefile, 0o200)
with pytest.raises(CacheError, match="Cannot access cache file"):
file_cache.init_entry(relpath)
# Ensure read-only parent causes exception
relpath = fs.join_path("test-dir", "another-file.txxt")
cachefile = file_cache.cache_path(relpath)
os.chmod(os.path.dirname(cachefile), 0o400)
with pytest.raises(CacheError, match="Cannot access cache dir"):
file_cache.init_entry(relpath)
def test_cache_write_readonly_cache_fails(file_cache):
"""Test writing a read-only cached file."""
filename = "read-only-file.txt"
path = file_cache.cache_path(filename)
fs.touch(path)
os.chmod(path, 0o400)
with pytest.raises(CacheError, match="Insufficient permissions to write"):
file_cache.write_transaction(filename)
@pytest.mark.regression("31475")
def test_delete_is_idempotent(file_cache):
"""Deleting a non-existent key should be idempotent, to simplify life when
running delete with multiple processes"""
file_cache.remove("test.yaml")
|