| 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 functools |   10 import functools | 
|   11 import json |   11 import json | 
|   12 import logging |   12 import logging | 
|   13 import os |   13 import os | 
|   14 import sys |   14 import sys | 
|   15 import tempfile |   15 import tempfile | 
|   16 import unittest |   16 import unittest | 
|   17  |   17  | 
|   18 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |   18 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 
|   19 sys.path.insert(0, ROOT_DIR) |   19 sys.path.insert(0, ROOT_DIR) | 
|   20 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party')) |   20 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party')) | 
|   21  |   21  | 
 |   22 import cipd | 
|   22 import isolated_format |   23 import isolated_format | 
|   23 import isolateserver |   24 import isolateserver | 
|   24 import run_isolated |   25 import run_isolated | 
|   25 from depot_tools import auto_stub |   26 from depot_tools import auto_stub | 
|   26 from depot_tools import fix_encoding |   27 from depot_tools import fix_encoding | 
|   27 from utils import file_path |   28 from utils import file_path | 
 |   29 from utils import fs | 
|   28 from utils import large |   30 from utils import large | 
|   29 from utils import logging_utils |   31 from utils import logging_utils | 
|   30 from utils import on_error |   32 from utils import on_error | 
|   31 from utils import subprocess42 |   33 from utils import subprocess42 | 
|   32 from utils import tools |   34 from utils import tools | 
|   33  |   35  | 
|   34 import isolateserver_mock |   36 import isolateserver_mock | 
 |   37 import cipdserver_mock | 
|   35  |   38  | 
|   36  |   39  | 
|   37 def write_content(filepath, content): |   40 def write_content(filepath, content): | 
|   38   with open(filepath, 'wb') as f: |   41   with open(filepath, 'wb') as f: | 
|   39     f.write(content) |   42     f.write(content) | 
|   40  |   43  | 
|   41  |   44  | 
|   42 def json_dumps(data): |   45 def json_dumps(data): | 
|   43   return json.dumps(data, sort_keys=True, separators=(',', ':')) |   46   return json.dumps(data, sort_keys=True, separators=(',', ':')) | 
|   44  |   47  | 
| (...skipping 27 matching lines...) Expand all  Loading... | 
|   72   def setUp(self): |   75   def setUp(self): | 
|   73     super(RunIsolatedTestBase, self).setUp() |   76     super(RunIsolatedTestBase, self).setUp() | 
|   74     self.tempdir = tempfile.mkdtemp(prefix=u'run_isolated_test') |   77     self.tempdir = tempfile.mkdtemp(prefix=u'run_isolated_test') | 
|   75     logging.debug(self.tempdir) |   78     logging.debug(self.tempdir) | 
|   76     self.mock(run_isolated, 'make_temp_dir', self.fake_make_temp_dir) |   79     self.mock(run_isolated, 'make_temp_dir', self.fake_make_temp_dir) | 
|   77     self.mock(run_isolated.auth, 'ensure_logged_in', lambda _: None) |   80     self.mock(run_isolated.auth, 'ensure_logged_in', lambda _: None) | 
|   78     self.mock( |   81     self.mock( | 
|   79         logging_utils.OptionParserWithLogging, 'logger_root', |   82         logging_utils.OptionParserWithLogging, 'logger_root', | 
|   80         logging.Logger('unittest')) |   83         logging.Logger('unittest')) | 
|   81  |   84  | 
 |   85     self.cipd_server = cipdserver_mock.MockCipdServer() | 
 |   86  | 
|   82   def tearDown(self): |   87   def tearDown(self): | 
|   83     for dirpath, dirnames, filenames in os.walk(self.tempdir, topdown=True): |   88     for dirpath, dirnames, filenames in os.walk(self.tempdir, topdown=True): | 
|   84       for filename in filenames: |   89       for filename in filenames: | 
|   85         file_path.set_read_only(os.path.join(dirpath, filename), False) |   90         file_path.set_read_only(os.path.join(dirpath, filename), False) | 
|   86       for dirname in dirnames: |   91       for dirname in dirnames: | 
|   87         file_path.set_read_only(os.path.join(dirpath, dirname), False) |   92         file_path.set_read_only(os.path.join(dirpath, dirname), False) | 
|   88     file_path.rmtree(self.tempdir) |   93     file_path.rmtree(self.tempdir) | 
 |   94     self.cipd_server.close() | 
