blob: 8bd1b6c36b1f122b31d9e6eb3fb1f167a2d10746 [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':
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:
maruel84537cb2015-10-16 14:21:28 -0700303 # On Windows rmtree(run_dir) call above has a synchronization effect: it
304 # finishes only when all task child processes terminate (since a running
305 # process locks *.exe file). Examine out_dir only after that call
306 # completes (since child processes may write to out_dir too and we need
307 # to wait for them to finish).
308 if fs.isdir(run_dir):
309 try:
310 success = file_path.rmtree(run_dir)
311 except OSError as e:
312 logging.error('Failure with %s', e)
313 success = False
314 if not success:
315 print >> sys.stderr, (
316 'Failed to delete the run directory, forcibly failing\n'
317 'the task because of it. No zombie process can outlive a\n'
318 'successful task run and still be marked as successful.\n'
319 'Fix your stuff.')
320 if result['exit_code'] == 0:
321 result['exit_code'] = 1
322 if fs.isdir(tmp_dir):
323 try:
324 success = file_path.rmtree(tmp_dir)
325 except OSError as e:
326 logging.error('Failure with %s', e)
327 success = False
328 if not success:
329 print >> sys.stderr, (
330 'Failed to delete the temporary directory, forcibly failing\n'
331 'the task because of it. No zombie process can outlive a\n'
332 'successful task run and still be marked as successful.\n'
333 'Fix your stuff.')
334 if result['exit_code'] == 0:
335 result['exit_code'] = 1
maruela9cfd6f2015-09-15 11:03:15 -0700336
marueleb5fbee2015-09-17 13:01:36 -0700337 # This deletes out_dir if leak_temp_dir is not set.
maruela9cfd6f2015-09-15 11:03:15 -0700338 result['outputs_ref'], success = delete_and_upload(
339 storage, out_dir, leak_temp_dir)
340 if not success and result['exit_code'] == 0:
341 result['exit_code'] = 1
342 except Exception as e:
343 # Swallow any exception in the main finally clause.
maruel12e30012015-10-09 11:55:35 -0700344 logging.exception('Leaking out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700345 result['internal_failure'] = str(e)
346 return result
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500347
348
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400349def run_tha_test(
marueleb5fbee2015-09-17 13:01:36 -0700350 isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir,
maruel6be7f9e2015-10-01 12:25:30 -0700351 hard_timeout, grace_period, extra_args):
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500352 """Downloads the dependencies in the cache, hardlinks them into a temporary
353 directory and runs the executable from there.
354
355 A temporary directory is created to hold the output files. The content inside
356 this directory will be uploaded back to |storage| packaged as a .isolated
357 file.
358
359 Arguments:
Marc-Antoine Ruel35b58432014-12-08 17:40:40 -0500360 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500361 recreate the tree of files to run the target executable.
362 storage: an isolateserver.Storage object to retrieve remote objects. This
363 object has a reference to an isolateserver.StorageApi, which does
364 the actual I/O.
365 cache: an isolateserver.LocalCache to keep from retrieving the same objects
366 constantly by caching the objects retrieved. Can be on-disk or
367 in-memory.
Kenneth Russell61d42352014-09-15 11:41:16 -0700368 leak_temp_dir: if true, the temporary directory will be deliberately leaked
369 for later examination.
maruela9cfd6f2015-09-15 11:03:15 -0700370 result_json: file path to dump result metadata into. If set, the process
371 exit code is always 0 unless an internal error occured.
marueleb5fbee2015-09-17 13:01:36 -0700372 root_dir: directory to the path to use to create the temporary directory. If
373 not specified, a random temporary directory is created.
maruel6be7f9e2015-10-01 12:25:30 -0700374 hard_timeout: kills the process if it lasts more than this amount of
375 seconds.
376 grace_period: number of seconds to wait between SIGTERM and SIGKILL.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500377 extra_args: optional arguments to add to the command stated in the .isolate
378 file.
maruela9cfd6f2015-09-15 11:03:15 -0700379
380 Returns:
381 Process exit code that should be used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000382 """
maruela9cfd6f2015-09-15 11:03:15 -0700383 # run_isolated exit code. Depends on if result_json is used or not.
384 result = map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700385 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
386 grace_period, extra_args)
maruela9cfd6f2015-09-15 11:03:15 -0700387 logging.info('Result:\n%s', tools.format_json(result, dense=True))
388 if result_json:
maruel05d5a882015-09-21 13:59:02 -0700389 # We've found tests to delete 'work' when quitting, causing an exception
390 # here. Try to recreate the directory if necessary.
391 work_dir = os.path.dirname(result_json)
maruel12e30012015-10-09 11:55:35 -0700392 if not fs.isdir(work_dir):
393 fs.mkdir(work_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700394 tools.write_json(result_json, result, dense=True)
395 # Only return 1 if there was an internal error.
396 return int(bool(result['internal_failure']))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000397
maruela9cfd6f2015-09-15 11:03:15 -0700398 # Marshall into old-style inline output.
399 if result['outputs_ref']:
400 data = {
401 'hash': result['outputs_ref']['isolated'],
402 'namespace': result['outputs_ref']['namespace'],
403 'storage': result['outputs_ref']['isolatedserver'],
404 }
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -0500405 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700406 print(
407 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
408 tools.format_json(data, dense=True))
maruelb76604c2015-11-11 11:53:44 -0800409 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700410 return result['exit_code'] or int(bool(result['internal_failure']))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000411
412
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500413def main(args):
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400414 parser = logging_utils.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000415 usage='%prog <options>',
416 version=__version__,
417 log_file=RUN_ISOLATED_LOG_FILE)
maruela9cfd6f2015-09-15 11:03:15 -0700418 parser.add_option(
419 '--json',
420 help='dump output metadata to json file. When used, run_isolated returns '
421 'non-zero only on internal failure')
maruel6be7f9e2015-10-01 12:25:30 -0700422 parser.add_option(
423 '--hard-timeout', type='int', help='Enforce hard timeout in execution')
424 parser.add_option(
425 '--grace-period', type='int',
426 help='Grace period between SIGTERM and SIGKILL')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500427 data_group = optparse.OptionGroup(parser, 'Data source')
428 data_group.add_option(
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500429 '-s', '--isolated',
430 help='Hash of the .isolated to grab from the isolate server')
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500431 isolateserver.add_isolate_server_options(data_group)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500432 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000433
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400434 isolateserver.add_cache_options(parser)
435 parser.set_defaults(cache='cache')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000436
Kenneth Russell61d42352014-09-15 11:41:16 -0700437 debug_group = optparse.OptionGroup(parser, 'Debugging')
438 debug_group.add_option(
439 '--leak-temp-dir',
440 action='store_true',
441 help='Deliberately leak isolate\'s temp dir for later examination '
442 '[default: %default]')
marueleb5fbee2015-09-17 13:01:36 -0700443 debug_group.add_option(
444 '--root-dir', help='Use a directory instead of a random one')
Kenneth Russell61d42352014-09-15 11:41:16 -0700445 parser.add_option_group(debug_group)
446
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800447 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500448 options, args = parser.parse_args(args)
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500449 if not options.isolated:
450 parser.error('--isolated is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800451 auth.process_auth_options(parser, options)
Marc-Antoine Ruele290ada2014-12-10 19:48:49 -0500452 isolateserver.process_isolate_server_options(parser, options, True)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000453
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400454 cache = isolateserver.process_cache_options(options)
maruel12e30012015-10-09 11:55:35 -0700455 if options.root_dir:
456 options.root_dir = unicode(os.path.abspath(options.root_dir))
457 if options.json:
458 options.json = unicode(os.path.abspath(options.json))
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500459 with isolateserver.get_storage(
460 options.isolate_server, options.namespace) as storage:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400461 # Hashing schemes used by |storage| and |cache| MUST match.
462 assert storage.hash_algo == cache.hash_algo
463 return run_tha_test(
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400464 options.isolated, storage, cache, options.leak_temp_dir, options.json,
maruel6be7f9e2015-10-01 12:25:30 -0700465 options.root_dir, options.hard_timeout, options.grace_period, args)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000466
467
468if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000469 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000470 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500471 sys.exit(main(sys.argv[1:]))