| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2012 The LUCI Authors. All rights reserved. | 2 # Copyright 2012 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import subprocess | 9 import subprocess |
| 10 import sys | 10 import sys |
| (...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 234 f.write(content) | 234 f.write(content) |
| 235 | 235 |
| 236 | 236 |
| 237 def tree_modes(root): | 237 def tree_modes(root): |
| 238 """Returns the dict of files in a directory with their filemode. | 238 """Returns the dict of files in a directory with their filemode. |
| 239 | 239 |
| 240 Includes |root| as '.'. | 240 Includes |root| as '.'. |
| 241 """ | 241 """ |
| 242 out = {} | 242 out = {} |
| 243 offset = len(root.rstrip('/\\')) + 1 | 243 offset = len(root.rstrip('/\\')) + 1 |
| 244 out['.'] = oct(os.stat(root).st_mode) | 244 out[u'.'] = oct(os.stat(root).st_mode) |
| 245 for dirpath, dirnames, filenames in os.walk(root): | 245 for dirpath, dirnames, filenames in os.walk(root): |
| 246 for filename in filenames: | 246 for filename in filenames: |
| 247 p = os.path.join(dirpath, filename) | 247 p = os.path.join(dirpath, filename) |
| 248 out[p[offset:]] = oct(os.stat(p).st_mode) | 248 out[p[offset:]] = oct(os.stat(p).st_mode) |
| 249 for dirname in dirnames: | 249 for dirname in dirnames: |
| 250 p = os.path.join(dirpath, dirname) | 250 p = os.path.join(dirpath, dirname) |
| 251 out[p[offset:]] = oct(os.stat(p).st_mode) | 251 out[p[offset:]] = oct(os.stat(p).st_mode) |
| 252 return out | 252 return out |
| 253 | 253 |
| 254 | 254 |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 436 self.assertEqual(sorted(expected), actual) | 436 self.assertEqual(sorted(expected), actual) |
| 437 | 437 |
| 438 def _test_corruption_common(self, new_content): | 438 def _test_corruption_common(self, new_content): |
| 439 isolated_hash = self._store('file_with_size.isolated') | 439 isolated_hash = self._store('file_with_size.isolated') |
| 440 file1_hash = self._store('file1.txt') | 440 file1_hash = self._store('file1.txt') |
| 441 | 441 |
| 442 # Run the test once to generate the cache. | 442 # Run the test once to generate the cache. |
| 443 _out, _err, returncode = self._run(self._cmd_args(isolated_hash)) | 443 _out, _err, returncode = self._run(self._cmd_args(isolated_hash)) |
| 444 self.assertEqual(0, returncode) | 444 self.assertEqual(0, returncode) |
| 445 expected = { | 445 expected = { |
| 446 '.': (040700, 040700, 040777), | 446 u'.': (040700, 040700, 040777), |
| 447 'state.json': (0100600, 0100600, 0100666), | 447 u'state.json': (0100600, 0100600, 0100666), |
| 448 # The reason for 0100666 on Windows is that the file node had to be | 448 # The reason for 0100666 on Windows is that the file node had to be |
| 449 # modified to delete the hardlinked node. The read only bit is reset on | 449 # modified to delete the hardlinked node. The read only bit is reset on |
| 450 # load. | 450 # load. |
| 451 file1_hash: (0100400, 0100400, 0100666), | 451 unicode(file1_hash): (0100400, 0100400, 0100666), |
| 452 isolated_hash: (0100400, 0100400, 0100444), | 452 unicode(isolated_hash): (0100400, 0100400, 0100444), |
| 453 } | 453 } |
| 454 self.assertTreeModes(self.cache, expected) | 454 self.assertTreeModes(self.cache, expected) |
| 455 | 455 |
| 456 # Modify one of the files in the cache to be invalid. | 456 # Modify one of the files in the cache to be invalid. |
| 457 cached_file_path = os.path.join(self.cache, file1_hash) | 457 cached_file_path = os.path.join(self.cache, file1_hash) |
| 458 previous_mode = os.stat(cached_file_path).st_mode | 458 previous_mode = os.stat(cached_file_path).st_mode |
| 459 os.chmod(cached_file_path, 0600) | 459 os.chmod(cached_file_path, 0600) |
| 460 write_content(cached_file_path, new_content) | 460 write_content(cached_file_path, new_content) |
| 461 os.chmod(cached_file_path, previous_mode) | 461 os.chmod(cached_file_path, previous_mode) |
| 462 logging.info('Modified %s', cached_file_path) | 462 logging.info('Modified %s', cached_file_path) |
| 463 # Ensure that the cache has an invalid file. | 463 # Ensure that the cache has an invalid file. |
| 464 self.assertNotEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) | 464 self.assertNotEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) |
| 465 | 465 |
| 466 # Rerun the test and make sure the cache contains the right file afterwards. | 466 # Rerun the test and make sure the cache contains the right file afterwards. |
| 467 out, err, returncode = self._run(self._cmd_args(isolated_hash)) | 467 out, err, returncode = self._run(self._cmd_args(isolated_hash)) |
| 468 self.assertEqual(0, returncode, (out, err, returncode)) | 468 self.assertEqual(0, returncode, (out, err, returncode)) |
| 469 expected = { | 469 expected = { |
| 470 '.': (040700, 040700, 040777), | 470 u'.': (040700, 040700, 040777), |
| 471 u'state.json': (0100600, 0100600, 0100666), | 471 u'state.json': (0100600, 0100600, 0100666), |
| 472 unicode(file1_hash): (0100400, 0100400, 0100666), | 472 unicode(file1_hash): (0100400, 0100400, 0100666), |
| 473 unicode(isolated_hash): (0100400, 0100400, 0100444), | 473 unicode(isolated_hash): (0100400, 0100400, 0100444), |
| 474 } | 474 } |
| 475 self.assertTreeModes(self.cache, expected) | 475 self.assertTreeModes(self.cache, expected) |
| 476 return cached_file_path | 476 return cached_file_path |
| 477 | 477 |
| 478 def test_corrupted_cache_entry_different_size(self): | 478 def test_corrupted_cache_entry_different_size(self): |
| 479 # Test that an entry with an invalid file size properly gets removed and | 479 # Test that an entry with an invalid file size properly gets removed and |
| 480 # fetched again. This test case also check for file modes. | 480 # fetched again. This test case also check for file modes. |
| 481 cached_file_path = self._test_corruption_common( | 481 cached_file_path = self._test_corruption_common( |
| 482 CONTENTS['file1.txt'] + ' now invalid size') | 482 CONTENTS['file1.txt'] + ' now invalid size') |
| 483 self.assertEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) | 483 self.assertEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) |
| 484 | 484 |
| 485 def test_corrupted_cache_entry_same_size(self): | 485 def test_corrupted_cache_entry_same_size(self): |
| 486 # Test that an entry with an invalid file content but same size is NOT | 486 # Test that an entry with an invalid file content but same size is NOT |
| 487 # detected property. | 487 # detected property. |
| 488 cached_file_path = self._test_corruption_common( | 488 cached_file_path = self._test_corruption_common( |
| 489 CONTENTS['file1.txt'][:-1] + ' ') | 489 CONTENTS['file1.txt'][:-1] + ' ') |
| 490 # TODO(maruel): This corruption is NOT detected. | 490 # TODO(maruel): This corruption is NOT detected. |
| 491 # This needs to be fixed. | 491 # This needs to be fixed. |
| 492 self.assertNotEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) | 492 self.assertNotEqual(CONTENTS['file1.txt'], read_content(cached_file_path)) |
| 493 | 493 |
| 494 | 494 |
| 495 if __name__ == '__main__': | 495 if __name__ == '__main__': |
| 496 fix_encoding.fix_encoding() | 496 fix_encoding.fix_encoding() |
| 497 test_utils.main() | 497 test_utils.main() |
| OLD | NEW |