| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The LUCI Authors. All rights reserved. | 2 # Copyright 2013 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 # pylint: disable=R0201 | 6 # pylint: disable=R0201 |
| 7 | 7 |
| 8 import StringIO | 8 import StringIO |
| 9 import base64 | 9 import base64 |
| 10 import contextlib | 10 import contextlib |
| 11 import functools | 11 import functools |
| 12 import hashlib |
| 12 import json | 13 import json |
| 13 import logging | 14 import logging |
| 14 import os | 15 import os |
| 15 import sys | 16 import sys |
| 16 import tempfile | 17 import tempfile |
| 17 import unittest | 18 import unittest |
| 18 | 19 |
| 19 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath( | 20 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath( |
| 20 __file__.decode(sys.getfilesystemencoding())))) | 21 __file__.decode(sys.getfilesystemencoding())))) |
| 21 sys.path.insert(0, ROOT_DIR) | 22 sys.path.insert(0, ROOT_DIR) |
| 22 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party')) | 23 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party')) |
| 23 | 24 |
| 24 import cipd | 25 import cipd |
| 25 import isolated_format | 26 import isolated_format |
| 26 import isolateserver | 27 import isolateserver |
| 28 import named_cache |
| 27 import run_isolated | 29 import run_isolated |
| 28 from depot_tools import auto_stub | 30 from depot_tools import auto_stub |
| 29 from depot_tools import fix_encoding | 31 from depot_tools import fix_encoding |
| 30 from utils import file_path | 32 from utils import file_path |
| 31 from utils import fs | 33 from utils import fs |
| 32 from utils import large | 34 from utils import large |
| 33 from utils import logging_utils | 35 from utils import logging_utils |
| 34 from utils import on_error | 36 from utils import on_error |
| 35 from utils import subprocess42 | 37 from utils import subprocess42 |
| 36 from utils import tools | 38 from utils import tools |
| 37 | 39 |
| 38 import isolateserver_mock | 40 import isolateserver_mock |
| 39 import cipdserver_mock | 41 import cipdserver_mock |
| 40 | 42 |
| 41 | 43 |
| 44 ALGO = hashlib.sha1 |
| 45 |
| 46 |
| 42 def write_content(filepath, content): | 47 def write_content(filepath, content): |
| 43 with open(filepath, 'wb') as f: | 48 with open(filepath, 'wb') as f: |
| 44 f.write(content) | 49 f.write(content) |
| 45 | 50 |
| 46 | 51 |
| 47 def json_dumps(data): | 52 def json_dumps(data): |
| 48 return json.dumps(data, sort_keys=True, separators=(',', ':')) | 53 return json.dumps(data, sort_keys=True, separators=(',', ':')) |
| 49 | 54 |
| 50 | 55 |
| 56 def genTree(path): |
| 57 """Returns a dict with {filepath: content}.""" |
| 58 if not os.path.isdir(path): |
| 59 return None |
| 60 out = {} |
| 61 for root, _, filenames in os.walk(path): |
| 62 for filename in filenames: |
| 63 p = os.path.join(root, filename) |
| 64 with open(p, 'rb') as f: |
| 65 out[os.path.relpath(p, path)] = f.read() |
| 66 return out |
| 67 |
| 68 |
| 51 @contextlib.contextmanager | 69 @contextlib.contextmanager |
| 52 def init_named_caches_stub(_run_dir): | 70 def init_named_caches_stub(_run_dir): |
| 53 yield | 71 yield |
| 54 | 72 |
| 55 | 73 |
| 56 class StorageFake(object): | 74 class StorageFake(object): |
| 57 def __init__(self, files): | 75 def __init__(self, files): |
| 58 self._files = files.copy() | 76 self._files = files.copy() |
| 59 self.namespace = 'default-gzip' | 77 self.namespace = 'default-gzip' |
| 60 self.location = 'http://localhost:1' | 78 self.location = 'http://localhost:1' |
| (...skipping 24 matching lines...) Expand all Loading... |
| 85 logging.debug(self.tempdir) | 103 logging.debug(self.tempdir) |
| 86 self.mock(run_isolated, 'make_temp_dir', self.fake_make_temp_dir) | 104 self.mock(run_isolated, 'make_temp_dir', self.fake_make_temp_dir) |
| 87 self.mock(run_isolated.auth, 'ensure_logged_in', lambda _: None) | 105 self.mock(run_isolated.auth, 'ensure_logged_in', lambda _: None) |
| 88 self.mock( | 106 self.mock( |
| 89 logging_utils.OptionParserWithLogging, 'logger_root', | 107 logging_utils.OptionParserWithLogging, 'logger_root', |
| 90 logging.Logger('unittest')) | 108 logging.Logger('unittest')) |
| 91 | 109 |
| 92 self.cipd_server = cipdserver_mock.MockCipdServer() | 110 self.cipd_server = cipdserver_mock.MockCipdServer() |
| 93 | 111 |
| 94 def tearDown(self): | 112 def tearDown(self): |
| 113 # Remove mocks. |
| 114 super(RunIsolatedTestBase, self).tearDown() |
| 95 file_path.rmtree(self.tempdir) | 115 file_path.rmtree(self.tempdir) |
| 96 self.cipd_server.close() | 116 self.cipd_server.close() |
| 97 super(RunIsolatedTestBase, self).tearDown() | |
| 98 | 117 |
| 99 @property | 118 @property |
| 100 def run_test_temp_dir(self): | 119 def run_test_temp_dir(self): |
| 101 """Where to map all files in run_isolated.run_tha_test.""" | 120 """Where to map all files in run_isolated.run_tha_test.""" |
| 102 return os.path.join(self.tempdir, run_isolated.ISOLATED_RUN_DIR) | 121 return os.path.join(self.tempdir, run_isolated.ISOLATED_RUN_DIR) |
| 103 | 122 |
| 104 def fake_make_temp_dir(self, prefix, _root_dir): | 123 def fake_make_temp_dir(self, prefix, _root_dir): |
| 105 """Predictably returns directory for run_tha_test (one per test case).""" | 124 """Predictably returns directory for run_tha_test (one per test case).""" |
| 106 self.assertIn( | 125 self.assertIn( |
| 107 prefix, | 126 prefix, |
| (...skipping 443 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 551 {'detached': True}), | 570 {'detached': True}), |
| 552 ], | 571 ], |
| 553 self.popen_calls) | 572 self.popen_calls) |
| 554 | 573 |
| 555 def test_run_tha_test_non_isolated(self): | 574 def test_run_tha_test_non_isolated(self): |
| 556 _ = self._run_tha_test(command=['/bin/echo', 'hello', 'world']) | 575 _ = self._run_tha_test(command=['/bin/echo', 'hello', 'world']) |
| 557 self.assertEqual( | 576 self.assertEqual( |
| 558 [([u'/bin/echo', u'hello', u'world'], {'detached': True})], | 577 [([u'/bin/echo', u'hello', u'world'], {'detached': True})], |
| 559 self.popen_calls) | 578 self.popen_calls) |
| 560 | 579 |
| 580 def test_clean_caches(self): |
| 581 # Create an isolated cache and a named cache each with 2 items. Ensure that |
| 582 # one item from each is removed. |
| 583 fake_time = 1 |
| 584 fake_free_space = [102400] |
| 585 np = self.temp_join('named_cache') |
| 586 ip = self.temp_join('isolated_cache') |
| 587 args = [ |
| 588 '--named-cache-root', np, '--cache', ip, '--clean', |
| 589 '--min-free-space', '10240', |
| 590 ] |
| 591 self.mock(file_path, 'get_free_space', lambda _: fake_free_space[0]) |
| 592 parser, options, _ = run_isolated.parse_args(args) |
| 593 isolate_cache = isolateserver.process_cache_options( |
| 594 options, trim=False, time_fn=lambda: fake_time) |
| 595 self.assertIsInstance(isolate_cache, isolateserver.DiskCache) |
| 596 named_cache_manager = named_cache.process_named_cache_options( |
| 597 parser, options) |
| 598 self.assertIsInstance(named_cache_manager, named_cache.CacheManager) |
| 599 |
| 600 # Add items to these caches. |
| 601 small = '0123456789' |
| 602 big = small * 1014 |
| 603 small_digest = unicode(ALGO(small).hexdigest()) |
| 604 big_digest = unicode(ALGO(big).hexdigest()) |
| 605 with isolate_cache: |
| 606 fake_time = 1 |
| 607 isolate_cache.write(big_digest, [big]) |
| 608 fake_time = 2 |
| 609 isolate_cache.write(small_digest, [small]) |
| 610 with named_cache_manager.open(time_fn=lambda: fake_time): |
| 611 fake_time = 1 |
| 612 p = named_cache_manager.request('first') |
| 613 with open(os.path.join(p, 'big'), 'wb') as f: |
| 614 f.write(big) |
| 615 fake_time = 3 |
| 616 p = named_cache_manager.request('second') |
| 617 with open(os.path.join(p, 'small'), 'wb') as f: |
| 618 f.write(small) |
| 619 |
| 620 # Ensures the cache contain the expected data. |
| 621 actual = genTree(np) |
| 622 # Figure out the cache path names. |
| 623 cache_small = [ |
| 624 os.path.dirname(n) for n in actual if os.path.basename(n) == 'small'][0] |
| 625 cache_big = [ |
| 626 os.path.dirname(n) for n in actual if os.path.basename(n) == 'big'][0] |
| 627 expected = { |
| 628 os.path.join(cache_small, u'small'): small, |
| 629 os.path.join(cache_big, u'big'): big, |
| 630 u'state.json': |
| 631 '{"items":[["first",["%s",1]],["second",["%s",3]]],"version":2}' % ( |
| 632 cache_big, cache_small), |
| 633 } |
| 634 self.assertEqual(expected, actual) |
| 635 expected = { |
| 636 big_digest: big, |
| 637 small_digest: small, |
| 638 u'state.json': |
| 639 '{"items":[["%s",[10140,1]],["%s",[10,2]]],"version":2}' % ( |
| 640 big_digest, small_digest), |
| 641 } |
| 642 self.assertEqual(expected, genTree(ip)) |
| 643 |
| 644 # Request triming. |
| 645 fake_free_space[0] = 1020 |
| 646 # Abuse the fact that named cache is trimed after isolated cache. |
| 647 def rmtree(p): |
| 648 self.assertEqual(os.path.join(np, cache_big), p) |
| 649 fake_free_space[0] += 10240 |
| 650 return old_rmtree(p) |
| 651 old_rmtree = self.mock(file_path, 'rmtree', rmtree) |
| 652 isolate_cache = isolateserver.process_cache_options(options, trim=False) |
| 653 named_cache_manager = named_cache.process_named_cache_options( |
| 654 parser, options) |
| 655 actual = run_isolated.clean_caches( |
| 656 options, isolate_cache, named_cache_manager) |
| 657 self.assertEqual(2, actual) |
| 658 # One of each entry should have been cleaned up. This only happen to work |
| 659 # because: |
| 660 # - file_path.get_free_space() is mocked |
| 661 # - DiskCache.trim() keeps its own internal counter while deleting files so |
| 662 # it ignores get_free_space() output while deleting files. |
| 663 actual = genTree(np) |
| 664 expected = { |
| 665 os.path.join(cache_small, u'small'): small, |
| 666 u'state.json': |
| 667 '{"items":[["second",["%s",3]]],"version":2}' % cache_small, |
| 668 } |
| 669 self.assertEqual(expected, actual) |
| 670 expected = { |
| 671 small_digest: small, |
| 672 u'state.json': |
| 673 '{"items":[["%s",[10,2]]],"version":2}' % small_digest, |
| 674 } |
| 675 self.assertEqual(expected, genTree(ip)) |
| 676 |
| 561 | 677 |
| 562 class RunIsolatedTestRun(RunIsolatedTestBase): | 678 class RunIsolatedTestRun(RunIsolatedTestBase): |
| 563 def test_output(self): | 679 def test_output(self): |
| 564 # Starts a full isolate server mock and have run_tha_test() uploads results | 680 # Starts a full isolate server mock and have run_tha_test() uploads results |
| 565 # back after the task completed. | 681 # back after the task completed. |
| 566 server = isolateserver_mock.MockIsolateServer() | 682 server = isolateserver_mock.MockIsolateServer() |
| 567 try: | 683 try: |
| 568 script = ( | 684 script = ( |
| 569 'import sys\n' | 685 'import sys\n' |
| 570 'open(sys.argv[1], "w").write("bar")\n') | 686 'open(sys.argv[1], "w").write("bar")\n') |
| (...skipping 298 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 869 self.assertEqual(expected, actual) | 985 self.assertEqual(expected, actual) |
| 870 | 986 |
| 871 | 987 |
| 872 if __name__ == '__main__': | 988 if __name__ == '__main__': |
| 873 fix_encoding.fix_encoding() | 989 fix_encoding.fix_encoding() |
| 874 if '-v' in sys.argv: | 990 if '-v' in sys.argv: |
| 875 unittest.TestCase.maxDiff = None | 991 unittest.TestCase.maxDiff = None |
| 876 logging.basicConfig( | 992 logging.basicConfig( |
| 877 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) | 993 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) |
| 878 unittest.main() | 994 unittest.main() |
| OLD | NEW |