fix mistake in RELEASE.txt content
[scons.git] / SCons / CacheDirTests.py
blob6ec9e84abb87ce6e194e332ac650032e27f60cae
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 import os.path
25 import shutil
26 import sys
27 import unittest
28 import tempfile
29 import stat
31 from TestCmd import TestCmd
33 import SCons.CacheDir
35 built_it = None
37 class Action:
38 def __call__(self, targets, sources, env, **kw) -> int:
39 global built_it
40 if kw.get('execute', 1):
41 built_it = 1
42 return 0
43 def genstring(self, target, source, env):
44 return str(self)
45 def get_contents(self, target, source, env):
46 return bytearray('','utf-8')
48 class Builder:
49 def __init__(self, environment, action) -> None:
50 self.env = environment
51 self.action = action
52 self.overrides = {}
53 self.source_scanner = None
54 self.target_scanner = None
56 class Environment:
57 def __init__(self, cachedir) -> None:
58 self.cachedir = cachedir
59 def Override(self, overrides):
60 return self
61 def get_CacheDir(self):
62 return self.cachedir
64 class BaseTestCase(unittest.TestCase):
65 """
66 Base fixtures common to our other unittest classes.
67 """
68 def setUp(self) -> None:
69 self.test = TestCmd(workdir='')
71 import SCons.Node.FS
72 self.fs = SCons.Node.FS.FS()
74 self._CacheDir = SCons.CacheDir.CacheDir('cache')
76 def File(self, name, bsig=None, action=Action()):
77 node = self.fs.File(name)
78 node.builder_set(Builder(Environment(self._CacheDir), action))
79 if bsig:
80 node.cachesig = bsig
81 #node.binfo = node.BuildInfo(node)
82 #node.binfo.ninfo.bsig = bsig
83 return node
85 def tearDown(self) -> None:
86 os.remove(os.path.join(self._CacheDir.path, 'config'))
87 os.rmdir(self._CacheDir.path)
88 # Should that be shutil.rmtree?
90 class CacheDirTestCase(BaseTestCase):
91 """
92 Test calling CacheDir code directly.
93 """
94 def test_cachepath(self) -> None:
95 """Test the cachepath() method"""
97 # Verify how the cachepath() method determines the name
98 # of the file in cache.
99 def my_collect(list, hash_format=None):
100 return list[0]
101 save_collect = SCons.Util.hash_collect
102 SCons.Util.hash_collect = my_collect
104 try:
105 name = 'a_fake_bsig'
106 f5 = self.File("cd.f5", name)
107 result = self._CacheDir.cachepath(f5)
108 len = self._CacheDir.config['prefix_len']
109 dirname = os.path.join('cache', name.upper()[:len])
110 filename = os.path.join(dirname, name)
111 assert result == (dirname, filename), result
112 finally:
113 SCons.Util.hash_collect = save_collect
115 class ExceptionTestCase(unittest.TestCase):
116 """Test that the correct exceptions are thrown by CacheDir."""
118 # Don't inherit from BaseTestCase, we're by definition trying to
119 # break things so we really want a clean slate for each test.
120 def setUp(self) -> None:
121 self.tmpdir = tempfile.mkdtemp()
122 self._CacheDir = SCons.CacheDir.CacheDir(self.tmpdir)
124 def tearDown(self) -> None:
125 shutil.rmtree(self.tmpdir)
127 @unittest.skipIf(sys.platform.startswith("win"), "This fixture will not trigger an OSError on Windows")
128 def test_throws_correct_on_OSError(self) -> None:
129 """Test that the correct error is thrown when cache directory cannot be created."""
130 privileged_dir = os.path.join(self.tmpdir, "privileged")
131 try:
132 os.mkdir(privileged_dir)
133 os.chmod(privileged_dir, stat.S_IREAD)
134 cd = SCons.CacheDir.CacheDir(os.path.join(privileged_dir, "cache"))
135 assert False, "Should have raised exception and did not"
136 except SCons.Errors.SConsEnvironmentError as e:
137 assert str(e) == "Failed to create cache directory {}".format(os.path.join(privileged_dir, "cache"))
138 finally:
139 os.chmod(privileged_dir, stat.S_IWRITE | stat.S_IEXEC | stat.S_IREAD)
140 shutil.rmtree(privileged_dir)
143 def test_throws_correct_when_failed_to_write_configfile(self) -> None:
144 class Unserializable:
145 """A class which the JSON should not be able to serialize"""
147 def __init__(self, oldconfig) -> None:
148 self.something = 1 # Make the object unserializable
149 # Pretend to be the old config just enough
150 self.__dict__["prefix_len"] = oldconfig["prefix_len"]
152 def __getitem__(self, name, default=None):
153 if name == "prefix_len":
154 return self.__dict__["prefix_len"]
155 else:
156 return None
158 def __setitem__(self, name, value) -> None:
159 self.__dict__[name] = value
161 oldconfig = self._CacheDir.config
162 self._CacheDir.config = Unserializable(oldconfig)
163 # Remove the config file that got created on object creation
164 # so that _readconfig* will try to rewrite it
165 old_config = os.path.join(self._CacheDir.path, "config")
166 os.remove(old_config)
168 try:
169 self._CacheDir._readconfig(self._CacheDir.path)
170 assert False, "Should have raised exception and did not"
171 except SCons.Errors.SConsEnvironmentError as e:
172 assert str(e) == "Failed to write cache configuration for {}".format(self._CacheDir.path)
174 def test_raise_environment_error_on_invalid_json(self) -> None:
175 config_file = os.path.join(self._CacheDir.path, "config")
176 with open(config_file, "r") as cfg:
177 content = cfg.read()
178 # This will make JSON load raise a ValueError
179 content += "{}"
180 with open(config_file, "w") as cfg:
181 cfg.write(content)
183 try:
184 # Construct a new cache dir that will try to read the invalid config
185 new_cache_dir = SCons.CacheDir.CacheDir(self._CacheDir.path)
186 assert False, "Should have raised exception and did not"
187 except SCons.Errors.SConsEnvironmentError as e:
188 assert str(e) == "Failed to read cache configuration for {}".format(self._CacheDir.path)
190 class FileTestCase(BaseTestCase):
192 Test calling CacheDir code through Node.FS.File interfaces.
194 # These tests were originally in Nodes/FSTests.py and got moved
195 # when the CacheDir support was refactored into its own module.
196 # Look in the history for Node/FSTests.py if any of this needs
197 # to be re-examined.
198 def retrieve_succeed(self, target, source, env, execute: int=1) -> int:
199 self.retrieved.append(target)
200 return 0
202 def retrieve_fail(self, target, source, env, execute: int=1) -> int:
203 self.retrieved.append(target)
204 return 1
206 def push(self, target, source, env) -> int:
207 self.pushed.append(target)
208 return 0
210 def test_CacheRetrieve(self) -> None:
211 """Test the CacheRetrieve() function"""
213 save_CacheRetrieve = SCons.CacheDir.CacheRetrieve
214 self.retrieved = []
216 f1 = self.File("cd.f1")
217 try:
218 SCons.CacheDir.CacheRetrieve = self.retrieve_succeed
219 self.retrieved = []
220 built_it = None
222 r = f1.retrieve_from_cache()
223 assert r == 1, r
224 assert self.retrieved == [f1], self.retrieved
225 assert built_it is None, built_it
227 SCons.CacheDir.CacheRetrieve = self.retrieve_fail
228 self.retrieved = []
229 built_it = None
231 r = f1.retrieve_from_cache()
232 assert not r, r
233 assert self.retrieved == [f1], self.retrieved
234 assert built_it is None, built_it
235 finally:
236 SCons.CacheDir.CacheRetrieve = save_CacheRetrieve
238 def test_CacheRetrieveSilent(self) -> None:
239 """Test the CacheRetrieveSilent() function"""
241 save_CacheRetrieveSilent = SCons.CacheDir.CacheRetrieveSilent
243 SCons.CacheDir.cache_show = 1
245 f2 = self.File("cd.f2", 'f2_bsig')
246 try:
247 SCons.CacheDir.CacheRetrieveSilent = self.retrieve_succeed
248 self.retrieved = []
249 built_it = None
251 r = f2.retrieve_from_cache()
252 assert r == 1, r
253 assert self.retrieved == [f2], self.retrieved
254 assert built_it is None, built_it
256 SCons.CacheDir.CacheRetrieveSilent = self.retrieve_fail
257 self.retrieved = []
258 built_it = None
260 r = f2.retrieve_from_cache()
261 assert r is False, r
262 assert self.retrieved == [f2], self.retrieved
263 assert built_it is None, built_it
264 finally:
265 SCons.CacheDir.CacheRetrieveSilent = save_CacheRetrieveSilent
267 def test_CachePush(self) -> None:
268 """Test the CachePush() function"""
270 save_CachePush = SCons.CacheDir.CachePush
272 SCons.CacheDir.CachePush = self.push
274 try:
275 self.pushed = []
277 cd_f3 = self.test.workpath("cd.f3")
278 f3 = self.File(cd_f3)
279 f3.push_to_cache()
280 assert self.pushed == [], self.pushed
281 self.test.write(cd_f3, "cd.f3\n")
282 f3.push_to_cache()
283 assert self.pushed == [f3], self.pushed
285 self.pushed = []
287 cd_f4 = self.test.workpath("cd.f4")
288 f4 = self.File(cd_f4)
289 f4.visited()
290 assert self.pushed == [], self.pushed
291 self.test.write(cd_f4, "cd.f4\n")
292 f4.clear()
293 f4.visited()
294 assert self.pushed == [], self.pushed
295 SCons.CacheDir.cache_force = 1
296 f4.clear()
297 f4.visited()
298 assert self.pushed == [f4], self.pushed
299 finally:
300 SCons.CacheDir.CachePush = save_CachePush
302 def test_warning(self) -> None:
303 """Test raising a warning if we can't copy a file to cache."""
305 test = TestCmd(workdir='')
307 save_copy2 = shutil.copy2
308 def copy2(src, dst):
309 raise OSError
310 shutil.copy2 = copy2
311 save_mkdir = os.mkdir
312 def mkdir(dir, mode: int=0) -> None:
313 pass
314 os.mkdir = mkdir
315 old_warn_exceptions = SCons.Warnings.warningAsException(1)
316 SCons.Warnings.enableWarningClass(SCons.Warnings.CacheWriteErrorWarning)
318 cd_f7 = self.test.workpath("cd.f7")
319 self.test.write(cd_f7, "cd.f7\n")
320 f7 = self.File(cd_f7, 'f7_bsig')
322 warn_caught = 0
323 r = f7.push_to_cache()
324 assert r.exc_info[0] == SCons.Warnings.CacheWriteErrorWarning
325 shutil.copy2 = save_copy2
326 os.mkdir = save_mkdir
327 SCons.Warnings.warningAsException(old_warn_exceptions)
328 SCons.Warnings.suppressWarningClass(SCons.Warnings.CacheWriteErrorWarning)
330 def test_no_strfunction(self) -> None:
331 """Test handling no strfunction() for an action."""
333 save_CacheRetrieveSilent = SCons.CacheDir.CacheRetrieveSilent
335 f8 = self.File("cd.f8", 'f8_bsig')
336 try:
337 SCons.CacheDir.CacheRetrieveSilent = self.retrieve_succeed
338 self.retrieved = []
339 built_it = None
341 r = f8.retrieve_from_cache()
342 assert r == 1, r
343 assert self.retrieved == [f8], self.retrieved
344 assert built_it is None, built_it
346 SCons.CacheDir.CacheRetrieveSilent = self.retrieve_fail
347 self.retrieved = []
348 built_it = None
350 r = f8.retrieve_from_cache()
351 assert r is False, r
352 assert self.retrieved == [f8], self.retrieved
353 assert built_it is None, built_it
354 finally:
355 SCons.CacheDir.CacheRetrieveSilent = save_CacheRetrieveSilent
357 if __name__ == "__main__":
358 unittest.main()
359 # Local Variables:
360 # tab-width:4
361 # indent-tabs-mode:nil
362 # End:
363 # vim: set expandtab tabstop=4 shiftwidth=4: