blob: fba02963999545b33c91825b6887d54a83a1a09f [file] [log] [blame]
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001#!/usr/bin/env python
Marc-Antoine Ruel8add1242013-11-05 17:28:27 -05002# Copyright 2012 The Swarming Authors. All rights reserved.
Marc-Antoine Ruele98b1122013-11-05 20:27:57 -05003# Use of this source code is governed under the Apache License, Version 2.0 that
4# can be found in the LICENSE file.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00005
maruel@chromium.org0cd0b182012-10-22 13:34:15 +00006"""Reads a .isolated, creates a tree of hardlinks and runs the test.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00007
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -05008To improve performance, it keeps a local cache. The local cache can safely be
9deleted.
10
11Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a
12temporary directory upon execution of the command specified in the .isolated
13file. All content written to this directory will be uploaded upon termination
14and the .isolated file describing this directory will be printed to stdout.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000015"""
16
maruel12e30012015-10-09 11:55:35 -070017__version__ = '0.5.4'
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000018
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000019import logging
20import optparse
21import os
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000022import sys
23import tempfile
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000024
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000025from third_party.depot_tools import fix_encoding
26
Vadim Shtayura6b555c12014-07-23 16:22:18 -070027from utils import file_path
maruel12e30012015-10-09 11:55:35 -070028from utils import fs
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -040029from utils import logging_utils
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040030from utils import on_error
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -050031from utils import subprocess42
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000032from utils import tools
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +000033from utils import zip_package
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000034
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080035import auth
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -040036import isolated_format
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000037import isolateserver
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000038
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000039
vadimsh@chromium.org85071062013-08-21 23:37:45 +000040# Absolute path to this file (can be None if running from zip on Mac).
41THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000042
43# Directory that contains this file (might be inside zip package).
vadimsh@chromium.org85071062013-08-21 23:37:45 +000044BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000045
46# Directory that contains currently running script file.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000047if zip_package.get_main_script_path():
48 MAIN_DIR = os.path.dirname(
49 os.path.abspath(zip_package.get_main_script_path()))
50else:
51 # This happens when 'import run_isolated' is executed at the python
52 # interactive prompt, in that case __file__ is undefined.
53 MAIN_DIR = None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000054
csharp@chromium.orgff2a4662012-11-21 20:49:32 +000055# The name of the log file to use.
56RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
57
csharp@chromium.orge217f302012-11-22 16:51:53 +000058# The name of the log to use for the run_test_cases.py command
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000059RUN_TEST_CASES_LOG = 'run_test_cases.log'
csharp@chromium.orge217f302012-11-22 16:51:53 +000060
vadimsh@chromium.org87d63262013-04-04 19:34:21 +000061
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000062def get_as_zip_package(executable=True):
63 """Returns ZipPackage with this module and all its dependencies.
64
65 If |executable| is True will store run_isolated.py as __main__.py so that
66 zip package is directly executable be python.
67 """
68 # Building a zip package when running from another zip package is
69 # unsupported and probably unneeded.
70 assert not zip_package.is_zipped_module(sys.modules[__name__])
vadimsh@chromium.org85071062013-08-21 23:37:45 +000071 assert THIS_FILE_PATH
72 assert BASE_DIR
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000073 package = zip_package.ZipPackage(root=BASE_DIR)
74 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None)
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -040075 package.add_python_file(os.path.join(BASE_DIR, 'isolated_format.py'))
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000076 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py'))
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080077 package.add_python_file(os.path.join(BASE_DIR, 'auth.py'))
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000078 package.add_directory(os.path.join(BASE_DIR, 'third_party'))
79 package.add_directory(os.path.join(BASE_DIR, 'utils'))
80 return package
81
82
Vadim Shtayuracb0b7432015-07-31 13:26:50 -070083def make_temp_dir(prefix, root_dir=None):
84 """Returns a temporary directory.
85
86 If root_dir is given and /tmp is on same file system as root_dir, uses /tmp.
87 Otherwise makes a new temp directory under root_dir.
88 """
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000089 base_temp_dir = None
Marc-Antoine Ruele4ad07e2014-10-15 20:22:29 -040090 if (root_dir and
maruel12e30012015-10-09 11:55:35 -070091 not file_path.is_same_filesystem(
92 root_dir, unicode(tempfile.gettempdir()))):
Paweł Hajdan, Jrf7d58722015-04-27 14:54:42 +020093 base_temp_dir = root_dir
marueleb5fbee2015-09-17 13:01:36 -070094 return unicode(tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000095
96
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -050097def change_tree_read_only(rootdir, read_only):
98 """Changes the tree read-only bits according to the read_only specification.
99
100 The flag can be 0, 1 or 2, which will affect the possibility to modify files
101 and create or delete files.
102 """
103 if read_only == 2:
104 # Files and directories (except on Windows) are marked read only. This
105 # inhibits modifying, creating or deleting files in the test directory,
106 # except on Windows where creating and deleting files is still possible.
Marc-Antoine Ruele4ad07e2014-10-15 20:22:29 -0400107 file_path.make_tree_read_only(rootdir)
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500108 elif read_only == 1:
109 # Files are marked read only but not the directories. This inhibits
110 # modifying files but creating or deleting files is still possible.
Marc-Antoine Ruele4ad07e2014-10-15 20:22:29 -0400111 file_path.make_tree_files_read_only(rootdir)
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500112 elif read_only in (0, None):
Marc-Antoine Ruelf1d827c2014-11-24 15:22:25 -0500113 # Anything can be modified.
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500114 # TODO(maruel): This is currently dangerous as long as DiskCache.touch()
115 # is not yet changed to verify the hash of the content of the files it is
116 # looking at, so that if a test modifies an input file, the file must be
117 # deleted.
Marc-Antoine Ruele4ad07e2014-10-15 20:22:29 -0400118 file_path.make_tree_writeable(rootdir)
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500119 else:
120 raise ValueError(
121 'change_tree_read_only(%s, %s): Unknown flag %s' %
122 (rootdir, read_only, read_only))
123
124
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500125def process_command(command, out_dir):
126 """Replaces isolated specific variables in a command line."""
maruela9cfd6f2015-09-15 11:03:15 -0700127 def fix(arg):
Vadim Shtayura51aba362014-05-14 15:39:23 -0700128 if '${ISOLATED_OUTDIR}' in arg:
maruela9cfd6f2015-09-15 11:03:15 -0700129 return arg.replace('${ISOLATED_OUTDIR}', out_dir).replace('/', os.sep)
130 return arg
131
132 return [fix(arg) for arg in command]
133
134
maruel6be7f9e2015-10-01 12:25:30 -0700135def run_command(command, cwd, tmp_dir, hard_timeout, grace_period):
136 """Runs the command.
137
138 Returns:
139 tuple(process exit code, bool if had a hard timeout)
140 """
maruela9cfd6f2015-09-15 11:03:15 -0700141 logging.info('run_command(%s, %s)' % (command, cwd))
142 sys.stdout.flush()
marueleb5fbee2015-09-17 13:01:36 -0700143
144 env = os.environ.copy()
145 if sys.platform == 'darwin':
146 env['TMPDIR'] = tmp_dir.encode('ascii')
147 elif sys.platform == 'win32':
maruelf71d1a32015-09-18 11:27:35 -0700148 # Temporarily disable this behavior on Windows while investigating
149 # https://crbug.com/533552.
150 # env['TEMP'] = tmp_dir.encode('ascii')
151 pass
marueleb5fbee2015-09-17 13:01:36 -0700152 else:
153 env['TMP'] = tmp_dir.encode('ascii')
maruel6be7f9e2015-10-01 12:25:30 -0700154 exit_code = None
155 had_hard_timeout = False
maruela9cfd6f2015-09-15 11:03:15 -0700156 with tools.Profiler('RunTest'):
maruel6be7f9e2015-10-01 12:25:30 -0700157 proc = None
158 had_signal = []
maruela9cfd6f2015-09-15 11:03:15 -0700159 try:
maruel6be7f9e2015-10-01 12:25:30 -0700160 # TODO(maruel): This code is imperfect. It doesn't handle well signals
161 # during the download phase and there's short windows were things can go
162 # wrong.
163 def handler(signum, _frame):
164 if proc and not had_signal:
165 logging.info('Received signal %d', signum)
166 had_signal.append(True)
maruel556d9052015-10-05 11:12:44 -0700167 raise subprocess42.TimeoutExpired(command, None)
maruel6be7f9e2015-10-01 12:25:30 -0700168
169 proc = subprocess42.Popen(command, cwd=cwd, env=env, detached=True)
170 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, handler):
171 try:
172 exit_code = proc.wait(hard_timeout or None)
173 except subprocess42.TimeoutExpired:
174 if not had_signal:
175 logging.warning('Hard timeout')
176 had_hard_timeout = True
177 logging.warning('Sending SIGTERM')
178 proc.terminate()
179
180 # Ignore signals in grace period. Forcibly give the grace period to the
181 # child process.
182 if exit_code is None:
183 ignore = lambda *_: None
184 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, ignore):
185 try:
186 exit_code = proc.wait(grace_period or None)
187 except subprocess42.TimeoutExpired:
188 # Now kill for real. The user can distinguish between the
189 # following states:
190 # - signal but process exited within grace period,
191 # hard_timed_out will be set but the process exit code will be
192 # script provided.
193 # - processed exited late, exit code will be -9 on posix.
194 logging.warning('Grace exhausted; sending SIGKILL')
195 proc.kill()
196 logging.info('Waiting for proces exit')
197 exit_code = proc.wait()
maruela9cfd6f2015-09-15 11:03:15 -0700198 except OSError:
199 # This is not considered to be an internal error. The executable simply
200 # does not exit.
201 exit_code = 1
202 logging.info(
203 'Command finished with exit code %d (%s)',
204 exit_code, hex(0xffffffff & exit_code))
maruel6be7f9e2015-10-01 12:25:30 -0700205 return exit_code, had_hard_timeout
maruela9cfd6f2015-09-15 11:03:15 -0700206
207
208def delete_and_upload(storage, out_dir, leak_temp_dir):
209 """Deletes the temporary run directory and uploads results back.
210
211 Returns:
212 tuple(outputs_ref, success)
213 - outputs_ref is a dict referring to the results archived back to the
214 isolated server, if applicable.
215 - success is False if something occurred that means that the task must
216 forcibly be considered a failure, e.g. zombie processes were left behind.
217 """
218
219 # Upload out_dir and generate a .isolated file out of this directory. It is
220 # only done if files were written in the directory.
221 outputs_ref = None
maruel12e30012015-10-09 11:55:35 -0700222 if fs.isdir(out_dir) and fs.listdir(out_dir):
maruela9cfd6f2015-09-15 11:03:15 -0700223 with tools.Profiler('ArchiveOutput'):
224 try:
225 results = isolateserver.archive_files_to_storage(
226 storage, [out_dir], None)
227 outputs_ref = {
228 'isolated': results[0][0],
229 'isolatedserver': storage.location,
230 'namespace': storage.namespace,
231 }
232 except isolateserver.Aborted:
233 # This happens when a signal SIGTERM was received while uploading data.
234 # There is 2 causes:
235 # - The task was too slow and was about to be killed anyway due to
236 # exceeding the hard timeout.
237 # - The amount of data uploaded back is very large and took too much
238 # time to archive.
239 sys.stderr.write('Received SIGTERM while uploading')
240 # Re-raise, so it will be treated as an internal failure.
241 raise
242 try:
maruel12e30012015-10-09 11:55:35 -0700243 if (not leak_temp_dir and fs.isdir(out_dir) and
maruel6eeea7d2015-09-16 12:17:42 -0700244 not file_path.rmtree(out_dir)):
maruela9cfd6f2015-09-15 11:03:15 -0700245 logging.error('Had difficulties removing out_dir %s', out_dir)
246 return outputs_ref, False
247 except OSError as e:
248 # When this happens, it means there's a process error.
maruel12e30012015-10-09 11:55:35 -0700249 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700250 return outputs_ref, False
251 return outputs_ref, True
252
253
marueleb5fbee2015-09-17 13:01:36 -0700254def map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700255 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
256 grace_period, extra_args):
maruela9cfd6f2015-09-15 11:03:15 -0700257 """Maps and run the command. Returns metadata about the result."""
258 # TODO(maruel): Include performance statistics.
259 result = {
260 'exit_code': None,
maruel6be7f9e2015-10-01 12:25:30 -0700261 'had_hard_timeout': False,
maruela9cfd6f2015-09-15 11:03:15 -0700262 'internal_failure': None,
263 'outputs_ref': None,
maruel6be7f9e2015-10-01 12:25:30 -0700264 'version': 2,
maruela9cfd6f2015-09-15 11:03:15 -0700265 }
marueleb5fbee2015-09-17 13:01:36 -0700266 if root_dir:
maruel12e30012015-10-09 11:55:35 -0700267 if not fs.isdir(root_dir):
268 fs.makedirs(root_dir, 0700)
marueleb5fbee2015-09-17 13:01:36 -0700269 prefix = u''
270 else:
271 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
272 prefix = u'isolated_'
273 run_dir = make_temp_dir(prefix + u'run', root_dir)
274 out_dir = make_temp_dir(prefix + u'out', root_dir)
275 tmp_dir = make_temp_dir(prefix + u'tmp', root_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700276 try:
277 bundle = isolateserver.fetch_isolated(
278 isolated_hash=isolated_hash,
279 storage=storage,
280 cache=cache,
281 outdir=run_dir,
282 require_command=True)
283
284 change_tree_read_only(run_dir, bundle.read_only)
285 cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
286 command = bundle.command + extra_args
287 file_path.ensure_command_has_abs_path(command, cwd)
maruel6be7f9e2015-10-01 12:25:30 -0700288 result['exit_code'], result['had_hard_timeout'] = run_command(
289 process_command(command, out_dir), cwd, tmp_dir, hard_timeout,
290 grace_period)
maruela9cfd6f2015-09-15 11:03:15 -0700291 except Exception as e:
292 # An internal error occured. Report accordingly so the swarming task will be
293 # retried automatically.
maruel12e30012015-10-09 11:55:35 -0700294 logging.exception('internal failure: %s', e)
maruela9cfd6f2015-09-15 11:03:15 -0700295 result['internal_failure'] = str(e)
296 on_error.report(None)
297 finally:
298 try:
299 if leak_temp_dir:
300 logging.warning(
301 'Deliberately leaking %s for later examination', run_dir)
marueleb5fbee2015-09-17 13:01:36 -0700302 else:
maruel12e30012015-10-09 11:55:35 -0700303 if fs.isdir(run_dir) and not file_path.rmtree(run_dir):
marueleb5fbee2015-09-17 13:01:36 -0700304 # On Windows rmtree(run_dir) call above has a synchronization effect:
305 # it finishes only when all task child processes terminate (since a
306 # running process locks *.exe file). Examine out_dir only after that
307 # call completes (since child processes may write to out_dir too and
308 # we need to wait for them to finish).
309 print >> sys.stderr, (
310 'Failed to delete the run directory, forcibly failing\n'
311 'the task because of it. No zombie process can outlive a\n'
312 'successful task run and still be marked as successful.\n'
313 'Fix your stuff.')
314 if result['exit_code'] == 0:
315 result['exit_code'] = 1
maruel12e30012015-10-09 11:55:35 -0700316 if fs.isdir(tmp_dir) and not file_path.rmtree(tmp_dir):
marueleb5fbee2015-09-17 13:01:36 -0700317 print >> sys.stderr, (
318 'Failed to delete the temporary directory, forcibly failing\n'
319 'the task because of it. No zombie process can outlive a\n'
320 'successful task run and still be marked as successful.\n'
321 'Fix your stuff.')
322 if result['exit_code'] == 0:
323 result['exit_code'] = 1
maruela9cfd6f2015-09-15 11:03:15 -0700324
marueleb5fbee2015-09-17 13:01:36 -0700325 # This deletes out_dir if leak_temp_dir is not set.
maruela9cfd6f2015-09-15 11:03:15 -0700326 result['outputs_ref'], success = delete_and_upload(
327 storage, out_dir, leak_temp_dir)
328 if not success and result['exit_code'] == 0:
329 result['exit_code'] = 1
330 except Exception as e:
331 # Swallow any exception in the main finally clause.
maruel12e30012015-10-09 11:55:35 -0700332 logging.exception('Leaking out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700333 result['internal_failure'] = str(e)
334 return result
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500335
336
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400337def run_tha_test(
marueleb5fbee2015-09-17 13:01:36 -0700338 isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir,
maruel6be7f9e2015-10-01 12:25:30 -0700339 hard_timeout, grace_period, extra_args):
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500340 """Downloads the dependencies in the cache, hardlinks them into a temporary
341 directory and runs the executable from there.
342
343 A temporary directory is created to hold the output files. The content inside
344 this directory will be uploaded back to |storage| packaged as a .isolated
345 file.
346
347 Arguments:
Marc-Antoine Ruel35b58432014-12-08 17:40:40 -0500348 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500349 recreate the tree of files to run the target executable.
350 storage: an isolateserver.Storage object to retrieve remote objects. This
351 object has a reference to an isolateserver.StorageApi, which does
352 the actual I/O.
353 cache: an isolateserver.LocalCache to keep from retrieving the same objects
354 constantly by caching the objects retrieved. Can be on-disk or
355 in-memory.
Kenneth Russell61d42352014-09-15 11:41:16 -0700356 leak_temp_dir: if true, the temporary directory will be deliberately leaked
357 for later examination.
maruela9cfd6f2015-09-15 11:03:15 -0700358 result_json: file path to dump result metadata into. If set, the process
359 exit code is always 0 unless an internal error occured.
marueleb5fbee2015-09-17 13:01:36 -0700360 root_dir: directory to the path to use to create the temporary directory. If
361 not specified, a random temporary directory is created.
maruel6be7f9e2015-10-01 12:25:30 -0700362 hard_timeout: kills the process if it lasts more than this amount of
363 seconds.
364 grace_period: number of seconds to wait between SIGTERM and SIGKILL.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500365 extra_args: optional arguments to add to the command stated in the .isolate
366 file.
maruela9cfd6f2015-09-15 11:03:15 -0700367
368 Returns:
369 Process exit code that should be used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000370 """
maruela9cfd6f2015-09-15 11:03:15 -0700371 # run_isolated exit code. Depends on if result_json is used or not.
372 result = map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700373 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
374 grace_period, extra_args)
maruela9cfd6f2015-09-15 11:03:15 -0700375 logging.info('Result:\n%s', tools.format_json(result, dense=True))
376 if result_json:
maruel05d5a882015-09-21 13:59:02 -0700377 # We've found tests to delete 'work' when quitting, causing an exception
378 # here. Try to recreate the directory if necessary.
379 work_dir = os.path.dirname(result_json)
maruel12e30012015-10-09 11:55:35 -0700380 if not fs.isdir(work_dir):
381 fs.mkdir(work_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700382 tools.write_json(result_json, result, dense=True)
383 # Only return 1 if there was an internal error.
384 return int(bool(result['internal_failure']))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000385
maruela9cfd6f2015-09-15 11:03:15 -0700386 # Marshall into old-style inline output.
387 if result['outputs_ref']:
388 data = {
389 'hash': result['outputs_ref']['isolated'],
390 'namespace': result['outputs_ref']['namespace'],
391 'storage': result['outputs_ref']['isolatedserver'],
392 }
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -0500393 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700394 print(
395 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
396 tools.format_json(data, dense=True))
397 return result['exit_code'] or int(bool(result['internal_failure']))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000398
399
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500400def main(args):
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000401 tools.disable_buffering()
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400402 parser = logging_utils.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000403 usage='%prog <options>',
404 version=__version__,
405 log_file=RUN_ISOLATED_LOG_FILE)
maruela9cfd6f2015-09-15 11:03:15 -0700406 parser.add_option(
407 '--json',
408 help='dump output metadata to json file. When used, run_isolated returns '
409 'non-zero only on internal failure')
maruel6be7f9e2015-10-01 12:25:30 -0700410 parser.add_option(
411 '--hard-timeout', type='int', help='Enforce hard timeout in execution')
412 parser.add_option(
413 '--grace-period', type='int',
414 help='Grace period between SIGTERM and SIGKILL')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500415 data_group = optparse.OptionGroup(parser, 'Data source')
416 data_group.add_option(
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500417 '-s', '--isolated',
418 help='Hash of the .isolated to grab from the isolate server')
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500419 isolateserver.add_isolate_server_options(data_group)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500420 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000421
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400422 isolateserver.add_cache_options(parser)
423 parser.set_defaults(cache='cache')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000424
Kenneth Russell61d42352014-09-15 11:41:16 -0700425 debug_group = optparse.OptionGroup(parser, 'Debugging')
426 debug_group.add_option(
427 '--leak-temp-dir',
428 action='store_true',
429 help='Deliberately leak isolate\'s temp dir for later examination '
430 '[default: %default]')
marueleb5fbee2015-09-17 13:01:36 -0700431 debug_group.add_option(
432 '--root-dir', help='Use a directory instead of a random one')
Kenneth Russell61d42352014-09-15 11:41:16 -0700433 parser.add_option_group(debug_group)
434
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800435 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500436 options, args = parser.parse_args(args)
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500437 if not options.isolated:
438 parser.error('--isolated is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800439 auth.process_auth_options(parser, options)
Marc-Antoine Ruele290ada2014-12-10 19:48:49 -0500440 isolateserver.process_isolate_server_options(parser, options, True)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000441
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400442 cache = isolateserver.process_cache_options(options)
maruel12e30012015-10-09 11:55:35 -0700443 if options.root_dir:
444 options.root_dir = unicode(os.path.abspath(options.root_dir))
445 if options.json:
446 options.json = unicode(os.path.abspath(options.json))
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500447 with isolateserver.get_storage(
448 options.isolate_server, options.namespace) as storage:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400449 # Hashing schemes used by |storage| and |cache| MUST match.
450 assert storage.hash_algo == cache.hash_algo
451 return run_tha_test(
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400452 options.isolated, storage, cache, options.leak_temp_dir, options.json,
maruel6be7f9e2015-10-01 12:25:30 -0700453 options.root_dir, options.hard_timeout, options.grace_period, args)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000454
455
456if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000457 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000458 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500459 sys.exit(main(sys.argv[1:]))