| 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 by the Apache v2.0 license that can be | 3 # Use of this source code is governed by the Apache v2.0 license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Reads a .isolated, creates a tree of hardlinks and runs the test. | 6 """Runs a command with optional isolated input/output. |
| 7 | 7 |
| 8 To improve performance, it keeps a local cache. The local cache can safely be | 8 Despite name "run_isolated", can run a generic non-isolated command specified as |
| 9 deleted. | 9 args. |
| 10 |
| 11 If input isolated hash is provided, fetches it, creates a tree of hard links, |
| 12 appends args to the command in the fetched isolated and runs it. |
| 13 To improve performance, keeps a local cache. |
| 14 The local cache can safely be deleted. |
| 10 | 15 |
| 11 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a | 16 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a |
| 12 temporary directory upon execution of the command specified in the .isolated | 17 temporary directory upon execution of the command specified in the .isolated |
| 13 file. All content written to this directory will be uploaded upon termination | 18 file. All content written to this directory will be uploaded upon termination |
| 14 and the .isolated file describing this directory will be printed to stdout. | 19 and the .isolated file describing this directory will be printed to stdout. |
| 15 """ | 20 """ |
| 16 | 21 |
| 17 __version__ = '0.6.1' | 22 __version__ = '0.7.0' |
| 18 | 23 |
| 19 import base64 | 24 import base64 |
| 20 import logging | 25 import logging |
| 21 import optparse | 26 import optparse |
| 22 import os | 27 import os |
| 23 import sys | 28 import sys |
| 24 import tempfile | 29 import tempfile |
| 25 import time | 30 import time |
| 26 | 31 |
| 27 from third_party.depot_tools import fix_encoding | 32 from third_party.depot_tools import fix_encoding |
| 28 | 33 |
| 29 from utils import file_path | 34 from utils import file_path |
| 30 from utils import fs | 35 from utils import fs |
| 31 from utils import large | 36 from utils import large |
| 32 from utils import logging_utils | 37 from utils import logging_utils |
| 33 from utils import on_error | 38 from utils import on_error |
| 34 from utils import subprocess42 | 39 from utils import subprocess42 |
| 35 from utils import tools | 40 from utils import tools |
| 36 from utils import zip_package | 41 from utils import zip_package |
| 37 | 42 |
| 38 import auth | 43 import auth |
| 39 import isolated_format | |
| 40 import isolateserver | 44 import isolateserver |
| 41 | 45 |
| 42 | 46 |
| 47 ISOLATED_OUTDIR_PARAMETER = '${ISOLATED_OUTDIR}' |
| 48 |
| 43 # Absolute path to this file (can be None if running from zip on Mac). | 49 # Absolute path to this file (can be None if running from zip on Mac). |
| 44 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None | 50 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None |
| 45 | 51 |
| 46 # Directory that contains this file (might be inside zip package). | 52 # Directory that contains this file (might be inside zip package). |
| 47 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None | 53 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None |
| 48 | 54 |
| 49 # Directory that contains currently running script file. | 55 # Directory that contains currently running script file. |
| 50 if zip_package.get_main_script_path(): | 56 if zip_package.get_main_script_path(): |
| 51 MAIN_DIR = os.path.dirname( | 57 MAIN_DIR = os.path.dirname( |
| 52 os.path.abspath(zip_package.get_main_script_path())) | 58 os.path.abspath(zip_package.get_main_script_path())) |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 file_path.make_tree_writeable(rootdir) | 136 file_path.make_tree_writeable(rootdir) |
| 131 else: | 137 else: |
| 132 raise ValueError( | 138 raise ValueError( |
| 133 'change_tree_read_only(%s, %s): Unknown flag %s' % | 139 'change_tree_read_only(%s, %s): Unknown flag %s' % |
| 134 (rootdir, read_only, read_only)) | 140 (rootdir, read_only, read_only)) |
| 135 | 141 |
| 136 | 142 |
| 137 def process_command(command, out_dir): | 143 def process_command(command, out_dir): |
| 138 """Replaces isolated specific variables in a command line.""" | 144 """Replaces isolated specific variables in a command line.""" |
| 139 def fix(arg): | 145 def fix(arg): |
| 140 if '${ISOLATED_OUTDIR}' in arg: | 146 if ISOLATED_OUTDIR_PARAMETER in arg: |
| 141 return arg.replace('${ISOLATED_OUTDIR}', out_dir).replace('/', os.sep) | 147 arg = arg.replace(ISOLATED_OUTDIR_PARAMETER, out_dir) |
| 148 # Replace slashes only if ISOLATED_OUTDIR_PARAMETER is present |
| 149 # because of arguments like '${ISOLATED_OUTDIR}/foo/bar' |
| 150 arg = arg.replace('/', os.sep) |
| 142 return arg | 151 return arg |
| 143 | 152 |
| 144 return [fix(arg) for arg in command] | 153 return [fix(arg) for arg in command] |
| 145 | 154 |
| 146 | 155 |
| 147 def run_command(command, cwd, tmp_dir, hard_timeout, grace_period): | 156 def run_command(command, cwd, tmp_dir, hard_timeout, grace_period): |
| 148 """Runs the command. | 157 """Runs the command. |
| 149 | 158 |
| 150 Returns: | 159 Returns: |
| 151 tuple(process exit code, bool if had a hard timeout) | 160 tuple(process exit code, bool if had a hard timeout) |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 295 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) | 304 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) |
| 296 stats = { | 305 stats = { |
| 297 'duration': time.time() - start, | 306 'duration': time.time() - start, |
| 298 'items_cold': base64.b64encode(large.pack(cold)), | 307 'items_cold': base64.b64encode(large.pack(cold)), |
| 299 'items_hot': base64.b64encode(large.pack(hot)), | 308 'items_hot': base64.b64encode(large.pack(hot)), |
| 300 } | 309 } |
| 301 return outputs_ref, success, stats | 310 return outputs_ref, success, stats |
| 302 | 311 |
| 303 | 312 |
| 304 def map_and_run( | 313 def map_and_run( |
| 305 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout, | 314 command, isolated_hash, storage, cache, leak_temp_dir, root_dir, |
| 306 grace_period, extra_args): | 315 hard_timeout, grace_period, extra_args): |
| 307 """Maps and run the command. Returns metadata about the result.""" | 316 """Runs a command with optional isolated input/output. |
| 317 |
| 318 See run_tha_test for argument documentation. |
| 319 |
| 320 Returns metadata about the result. |
| 321 """ |
| 322 assert bool(command) ^ bool(isolated_hash) |
| 308 result = { | 323 result = { |
| 309 'duration': None, | 324 'duration': None, |
| 310 'exit_code': None, | 325 'exit_code': None, |
| 311 'had_hard_timeout': False, | 326 'had_hard_timeout': False, |
| 312 'internal_failure': None, | 327 'internal_failure': None, |
| 313 'stats': { | 328 'stats': { |
| 314 # 'download': { | 329 # 'download': { |
| 315 # 'duration': 0., | 330 # 'duration': 0., |
| 316 # 'initial_number_items': 0, | 331 # 'initial_number_items': 0, |
| 317 # 'initial_size': 0, | 332 # 'initial_size': 0, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 329 } | 344 } |
| 330 if root_dir: | 345 if root_dir: |
| 331 file_path.ensure_tree(root_dir, 0700) | 346 file_path.ensure_tree(root_dir, 0700) |
| 332 prefix = u'' | 347 prefix = u'' |
| 333 else: | 348 else: |
| 334 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None | 349 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None |
| 335 prefix = u'isolated_' | 350 prefix = u'isolated_' |
| 336 run_dir = make_temp_dir(prefix + u'run', root_dir) | 351 run_dir = make_temp_dir(prefix + u'run', root_dir) |
| 337 out_dir = make_temp_dir(prefix + u'out', root_dir) | 352 out_dir = make_temp_dir(prefix + u'out', root_dir) |
| 338 tmp_dir = make_temp_dir(prefix + u'tmp', root_dir) | 353 tmp_dir = make_temp_dir(prefix + u'tmp', root_dir) |
| 354 cwd = run_dir |
| 355 |
| 339 try: | 356 try: |
| 340 bundle, result['stats']['download'] = fetch_and_measure( | 357 if isolated_hash: |
| 341 isolated_hash=isolated_hash, | 358 bundle, result['stats']['download'] = fetch_and_measure( |
| 342 storage=storage, | 359 isolated_hash=isolated_hash, |
| 343 cache=cache, | 360 storage=storage, |
| 344 outdir=run_dir) | 361 cache=cache, |
| 345 if not bundle.command: | 362 outdir=run_dir) |
| 346 # Handle this as a task failure, not an internal failure. | 363 if not bundle.command: |
| 347 sys.stderr.write( | 364 # Handle this as a task failure, not an internal failure. |
| 348 '<The .isolated doesn\'t declare any command to run!>\n' | 365 sys.stderr.write( |
| 349 '<Check your .isolate for missing \'command\' variable>\n') | 366 '<The .isolated doesn\'t declare any command to run!>\n' |
| 350 if os.environ.get('SWARMING_TASK_ID'): | 367 '<Check your .isolate for missing \'command\' variable>\n') |
| 351 # Give an additional hint when running as a swarming task. | 368 if os.environ.get('SWARMING_TASK_ID'): |
| 352 sys.stderr.write('<This occurs at the \'isolate\' step>\n') | 369 # Give an additional hint when running as a swarming task. |
| 353 result['exit_code'] = 1 | 370 sys.stderr.write('<This occurs at the \'isolate\' step>\n') |
| 354 return result | 371 result['exit_code'] = 1 |
| 372 return result |
| 355 | 373 |
| 356 change_tree_read_only(run_dir, bundle.read_only) | 374 change_tree_read_only(run_dir, bundle.read_only) |
| 357 cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd)) | 375 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd)) |
| 358 command = bundle.command + extra_args | 376 command = bundle.command + extra_args |
| 359 file_path.ensure_command_has_abs_path(command, cwd) | 377 file_path.ensure_command_has_abs_path(command, cwd) |
| 360 sys.stdout.flush() | 378 sys.stdout.flush() |
| 361 start = time.time() | 379 start = time.time() |
| 362 try: | 380 try: |
| 363 result['exit_code'], result['had_hard_timeout'] = run_command( | 381 result['exit_code'], result['had_hard_timeout'] = run_command( |
| 364 process_command(command, out_dir), cwd, tmp_dir, hard_timeout, | 382 process_command(command, out_dir), cwd, tmp_dir, hard_timeout, |
| 365 grace_period) | 383 grace_period) |
| 366 finally: | 384 finally: |
| 367 result['duration'] = max(time.time() - start, 0) | 385 result['duration'] = max(time.time() - start, 0) |
| 368 except Exception as e: | 386 except Exception as e: |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 417 if not success and result['exit_code'] == 0: | 435 if not success and result['exit_code'] == 0: |
| 418 result['exit_code'] = 1 | 436 result['exit_code'] = 1 |
| 419 except Exception as e: | 437 except Exception as e: |
| 420 # Swallow any exception in the main finally clause. | 438 # Swallow any exception in the main finally clause. |
| 421 logging.exception('Leaking out_dir %s: %s', out_dir, e) | 439 logging.exception('Leaking out_dir %s: %s', out_dir, e) |
| 422 result['internal_failure'] = str(e) | 440 result['internal_failure'] = str(e) |
| 423 return result | 441 return result |
| 424 | 442 |
| 425 | 443 |
| 426 def run_tha_test( | 444 def run_tha_test( |
| 427 isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir, | 445 command, isolated_hash, storage, cache, leak_temp_dir, result_json, |
| 428 hard_timeout, grace_period, extra_args): | 446 root_dir, hard_timeout, grace_period, extra_args): |
| 429 """Downloads the dependencies in the cache, hardlinks them into a temporary | 447 """Runs an executable and records execution metadata. |
| 430 directory and runs the executable from there. | 448 |
| 449 Either command or isolated_hash must be specified. |
| 450 |
| 451 If isolated_hash is specified, downloads the dependencies in the cache, |
| 452 hardlinks them into a temporary directory and runs the command specified in |
| 453 the .isolated. |
| 431 | 454 |
| 432 A temporary directory is created to hold the output files. The content inside | 455 A temporary directory is created to hold the output files. The content inside |
| 433 this directory will be uploaded back to |storage| packaged as a .isolated | 456 this directory will be uploaded back to |storage| packaged as a .isolated |
| 434 file. | 457 file. |
| 435 | 458 |
| 436 Arguments: | 459 Arguments: |
| 460 command: the command to run, a list of strings. Mutually exclusive with |
| 461 isolated_hash. |
| 437 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to | 462 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to |
| 438 recreate the tree of files to run the target executable. | 463 recreate the tree of files to run the target executable. |
| 464 The command specified in the .isolated is executed. |
| 465 Mutually exclusive with command argument. |
| 439 storage: an isolateserver.Storage object to retrieve remote objects. This | 466 storage: an isolateserver.Storage object to retrieve remote objects. This |
| 440 object has a reference to an isolateserver.StorageApi, which does | 467 object has a reference to an isolateserver.StorageApi, which does |
| 441 the actual I/O. | 468 the actual I/O. |
| 442 cache: an isolateserver.LocalCache to keep from retrieving the same objects | 469 cache: an isolateserver.LocalCache to keep from retrieving the same objects |
| 443 constantly by caching the objects retrieved. Can be on-disk or | 470 constantly by caching the objects retrieved. Can be on-disk or |
| 444 in-memory. | 471 in-memory. |
| 445 leak_temp_dir: if true, the temporary directory will be deliberately leaked | 472 leak_temp_dir: if true, the temporary directory will be deliberately leaked |
| 446 for later examination. | 473 for later examination. |
| 447 result_json: file path to dump result metadata into. If set, the process | 474 result_json: file path to dump result metadata into. If set, the process |
| 448 exit code is always 0 unless an internal error occured. | 475 exit code is always 0 unless an internal error occured. |
| 449 root_dir: directory to the path to use to create the temporary directory. If | 476 root_dir: directory to the path to use to create the temporary directory. If |
| 450 not specified, a random temporary directory is created. | 477 not specified, a random temporary directory is created. |
| 451 hard_timeout: kills the process if it lasts more than this amount of | 478 hard_timeout: kills the process if it lasts more than this amount of |
| 452 seconds. | 479 seconds. |
| 453 grace_period: number of seconds to wait between SIGTERM and SIGKILL. | 480 grace_period: number of seconds to wait between SIGTERM and SIGKILL. |
| 454 extra_args: optional arguments to add to the command stated in the .isolate | 481 extra_args: optional arguments to add to the command stated in the .isolate |
| 455 file. | 482 file. Ignored if isolate_hash is empty. |
| 456 | 483 |
| 457 Returns: | 484 Returns: |
| 458 Process exit code that should be used. | 485 Process exit code that should be used. |
| 459 """ | 486 """ |
| 487 assert bool(command) ^ bool(isolated_hash) |
| 488 extra_args = extra_args or [] |
| 489 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)): |
| 490 assert storage is not None, 'storage is None although outdir is specified' |
| 491 |
| 460 if result_json: | 492 if result_json: |
| 461 # Write a json output file right away in case we get killed. | 493 # Write a json output file right away in case we get killed. |
| 462 result = { | 494 result = { |
| 463 'exit_code': None, | 495 'exit_code': None, |
| 464 'had_hard_timeout': False, | 496 'had_hard_timeout': False, |
| 465 'internal_failure': 'Was terminated before completion', | 497 'internal_failure': 'Was terminated before completion', |
| 466 'outputs_ref': None, | 498 'outputs_ref': None, |
| 467 'version': 2, | 499 'version': 2, |
| 468 } | 500 } |
| 469 tools.write_json(result_json, result, dense=True) | 501 tools.write_json(result_json, result, dense=True) |
| 470 | 502 |
| 471 # run_isolated exit code. Depends on if result_json is used or not. | 503 # run_isolated exit code. Depends on if result_json is used or not. |
| 472 result = map_and_run( | 504 result = map_and_run( |
| 473 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout, | 505 command, isolated_hash, storage, cache, leak_temp_dir, root_dir, |
| 474 grace_period, extra_args) | 506 hard_timeout, grace_period, extra_args) |
| 475 logging.info('Result:\n%s', tools.format_json(result, dense=True)) | 507 logging.info('Result:\n%s', tools.format_json(result, dense=True)) |
| 476 if result_json: | 508 if result_json: |
| 477 # We've found tests to delete 'work' when quitting, causing an exception | 509 # We've found tests to delete 'work' when quitting, causing an exception |
| 478 # here. Try to recreate the directory if necessary. | 510 # here. Try to recreate the directory if necessary. |
| 479 file_path.ensure_tree(os.path.dirname(result_json)) | 511 file_path.ensure_tree(os.path.dirname(result_json)) |
| 480 tools.write_json(result_json, result, dense=True) | 512 tools.write_json(result_json, result, dense=True) |
| 481 # Only return 1 if there was an internal error. | 513 # Only return 1 if there was an internal error. |
| 482 return int(bool(result['internal_failure'])) | 514 return int(bool(result['internal_failure'])) |
| 483 | 515 |
| 484 # Marshall into old-style inline output. | 516 # Marshall into old-style inline output. |
| 485 if result['outputs_ref']: | 517 if result['outputs_ref']: |
| 486 data = { | 518 data = { |
| 487 'hash': result['outputs_ref']['isolated'], | 519 'hash': result['outputs_ref']['isolated'], |
| 488 'namespace': result['outputs_ref']['namespace'], | 520 'namespace': result['outputs_ref']['namespace'], |
| 489 'storage': result['outputs_ref']['isolatedserver'], | 521 'storage': result['outputs_ref']['isolatedserver'], |
| 490 } | 522 } |
| 491 sys.stdout.flush() | 523 sys.stdout.flush() |
| 492 print( | 524 print( |
| 493 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % | 525 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % |
| 494 tools.format_json(data, dense=True)) | 526 tools.format_json(data, dense=True)) |
| 495 sys.stdout.flush() | 527 sys.stdout.flush() |
| 496 return result['exit_code'] or int(bool(result['internal_failure'])) | 528 return result['exit_code'] or int(bool(result['internal_failure'])) |
| 497 | 529 |
| 498 | 530 |
| 499 def main(args): | 531 def main(args): |
| 500 parser = logging_utils.OptionParserWithLogging( | 532 parser = logging_utils.OptionParserWithLogging( |
| 501 usage='%prog <options>', | 533 usage='%prog <options> [command to run or extra args]', |
| 502 version=__version__, | 534 version=__version__, |
| 503 log_file=RUN_ISOLATED_LOG_FILE) | 535 log_file=RUN_ISOLATED_LOG_FILE) |
| 504 parser.add_option( | 536 parser.add_option( |
| 505 '--clean', action='store_true', | 537 '--clean', action='store_true', |
| 506 help='Cleans the cache, trimming it necessary and remove corrupted items ' | 538 help='Cleans the cache, trimming it necessary and remove corrupted items ' |
| 507 'and returns without executing anything; use with -v to know what ' | 539 'and returns without executing anything; use with -v to know what ' |
| 508 'was done') | 540 'was done') |
| 509 parser.add_option( | 541 parser.add_option( |
| 510 '--json', | 542 '--json', |
| 511 help='dump output metadata to json file. When used, run_isolated returns ' | 543 help='dump output metadata to json file. When used, run_isolated returns ' |
| 512 'non-zero only on internal failure') | 544 'non-zero only on internal failure') |
| 513 parser.add_option( | 545 parser.add_option( |
| 514 '--hard-timeout', type='float', help='Enforce hard timeout in execution') | 546 '--hard-timeout', type='float', help='Enforce hard timeout in execution') |
| 515 parser.add_option( | 547 parser.add_option( |
| 516 '--grace-period', type='float', | 548 '--grace-period', type='float', |
| 517 help='Grace period between SIGTERM and SIGKILL') | 549 help='Grace period between SIGTERM and SIGKILL') |
| 518 data_group = optparse.OptionGroup(parser, 'Data source') | 550 data_group = optparse.OptionGroup(parser, 'Data source') |
| 519 data_group.add_option( | 551 data_group.add_option( |
| 520 '-s', '--isolated', | 552 '-s', '--isolated', |
| 521 help='Hash of the .isolated to grab from the isolate server') | 553 help='Hash of the .isolated to grab from the isolate server.') |
| 522 isolateserver.add_isolate_server_options(data_group) | 554 isolateserver.add_isolate_server_options(data_group) |
| 523 parser.add_option_group(data_group) | 555 parser.add_option_group(data_group) |
| 524 | 556 |
| 525 isolateserver.add_cache_options(parser) | 557 isolateserver.add_cache_options(parser) |
| 526 parser.set_defaults(cache='cache') | 558 parser.set_defaults(cache='cache') |
| 527 | 559 |
| 528 debug_group = optparse.OptionGroup(parser, 'Debugging') | 560 debug_group = optparse.OptionGroup(parser, 'Debugging') |
| 529 debug_group.add_option( | 561 debug_group.add_option( |
| 530 '--leak-temp-dir', | 562 '--leak-temp-dir', |
| 531 action='store_true', | 563 action='store_true', |
| 532 help='Deliberately leak isolate\'s temp dir for later examination ' | 564 help='Deliberately leak isolate\'s temp dir for later examination ' |
| 533 '[default: %default]') | 565 '[default: %default]') |
| 534 debug_group.add_option( | 566 debug_group.add_option( |
| 535 '--root-dir', help='Use a directory instead of a random one') | 567 '--root-dir', help='Use a directory instead of a random one') |
| 536 parser.add_option_group(debug_group) | 568 parser.add_option_group(debug_group) |
| 537 | 569 |
| 538 auth.add_auth_options(parser) | 570 auth.add_auth_options(parser) |
| 539 options, args = parser.parse_args(args) | 571 options, args = parser.parse_args(args) |
| 540 | 572 |
| 541 cache = isolateserver.process_cache_options(options) | 573 cache = isolateserver.process_cache_options(options) |
| 542 if options.clean: | 574 if options.clean: |
| 543 if options.isolated: | 575 if options.isolated: |
| 544 parser.error('Can\'t use --isolated with --clean.') | 576 parser.error('Can\'t use --isolated with --clean.') |
| 545 if options.isolate_server: | 577 if options.isolate_server: |
| 546 parser.error('Can\'t use --isolate-server with --clean.') | 578 parser.error('Can\'t use --isolate-server with --clean.') |
| 547 if options.json: | 579 if options.json: |
| 548 parser.error('Can\'t use --json with --clean.') | 580 parser.error('Can\'t use --json with --clean.') |
| 549 cache.cleanup() | 581 cache.cleanup() |
| 550 return 0 | 582 return 0 |
| 551 | 583 |
| 584 if not options.isolated and not args: |
| 585 parser.error('--isolated or command to run is required.') |
| 586 |
| 552 auth.process_auth_options(parser, options) | 587 auth.process_auth_options(parser, options) |
| 553 isolateserver.process_isolate_server_options(parser, options, True) | 588 |
| 589 isolateserver.process_isolate_server_options( |
| 590 parser, options, True, False) |
| 591 if not options.isolate_server: |
| 592 if options.isolated: |
| 593 parser.error('--isolated requires --isolate-server') |
| 594 if ISOLATED_OUTDIR_PARAMETER in args: |
| 595 parser.error( |
| 596 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER) |
| 554 | 597 |
| 555 if options.root_dir: | 598 if options.root_dir: |
| 556 options.root_dir = unicode(os.path.abspath(options.root_dir)) | 599 options.root_dir = unicode(os.path.abspath(options.root_dir)) |
| 557 if options.json: | 600 if options.json: |
| 558 options.json = unicode(os.path.abspath(options.json)) | 601 options.json = unicode(os.path.abspath(options.json)) |
| 559 if not options.isolated: | 602 |
| 560 parser.error('--isolated is required.') | 603 command = [] if options.isolated else args |
| 561 with isolateserver.get_storage( | 604 if options.isolate_server: |
| 562 options.isolate_server, options.namespace) as storage: | 605 storage = isolateserver.get_storage( |
| 606 options.isolate_server, options.namespace) |
| 563 # Hashing schemes used by |storage| and |cache| MUST match. | 607 # Hashing schemes used by |storage| and |cache| MUST match. |
| 564 assert storage.hash_algo == cache.hash_algo | 608 assert storage.hash_algo == cache.hash_algo |
| 565 return run_tha_test( | 609 return run_tha_test( |
| 566 options.isolated, storage, cache, options.leak_temp_dir, options.json, | 610 command, options.isolated, storage, cache, options.leak_temp_dir, |
| 567 options.root_dir, options.hard_timeout, options.grace_period, args) | 611 options.json, options.root_dir, options.hard_timeout, |
| 612 options.grace_period, args) |
| 613 else: |
| 614 return run_tha_test( |
| 615 command, options.isolated, None, cache, options.leak_temp_dir, |
| 616 options.json, options.root_dir, options.hard_timeout, |
| 617 options.grace_period, args) |
| 568 | 618 |
| 569 | 619 |
| 570 if __name__ == '__main__': | 620 if __name__ == '__main__': |
| 571 # Ensure that we are always running with the correct encoding. | 621 # Ensure that we are always running with the correct encoding. |
| 572 fix_encoding.fix_encoding() | 622 fix_encoding.fix_encoding() |
| 573 sys.exit(main(sys.argv[1:])) | 623 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |