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
|
import os
import textwrap
from time import sleep
from unittest.mock import patch
import pytest
from pkgcheck.addons import init_addon
from pkgcheck.addons.eclass import Eclass, EclassAddon
from pkgcheck.base import PkgcheckUserException
from pkgcheck.addons.caches import CacheDisabled
from snakeoil.fileutils import touch
from snakeoil.osutils import pjoin
class TestEclass:
@pytest.fixture(autouse=True)
def _setup(self, tmp_path):
path = str(tmp_path / 'foo.eclass')
with open(path, 'w') as f:
f.write(textwrap.dedent("""\
# eclass header
foo () { :; }
"""))
self.eclass1 = Eclass('foo', path)
path = str(tmp_path / 'bar.eclass')
self.eclass2 = Eclass('bar', path)
def test_lines(self):
assert self.eclass1.lines == ('# eclass header\n', 'foo () { :; }\n')
assert self.eclass2.lines == ()
def test_lt(self):
assert self.eclass2 < self.eclass1
assert self.eclass1 < 'zoo.eclass'
def test_hash(self):
eclasses = {self.eclass1, self.eclass2}
assert self.eclass1 in eclasses and self.eclass2 in eclasses
assert {self.eclass1, self.eclass1} == {self.eclass1}
def test_eq(self):
assert self.eclass1 == self.eclass1
assert self.eclass1 == self.eclass1.path
assert not self.eclass1 == self.eclass2
class TestEclassAddon:
@pytest.fixture(autouse=True)
def _setup(self, tool, tmp_path, repo):
self.repo = repo
self.cache_dir = str(tmp_path)
self.eclass_dir = pjoin(repo.location, 'eclass')
args = ['scan', '--cache-dir', self.cache_dir, '--repo', repo.location]
options, _ = tool.parse_args(args)
self.addon = EclassAddon(options)
self.cache_file = self.addon.cache_file(self.repo)
def test_cache_disabled(self, tool):
args = ['scan', '--cache', 'no', '--repo', self.repo.location]
options, _ = tool.parse_args(args)
with pytest.raises(CacheDisabled, match='eclass cache support required'):
init_addon(EclassAddon, options)
def test_no_eclasses(self):
self.addon.update_cache()
assert not os.path.exists(self.cache_file)
assert not self.addon.eclasses
assert not self.addon.deprecated
def test_eclasses(self):
# non-eclass files are ignored
for f in ('foo.eclass', 'bar'):
touch(pjoin(self.eclass_dir, f))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
assert not self.addon.deprecated
def test_cache_load(self):
touch(pjoin(self.eclass_dir, 'foo.eclass'))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
with patch('pkgcheck.addons.caches.CachedAddon.save_cache') as save_cache:
self.addon.update_cache()
# verify the cache was loaded and not regenerated
save_cache.assert_not_called()
self.addon.update_cache(force=True)
# and is regenerated on a forced cache update
save_cache.assert_called_once()
def test_outdated_cache(self):
touch(pjoin(self.eclass_dir, 'foo.eclass'))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
# increment cache version and dump cache
cache = self.addon.load_cache(self.cache_file)
cache.version += 1
self.addon.save_cache(cache, self.cache_file)
# verify cache load causes regen
with patch('pkgcheck.addons.caches.CachedAddon.save_cache') as save_cache:
self.addon.update_cache()
save_cache.assert_called_once()
def test_eclass_changes(self):
"""The cache stores eclass mtimes and regenerates entries if they differ."""
eclass_path = pjoin(self.eclass_dir, 'foo.eclass')
touch(eclass_path)
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
sleep(1)
with open(eclass_path, 'w') as f:
f.write('# changed eclass\n')
with patch('pkgcheck.addons.caches.CachedAddon.save_cache') as save_cache:
self.addon.update_cache()
save_cache.assert_called_once()
def test_error_loading_cache(self):
touch(pjoin(self.eclass_dir, 'foo.eclass'))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
with patch('pkgcheck.addons.caches.pickle.load') as pickle_load:
# catastrophic errors are raised
pickle_load.side_effect = MemoryError('unpickling failed')
with pytest.raises(MemoryError, match='unpickling failed'):
self.addon.update_cache()
# but various load failure exceptions cause cache regen
pickle_load.side_effect = Exception('unpickling failed')
with patch('pkgcheck.addons.caches.CachedAddon.save_cache') as save_cache:
self.addon.update_cache()
save_cache.assert_called_once()
def test_error_dumping_cache(self):
touch(pjoin(self.eclass_dir, 'foo.eclass'))
# verify IO related dump failures are raised
with patch('pkgcheck.addons.caches.pickle.dump') as pickle_dump:
pickle_dump.side_effect = IOError('unpickling failed')
with pytest.raises(PkgcheckUserException, match='failed dumping eclass cache'):
self.addon.update_cache()
def test_eclass_removal(self):
for name in ('foo', 'bar'):
touch(pjoin(self.eclass_dir, f'{name}.eclass'))
self.addon.update_cache()
assert sorted(self.addon.eclasses) == ['bar', 'foo']
os.unlink(pjoin(self.eclass_dir, 'bar.eclass'))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
def test_deprecated(self):
with open(pjoin(self.eclass_dir, 'foo.eclass'), 'w') as f:
f.write(textwrap.dedent("""
# @ECLASS: foo.eclass
# @MAINTAINER:
# Random Person <random.person@random.email>
# @AUTHOR:
# Random Person <random.person@random.email>
# @BLURB: Example deprecated eclass with replacement.
# @DEPRECATED: foo2
"""))
self.addon.update_cache()
assert list(self.addon.eclasses) == ['foo']
assert self.addon.deprecated == {'foo': 'foo2'}
|