blob: 3d0b18b50a62860b00832582a29d74014415d426 [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
maruelb76604c2015-11-11 11:53:44 -080017__version__ = '0.5.5'
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':
marueldf2329b2016-01-19 15:33:23 -0800148 env['TEMP'] = tmp_dir.encode('ascii')
marueleb5fbee2015-09-17 13:01:36 -0700149 else:
150 env['TMP'] = tmp_dir.encode('ascii')
maruel6be7f9e2015-10-01 12:25:30 -0700151 exit_code = None
152 had_hard_timeout = False
maruela9cfd6f2015-09-15 11:03:15 -0700153 with tools.Profiler('RunTest'):
maruel6be7f9e2015-10-01 12:25:30 -0700154 proc = None
155 had_signal = []
maruela9cfd6f2015-09-15 11:03:15 -0700156 try:
maruel6be7f9e2015-10-01 12:25:30 -0700157 # TODO(maruel): This code is imperfect. It doesn't handle well signals
158 # during the download phase and there's short windows were things can go
159 # wrong.
160 def handler(signum, _frame):
161 if proc and not had_signal:
162 logging.info('Received signal %d', signum)
163 had_signal.append(True)
maruel556d9052015-10-05 11:12:44 -0700164 raise subprocess42.TimeoutExpired(command, None)
maruel6be7f9e2015-10-01 12:25:30 -0700165
166 proc = subprocess42.Popen(command, cwd=cwd, env=env, detached=True)
167 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, handler):
168 try:
169 exit_code = proc.wait(hard_timeout or None)
170 except subprocess42.TimeoutExpired:
171 if not had_signal:
172 logging.warning('Hard timeout')
173 had_hard_timeout = True
174 logging.warning('Sending SIGTERM')
175 proc.terminate()
176
177 # Ignore signals in grace period. Forcibly give the grace period to the
178 # child process.
179 if exit_code is None:
180 ignore = lambda *_: None
181 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, ignore):
182 try:
183 exit_code = proc.wait(grace_period or None)
184 except subprocess42.TimeoutExpired:
185 # Now kill for real. The user can distinguish between the
186 # following states:
187 # - signal but process exited within grace period,
188 # hard_timed_out will be set but the process exit code will be
189 # script provided.
190 # - processed exited late, exit code will be -9 on posix.
191 logging.warning('Grace exhausted; sending SIGKILL')
192 proc.kill()
193 logging.info('Waiting for proces exit')
194 exit_code = proc.wait()
maruela9cfd6f2015-09-15 11:03:15 -0700195 except OSError:
196 # This is not considered to be an internal error. The executable simply
197 # does not exit.
198 exit_code = 1
199 logging.info(
200 'Command finished with exit code %d (%s)',
201 exit_code, hex(0xffffffff & exit_code))
maruel6be7f9e2015-10-01 12:25:30 -0700202 return exit_code, had_hard_timeout
maruela9cfd6f2015-09-15 11:03:15 -0700203
204
205def delete_and_upload(storage, out_dir, leak_temp_dir):
206 """Deletes the temporary run directory and uploads results back.
207
208 Returns:
209 tuple(outputs_ref, success)
210 - outputs_ref is a dict referring to the results archived back to the
211 isolated server, if applicable.
212 - success is False if something occurred that means that the task must
213 forcibly be considered a failure, e.g. zombie processes were left behind.
214 """
215
216 # Upload out_dir and generate a .isolated file out of this directory. It is
217 # only done if files were written in the directory.
218 outputs_ref = None
maruel12e30012015-10-09 11:55:35 -0700219 if fs.isdir(out_dir) and fs.listdir(out_dir):
maruela9cfd6f2015-09-15 11:03:15 -0700220 with tools.Profiler('ArchiveOutput'):
221 try:
222 results = isolateserver.archive_files_to_storage(
223 storage, [out_dir], None)
224 outputs_ref = {
225 'isolated': results[0][0],
226 'isolatedserver': storage.location,
227 'namespace': storage.namespace,
228 }
229 except isolateserver.Aborted:
230 # This happens when a signal SIGTERM was received while uploading data.
231 # There is 2 causes:
232 # - The task was too slow and was about to be killed anyway due to
233 # exceeding the hard timeout.
234 # - The amount of data uploaded back is very large and took too much
235 # time to archive.
236 sys.stderr.write('Received SIGTERM while uploading')
237 # Re-raise, so it will be treated as an internal failure.
238 raise
239 try:
maruel12e30012015-10-09 11:55:35 -0700240 if (not leak_temp_dir and fs.isdir(out_dir) and
maruel6eeea7d2015-09-16 12:17:42 -0700241 not file_path.rmtree(out_dir)):
maruela9cfd6f2015-09-15 11:03:15 -0700242 logging.error('Had difficulties removing out_dir %s', out_dir)
243 return outputs_ref, False
244 except OSError as e:
245 # When this happens, it means there's a process error.
maruel12e30012015-10-09 11:55:35 -0700246 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700247 return outputs_ref, False
248 return outputs_ref, True
249
250
marueleb5fbee2015-09-17 13:01:36 -0700251def map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700252 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
253 grace_period, extra_args):
maruela9cfd6f2015-09-15 11:03:15 -0700254 """Maps and run the command. Returns metadata about the result."""
255 # TODO(maruel): Include performance statistics.
256 result = {
257 'exit_code': None,
maruel6be7f9e2015-10-01 12:25:30 -0700258 'had_hard_timeout': False,
maruela9cfd6f2015-09-15 11:03:15 -0700259 'internal_failure': None,
260 'outputs_ref': None,
maruel6be7f9e2015-10-01 12:25:30 -0700261 'version': 2,
maruela9cfd6f2015-09-15 11:03:15 -0700262 }
marueleb5fbee2015-09-17 13:01:36 -0700263 if root_dir:
maruel12e30012015-10-09 11:55:35 -0700264 if not fs.isdir(root_dir):
265 fs.makedirs(root_dir, 0700)
marueleb5fbee2015-09-17 13:01:36 -0700266 prefix = u''
267 else:
268 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
269 prefix = u'isolated_'
270 run_dir = make_temp_dir(prefix + u'run', root_dir)
271 out_dir = make_temp_dir(prefix + u'out', root_dir)
272 tmp_dir = make_temp_dir(prefix + u'tmp', root_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700273 try:
274 bundle = isolateserver.fetch_isolated(
275 isolated_hash=isolated_hash,
276 storage=storage,
277 cache=cache,
278 outdir=run_dir,
279 require_command=True)
280
281 change_tree_read_only(run_dir, bundle.read_only)
282 cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
283 command = bundle.command + extra_args
284 file_path.ensure_command_has_abs_path(command, cwd)
maruel6be7f9e2015-10-01 12:25:30 -0700285 result['exit_code'], result['had_hard_timeout'] = run_command(
286 process_command(command, out_dir), cwd, tmp_dir, hard_timeout,
287 grace_period)
maruela9cfd6f2015-09-15 11:03:15 -0700288 except Exception as e:
289 # An internal error occured. Report accordingly so the swarming task will be
290 # retried automatically.
maruel12e30012015-10-09 11:55:35 -0700291 logging.exception('internal failure: %s', e)
maruela9cfd6f2015-09-15 11:03:15 -0700292 result['internal_failure'] = str(e)
293 on_error.report(None)
294 finally:
295 try:
296 if leak_temp_dir:
297 logging.warning(
298 'Deliberately leaking %s for later examination', run_dir)
marueleb5fbee2015-09-17 13:01:36 -0700299 else:
maruel84537cb2015-10-16 14:21:28 -0700300 # On Windows rmtree(run_dir) call above has a synchronization effect: it
301 # finishes only when all task child processes terminate (since a running
302 # process locks *.exe file). Examine out_dir only after that call
303 # completes (since child processes may write to out_dir too and we need
304 # to wait for them to finish).
305 if fs.isdir(run_dir):
306 try:
307 success = file_path.rmtree(run_dir)
308 except OSError as e:
309 logging.error('Failure with %s', e)
310 success = False
311 if not success:
312 print >> sys.stderr, (
313 'Failed to delete the run directory, forcibly failing\n'
314 'the task because of it. No zombie process can outlive a\n'
315 'successful task run and still be marked as successful.\n'
316 'Fix your stuff.')
317 if result['exit_code'] == 0:
318 result['exit_code'] = 1
319 if fs.isdir(tmp_dir):
320 try:
321 success = file_path.rmtree(tmp_dir)
322 except OSError as e:
323 logging.error('Failure with %s', e)
324 success = False
325 if not success:
326 print >> sys.stderr, (
327 'Failed to delete the temporary directory, forcibly failing\n'
328 'the task because of it. No zombie process can outlive a\n'
329 'successful task run and still be marked as successful.\n'
330 'Fix your stuff.')
331 if result['exit_code'] == 0:
332 result['exit_code'] = 1
maruela9cfd6f2015-09-15 11:03:15 -0700333
marueleb5fbee2015-09-17 13:01:36 -0700334 # This deletes out_dir if leak_temp_dir is not set.
maruela9cfd6f2015-09-15 11:03:15 -0700335 result['outputs_ref'], success = delete_and_upload(
336 storage, out_dir, leak_temp_dir)
337 if not success and result['exit_code'] == 0:
338 result['exit_code'] = 1
339 except Exception as e:
340 # Swallow any exception in the main finally clause.
maruel12e30012015-10-09 11:55:35 -0700341 logging.exception('Leaking out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700342 result['internal_failure'] = str(e)
343 return result
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500344
345
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400346def run_tha_test(
marueleb5fbee2015-09-17 13:01:36 -0700347 isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir,
maruel6be7f9e2015-10-01 12:25:30 -0700348 hard_timeout, grace_period, extra_args):
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500349 """Downloads the dependencies in the cache, hardlinks them into a temporary
350 directory and runs the executable from there.
351
352 A temporary directory is created to hold the output files. The content inside
353 this directory will be uploaded back to |storage| packaged as a .isolated
354 file.
355
356 Arguments:
Marc-Antoine Ruel35b58432014-12-08 17:40:40 -0500357 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500358 recreate the tree of files to run the target executable.
359 storage: an isolateserver.Storage object to retrieve remote objects. This
360 object has a reference to an isolateserver.StorageApi, which does
361 the actual I/O.
362 cache: an isolateserver.LocalCache to keep from retrieving the same objects
363 constantly by caching the objects retrieved. Can be on-disk or
364 in-memory.
Kenneth Russell61d42352014-09-15 11:41:16 -0700365 leak_temp_dir: if true, the temporary directory will be deliberately leaked
366 for later examination.
maruela9cfd6f2015-09-15 11:03:15 -0700367 result_json: file path to dump result metadata into. If set, the process
368 exit code is always 0 unless an internal error occured.
marueleb5fbee2015-09-17 13:01:36 -0700369 root_dir: directory to the path to use to create the temporary directory. If
370 not specified, a random temporary directory is created.
maruel6be7f9e2015-10-01 12:25:30 -0700371 hard_timeout: kills the process if it lasts more than this amount of
372 seconds.
373 grace_period: number of seconds to wait between SIGTERM and SIGKILL.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500374 extra_args: optional arguments to add to the command stated in the .isolate
375 file.
maruela9cfd6f2015-09-15 11:03:15 -0700376
377 Returns:
378 Process exit code that should be used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000379 """
maruela76b9ee2015-12-15 06:18:08 -0800380 if result_json:
381 # Write a json output file right away in case we get killed.
382 result = {
383 'exit_code': None,
384 'had_hard_timeout': False,
385 'internal_failure': 'Was terminated before completion',
386 'outputs_ref': None,
387 'version': 2,
388 }
389 tools.write_json(result_json, result, dense=True)
390
maruela9cfd6f2015-09-15 11:03:15 -0700391 # run_isolated exit code. Depends on if result_json is used or not.
392 result = map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700393 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
394 grace_period, extra_args)
maruela9cfd6f2015-09-15 11:03:15 -0700395 logging.info('Result:\n%s', tools.format_json(result, dense=True))
396 if result_json:
maruel05d5a882015-09-21 13:59:02 -0700397 # We've found tests to delete 'work' when quitting, causing an exception
398 # here. Try to recreate the directory if necessary.
399 work_dir = os.path.dirname(result_json)
maruel12e30012015-10-09 11:55:35 -0700400 if not fs.isdir(work_dir):
401 fs.mkdir(work_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700402 tools.write_json(result_json, result, dense=True)
403 # Only return 1 if there was an internal error.
404 return int(bool(result['internal_failure']))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000405
maruela9cfd6f2015-09-15 11:03:15 -0700406 # Marshall into old-style inline output.
407 if result['outputs_ref']:
408 data = {
409 'hash': result['outputs_ref']['isolated'],
410 'namespace': result['outputs_ref']['namespace'],
411 'storage': result['outputs_ref']['isolatedserver'],
412 }
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -0500413 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700414 print(
415 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
416 tools.format_json(data, dense=True))
maruelb76604c2015-11-11 11:53:44 -0800417 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700418 return result['exit_code'] or int(bool(result['internal_failure']))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000419
420
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500421def main(args):
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400422 parser = logging_utils.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000423 usage='%prog <options>',
424 version=__version__,
425 log_file=RUN_ISOLATED_LOG_FILE)
maruela9cfd6f2015-09-15 11:03:15 -0700426 parser.add_option(
427 '--json',
428 help='dump output metadata to json file. When used, run_isolated returns '
429 'non-zero only on internal failure')
maruel6be7f9e2015-10-01 12:25:30 -0700430 parser.add_option(
maruel5c9e47b2015-12-18 13:02:30 -0800431 '--hard-timeout', type='float', help='Enforce hard timeout in execution')
maruel6be7f9e2015-10-01 12:25:30 -0700432 parser.add_option(
maruel5c9e47b2015-12-18 13:02:30 -0800433 '--grace-period', type='float',
maruel6be7f9e2015-10-01 12:25:30 -0700434 help='Grace period between SIGTERM and SIGKILL')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500435 data_group = optparse.OptionGroup(parser, 'Data source')
436 data_group.add_option(
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500437 '-s', '--isolated',
438 help='Hash of the .isolated to grab from the isolate server')
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500439 isolateserver.add_isolate_server_options(data_group)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500440 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000441
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400442 isolateserver.add_cache_options(parser)
443 parser.set_defaults(cache='cache')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000444
Kenneth Russell61d42352014-09-15 11:41:16 -0700445 debug_group = optparse.OptionGroup(parser, 'Debugging')
446 debug_group.add_option(
447 '--leak-temp-dir',
448 action='store_true',
449 help='Deliberately leak isolate\'s temp dir for later examination '
450 '[default: %default]')
marueleb5fbee2015-09-17 13:01:36 -0700451 debug_group.add_option(
452 '--root-dir', help='Use a directory instead of a random one')
Kenneth Russell61d42352014-09-15 11:41:16 -0700453 parser.add_option_group(debug_group)
454
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800455 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500456 options, args = parser.parse_args(args)
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500457 if not options.isolated:
458 parser.error('--isolated is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800459 auth.process_auth_options(parser, options)
Marc-Antoine Ruele290ada2014-12-10 19:48:49 -0500460 isolateserver.process_isolate_server_options(parser, options, True)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000461
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400462 cache = isolateserver.process_cache_options(options)
maruel12e30012015-10-09 11:55:35 -0700463 if options.root_dir:
464 options.root_dir = unicode(os.path.abspath(options.root_dir))
465 if options.json:
466 options.json = unicode(os.path.abspath(options.json))
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500467 with isolateserver.get_storage(
468 options.isolate_server, options.namespace) as storage:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400469 # Hashing schemes used by |storage| and |cache| MUST match.
470 assert storage.hash_algo == cache.hash_algo
471 return run_tha_test(
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400472 options.isolated, storage, cache, options.leak_temp_dir, options.json,
maruel6be7f9e2015-10-01 12:25:30 -0700473 options.root_dir, options.hard_timeout, options.grace_period, args)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000474
475
476if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000477 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000478 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500479 sys.exit(main(sys.argv[1:]))