|   89     super(RunIsolatedTestBase, self).tearDown() |   95     super(RunIsolatedTestBase, self).tearDown() | 
|   90  |   96  | 
|   91   @property |   97   @property | 
|   92   def run_test_temp_dir(self): |   98   def run_test_temp_dir(self): | 
|   93     """Where to map all files in run_isolated.run_tha_test.""" |   99     """Where to map all files in run_isolated.run_tha_test.""" | 
|   94     return os.path.join(self.tempdir, 'isolated_run') |  100     return os.path.join(self.tempdir, 'isolated_run') | 
|   95  |  101  | 
|   96   def fake_make_temp_dir(self, prefix, _root_dir=None): |  102   def fake_make_temp_dir(self, prefix, _root_dir=None): | 
|   97     """Predictably returns directory for run_tha_test (one per test case).""" |  103     """Predictably returns directory for run_tha_test (one per test case).""" | 
|   98     self.assertIn(prefix, ('isolated_out', 'isolated_run', 'isolated_tmp')) |  104     self.assertIn( | 
 |  105         prefix, | 
 |  106         ('isolated_out', 'isolated_run', 'isolated_tmp', 'cipd_site_root')) | 
|   99     temp_dir = os.path.join(self.tempdir, prefix) |  107     temp_dir = os.path.join(self.tempdir, prefix) | 
|  100     self.assertFalse(os.path.isdir(temp_dir)) |  108     self.assertFalse(os.path.isdir(temp_dir)) | 
|  101     os.makedirs(temp_dir) |  109     os.makedirs(temp_dir) | 
|  102     return temp_dir |  110     return temp_dir | 
|  103  |  111  | 
|  104   def temp_join(self, *args): |  112   def temp_join(self, *args): | 
|  105     """Shortcut for joining path with self.run_test_temp_dir.""" |  113     """Shortcut for joining path with self.run_test_temp_dir.""" | 
|  106     return os.path.join(self.run_test_temp_dir, *args) |  114     return os.path.join(self.run_test_temp_dir, *args) | 
|  107  |  115  | 
|  108  |  116  | 
|  109 class RunIsolatedTest(RunIsolatedTestBase): |  117 class RunIsolatedTest(RunIsolatedTestBase): | 
|  110   def setUp(self): |  118   def setUp(self): | 
|  111     super(RunIsolatedTest, self).setUp() |  119     super(RunIsolatedTest, self).setUp() | 
|  112     self.popen_calls = [] |  120     self.popen_calls = [] | 
|  113     # pylint: disable=no-self-argument |  121     # pylint: disable=no-self-argument | 
|  114     class Popen(object): |  122     class Popen(object): | 
|  115       def __init__(self2, args, **kwargs): |  123       def __init__(self2, args, **kwargs): | 
|  116         kwargs.pop('cwd', None) |  124         kwargs.pop('cwd', None) | 
|  117         kwargs.pop('env', None) |  125         kwargs.pop('env', None) | 
|  118         self.popen_calls.append((args, kwargs)) |  126         self.popen_calls.append((args, kwargs)) | 
|  119         self2.returncode = None |  127         self2.returncode = None | 
|  120  |  128  | 
 |  129       def yield_any_line(self, timeout=None):  # pylint: disable=unused-argument | 
 |  130         return () | 
 |  131  | 
