| 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('cipd_cache/cipd' + cipd.EXECUTABLE_SUFFIX), |
| 354 'ensure', |
| 355 ]) |
| 356 cache_dir_index = cipd_ensure_cmd.index('-cache-dir') |
| 357 self.assertEqual( |
| 358 cipd_ensure_cmd[cache_dir_index+1], |
| 359 os.path.abspath('cipd_cache/cipd_internal')) |
| 360 |
| 361 # Test cipd cache. |
| 362 version_file = unicode(os.path.abspath( |
| 363 'cipd_cache/versions/1481d0a0ceb16ea4672fed76a0710306eb9f3a33')) |
| 364 self.assertTrue(fs.isfile(version_file)) |
| 365 with open(version_file) as f: |
| 366 self.assertEqual(f.read(), 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') |
| 367 |
| 368 client_binary_file = unicode(os.path.abspath( |
| 369 'cipd_cache/clients/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')) |
| 370 self.assertTrue(fs.isfile(client_binary_file)) |
| 371 |
| 372 # Test echo call. |
| 373 echo_cmd, _ = self.popen_calls[1] |
| 374 self.assertTrue(echo_cmd[0].endswith('cipd_site_root/echo')) |
| 375 self.assertEqual(echo_cmd[1:], ['hello', 'world']) |
| 376 |
| 323 def test_modified_cwd(self): | 377 def test_modified_cwd(self): |
| 324 isolated = json_dumps({ | 378 isolated = json_dumps({ |
| 325 'command': ['../out/some.exe', 'arg'], | 379 'command': ['../out/some.exe', 'arg'], |
| 326 'relative_cwd': 'some', | 380 'relative_cwd': 'some', |
| 327 }) | 381 }) |
| 328 isolated_hash = isolateserver_mock.hash_content(isolated) | 382 isolated_hash = isolateserver_mock.hash_content(isolated) |
| 329 files = {isolated_hash:isolated} | 383 files = {isolated_hash:isolated} |
| 330 _ = self._run_tha_test(isolated_hash, files) | 384 _ = self._run_tha_test(isolated_hash, files) |
| 331 self.assertEqual(1, len(self.popen_calls)) | 385 self.assertEqual(1, len(self.popen_calls)) |
| 332 self.assertEqual( | 386 self.assertEqual( |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 391 ret = run_isolated.run_tha_test( | 445 ret = run_isolated.run_tha_test( |
| 392 None, | 446 None, |
| 393 isolated_hash, | 447 isolated_hash, |
| 394 store, | 448 store, |
| 395 isolateserver.MemoryCache(), | 449 isolateserver.MemoryCache(), |
| 396 False, | 450 False, |
| 397 None, | 451 None, |
| 398 None, | 452 None, |
| 399 None, | 453 None, |
| 400 None, | 454 None, |
| 401 []) | 455 [], |
| 456 None, |
| 457 None) |
| 402 self.assertEqual(0, ret) | 458 self.assertEqual(0, ret) |
| 403 | 459 |
| 404 # It uploaded back. Assert the store has a new item containing foo. | 460 # It uploaded back. Assert the store has a new item containing foo. |
| 405 hashes = {isolated_hash, script_hash} | 461 hashes = {isolated_hash, script_hash} |
| 406 output_hash = isolateserver_mock.hash_content('bar') | 462 output_hash = isolateserver_mock.hash_content('bar') |
| 407 hashes.add(output_hash) | 463 hashes.add(output_hash) |
| 408 isolated = { | 464 isolated = { |
| 409 'algo': 'sha-1', | 465 'algo': 'sha-1', |
| 410 'files': { | 466 'files': { |
| 411 'foo': { | 467 'foo': { |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 522 u'initial_size': 0, | 578 u'initial_size': 0, |
| 523 u'items_cold': [len(isolated_in_json)], | 579 u'items_cold': [len(isolated_in_json)], |
| 524 u'items_hot': [], | 580 u'items_hot': [], |
| 525 }, | 581 }, |
| 526 u'upload': { | 582 u'upload': { |
| 527 u'items_cold': [len(isolated_out_json)], | 583 u'items_cold': [len(isolated_out_json)], |
| 528 u'items_hot': [15], | 584 u'items_hot': [15], |
| 529 }, | 585 }, |
| 530 }, | 586 }, |
| 531 }, | 587 }, |
| 532 u'version': 4, | 588 u'version': 5, |
| 533 } | 589 } |
| 534 actual = tools.read_json(out) | 590 actual = tools.read_json(out) |
| 535 # duration can be exactly 0 due to low timer resolution, especially but not | 591 # duration can be exactly 0 due to low timer resolution, especially but not |
| 536 # exclusively on Windows. | 592 # exclusively on Windows. |
| 537 self.assertLessEqual(0, actual.pop(u'duration')) | 593 self.assertLessEqual(0, actual.pop(u'duration')) |
| 538 actual_isolated_stats = actual[u'stats'][u'isolated'] | 594 actual_isolated_stats = actual[u'stats'][u'isolated'] |
| 539 self.assertLessEqual(0, actual_isolated_stats[u'download'].pop(u'duration')) | 595 self.assertLessEqual(0, actual_isolated_stats[u'download'].pop(u'duration')) |
| 540 self.assertLessEqual(0, actual_isolated_stats[u'upload'].pop(u'duration')) | 596 self.assertLessEqual(0, actual_isolated_stats[u'upload'].pop(u'duration')) |
| 541 for i in (u'download', u'upload'): | 597 for i in (u'download', u'upload'): |
| 542 for j in (u'items_cold', u'items_hot'): | 598 for j in (u'items_cold', u'items_hot'): |
| 543 actual_isolated_stats[i][j] = large.unpack( | 599 actual_isolated_stats[i][j] = large.unpack( |
| 544 base64.b64decode(actual_isolated_stats[i][j])) | 600 base64.b64decode(actual_isolated_stats[i][j])) |
| 545 self.assertEqual(expected, actual) | 601 self.assertEqual(expected, actual) |
| 546 | 602 |
| 547 | 603 |
| 548 if __name__ == '__main__': | 604 if __name__ == '__main__': |
| 549 fix_encoding.fix_encoding() | 605 fix_encoding.fix_encoding() |
| 550 if '-v' in sys.argv: | 606 if '-v' in sys.argv: |
| 551 unittest.TestCase.maxDiff = None | 607 unittest.TestCase.maxDiff = None |
| 552 logging.basicConfig( | 608 logging.basicConfig( |
| 553 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) | 609 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) |
| 554 unittest.main() | 610 unittest.main() |
| OLD | NEW |