|  121       def wait(self, timeout=None):  # pylint: disable=unused-argument |  132       def wait(self, timeout=None):  # pylint: disable=unused-argument | 
|  122         self.returncode = 0 |  133         self.returncode = 0 | 
|  123         return self.returncode |  134         return self.returncode | 
|  124  |  135  | 
|  125       def kill(self): |  136       def kill(self): | 
|  126         pass |  137         pass | 
|  127  |  138  | 
|  128     self.mock(subprocess42, 'Popen', Popen) |  139     self.mock(subprocess42, 'Popen', Popen) | 
|  129  |  140  | 
|  130   def test_main(self): |  141   def test_main(self): | 
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  188     ret = run_isolated.run_tha_test( |  199     ret = run_isolated.run_tha_test( | 
|  189         command, |  200         command, | 
|  190         isolated_hash, |  201         isolated_hash, | 
|  191         StorageFake(files), |  202         StorageFake(files), | 
|  192         isolateserver.MemoryCache(), |  203         isolateserver.MemoryCache(), | 
|  193         False, |  204         False, | 
|  194         None, |  205         None, | 
|  195         None, |  206         None, | 
|  196         None, |  207         None, | 
|  197         None, |  208         None, | 
 |  209         None, | 
 |  210         None, | 
|  198         None) |  211         None) | 
|  199     self.assertEqual(0, ret) |  212     self.assertEqual(0, ret) | 
|  200     return make_tree_call |  213     return make_tree_call | 
|  201  |  214  | 
|  202   def test_run_tha_test_naked(self): |  215   def test_run_tha_test_naked(self): | 
|  203     isolated = json_dumps({'command': ['invalid', 'command']}) |  216     isolated = json_dumps({'command': ['invalid', 'command']}) | 
|  204     isolated_hash = isolateserver_mock.hash_content(isolated) |  217     isolated_hash = isolateserver_mock.hash_content(isolated) | 
|  205     files = {isolated_hash:isolated} |  218     files = {isolated_hash:isolated} | 
|  206     make_tree_call = self._run_tha_test(isolated_hash, files) |  219     make_tree_call = self._run_tha_test(isolated_hash, files) | 
|  207     self.assertEqual( |  220     self.assertEqual( | 
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  313       'hello', |  326       'hello', | 
|  314       'world', |  327       'world', | 
|  315     ] |  328     ] | 
|  316     ret = run_isolated.main(cmd) |  329     ret = run_isolated.main(cmd) | 
|  317     self.assertEqual(1, ret) |  330     self.assertEqual(1, ret) | 
|  318     self.assertEqual(1, len(self.popen_calls)) |  331     self.assertEqual(1, len(self.popen_calls)) | 
|  319     self.assertEqual( |  332     self.assertEqual( | 
|  320         [([u'/bin/echo', u'hello', u'world'], {'detached': True})], |  333         [([u'/bin/echo', u'hello', u'world'], {'detached': True})], | 
|  321         self.popen_calls) |  334         self.popen_calls) | 
|  322  |  335  | 
 |  336   def test_main_naked_with_packages(self): | 
 |  337     cmd = [ | 
 |  338       '--no-log', | 
 |  339       '--cipd-package', 'infra/tools/echo/${PLATFORM}:latest', | 
 |  340       '--cipd-server', self.cipd_server.url, | 
 |  341       '${CIPD_PATH}/echo', | 
 |  342       'hello', | 
 |  343       'world', | 
 |  344     ] | 
 |  345     ret = run_isolated.main(cmd) | 
 |  346     self.assertEqual(0, ret) | 
 |  347  | 
 |  348     self.assertEqual(2, len(self.popen_calls)) | 
 |  349  | 
 |  350     # Test cipd-ensure command. | 
 |  351     cipd_ensure_cmd, _ = self.popen_calls[0] | 
 |  352     self.assertEqual(cipd_ensure_cmd[:2], [ | 
 |  353       os.path.abspath( | 
 |  354           'cipd_cache/cipd' + cipd.EXECUTABLE_SUFFIX), | 
 |  355       'ensure', | 
 |  356     ]) | 
 |  357     cache_dir_index = cipd_ensure_cmd.index('-cache-dir') | 
 |  358     self.assertEqual( | 
 |  359         cipd_ensure_cmd[cache_dir_index+1], | 
 |  360         os.path.abspath('cipd_cache/cipd_internal')) | 
 |  361  | 
 |  362     # Test cipd cache. | 
 |  363     version_file = unicode(os.path.abspath( | 
 |  364         'cipd_cache/versions/1481d0a0ceb16ea4672fed76a0710306eb9f3a33')) | 
 |  365     self.assertTrue(fs.isfile(version_file)) | 
 |  366     with open(version_file) as f: | 
 |  367       self.assertEqual(f.read(), 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') | 
 |  368  | 
 |  369     client_binary_file = unicode(os.path.abspath( | 
 |  370         'cipd_cache/clients/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')) | 
 |  371     self.assertTrue(fs.isfile(client_binary_file)) | 
 |  372  | 
 |  373     # Test echo call. | 
 |  374     echo_cmd, _ = self.popen_calls[1] | 
 |  375     self.assertTrue(echo_cmd[0].endswith('cipd_site_root/echo')) | 
 |  376     self.assertEqual(echo_cmd[1:], ['hello', 'world']) | 
 |  377  | 
|  323   def test_modified_cwd(self): |  378   def test_modified_cwd(self): | 
|  324     isolated = json_dumps({ |  379     isolated = json_dumps({ | 
|  325         'command': ['../out/some.exe', 'arg'], |  380         'command': ['../out/some.exe', 'arg'], | 
|  326         'relative_cwd': 'some', |  381         'relative_cwd': 'some', | 
|  327     }) |  382     }) | 
|  328     isolated_hash = isolateserver_mock.hash_content(isolated) |  383     isolated_hash = isolateserver_mock.hash_content(isolated) | 
|  329     files = {isolated_hash:isolated} |  384     files = {isolated_hash:isolated} | 
|  330     _ = self._run_tha_test(isolated_hash, files) |  385     _ = self._run_tha_test(isolated_hash, files) | 
|  331     self.assertEqual(1, len(self.popen_calls)) |  386     self.assertEqual(1, len(self.popen_calls)) | 
|  332     self.assertEqual( |  387     self.assertEqual( | 
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  391       ret = run_isolated.run_tha_test( |  446       ret = run_isolated.run_tha_test( | 
|  392           None, |  447           None, | 
|  393           isolated_hash, |  448           isolated_hash, | 
|  394           store, |  449           store, | 
|  395           isolateserver.MemoryCache(), |  450           isolateserver.MemoryCache(), | 
|  396           False, |  451           False, | 
|  397           None, |  452           None, | 
|  398           None, |  453           None, | 
|  399           None, |  454           None, | 
|  400           None, |  455           None, | 
|  401           []) |  456           [], | 
 |  457           None, | 
 |  458           None) | 
|  402       self.assertEqual(0, ret) |  459       self.assertEqual(0, ret) | 
|  403  |  460  | 
|  404       # It uploaded back. Assert the store has a new item containing foo. |  461       # It uploaded back. Assert the store has a new item containing foo. | 
|  405       hashes = {isolated_hash, script_hash} |  462       hashes = {isolated_hash, script_hash} | 
|  406       output_hash = isolateserver_mock.hash_content('bar') |  463       output_hash = isolateserver_mock.hash_content('bar') | 
|  407       hashes.add(output_hash) |  464       hashes.add(output_hash) | 
|  408       isolated =  { |  465       isolated =  { | 
|  409         'algo': 'sha-1', |  466         'algo': 'sha-1', | 
|  410         'files': { |  467         'files': { | 
|  411           'foo': { |  468           'foo': { | 
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  522             u'initial_size': 0, |  579             u'initial_size': 0, | 
|  523             u'items_cold': [len(isolated_in_json)], |  580             u'items_cold': [len(isolated_in_json)], | 
|  524             u'items_hot': [], |  581             u'items_hot': [], | 
|  525           }, |  582           }, | 
|  526           u'upload': { |  583           u'upload': { | 
|  527             u'items_cold': [len(isolated_out_json)], |  584             u'items_cold': [len(isolated_out_json)], | 
|  528             u'items_hot': [15], |  585             u'items_hot': [15], | 
|  529           }, |  586           }, | 
|  530         }, |  587         }, | 
|  531       }, |  588       }, | 
|  532       u'version': 4, |  589       u'version': 5, | 
|  533     } |  590     } | 
|  534     actual = tools.read_json(out) |  591     actual = tools.read_json(out) | 
|  535     # duration can be exactly 0 due to low timer resolution, especially but not |  592     # duration can be exactly 0 due to low timer resolution, especially but not | 
|  536     # exclusively on Windows. |  593     # exclusively on Windows. | 
|  537     self.assertLessEqual(0, actual.pop(u'duration')) |  594     self.assertLessEqual(0, actual.pop(u'duration')) | 
|  538     actual_isolated_stats = actual[u'stats'][u'isolated'] |  595     actual_isolated_stats = actual[u'stats'][u'isolated'] | 
|  539     self.assertLessEqual(0, actual_isolated_stats[u'download'].pop(u'duration')) |  596     self.assertLessEqual(0, actual_isolated_stats[u'download'].pop(u'duration')) | 
|  540     self.assertLessEqual(0, actual_isolated_stats[u'upload'].pop(u'duration')) |  597     self.assertLessEqual(0, actual_isolated_stats[u'upload'].pop(u'duration')) | 
|  541     for i in (u'download', u'upload'): |  598     for i in (u'download', u'upload'): | 
|  542       for j in (u'items_cold', u'items_hot'): |  599       for j in (u'items_cold', u'items_hot'): | 
|  543         actual_isolated_stats[i][j] = large.unpack( |  600         actual_isolated_stats[i][j] = large.unpack( | 
|  544             base64.b64decode(actual_isolated_stats[i][j])) |  601             base64.b64decode(actual_isolated_stats[i][j])) | 
|  545     self.assertEqual(expected, actual) |  602     self.assertEqual(expected, actual) | 
|  546  |  603  | 
|  547  |  604  | 
|  548 if __name__ == '__main__': |  605 if __name__ == '__main__': | 
|  549   fix_encoding.fix_encoding() |  606   fix_encoding.fix_encoding() | 
|  550   if '-v' in sys.argv: |  607   if '-v' in sys.argv: | 
|  551     unittest.TestCase.maxDiff = None |  608     unittest.TestCase.maxDiff = None | 
|  552   logging.basicConfig( |  609   logging.basicConfig( | 
|  553       level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) |  610       level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) | 
|  554   unittest.main() |  611   unittest.main() | 
| OLD | NEW |