blob: fb4f12896dd398f2588eb36790231b7107bd151d [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.
maruela72f46e2016-02-24 11:05:45 -0800198 sys.stderr.write(
199 '<The executable does not exist or a dependent library is missing>\n'
200 '<Check for missing .so/.dll in the .isolate or GN file>\n'
201 '<Command: %s>\n' % command)
202 if os.environ.get('SWARMING_TASK_ID'):
203 # Give an additional hint when running as a swarming task.
204 sys.stderr.write(
205 '<See the task\'s page for commands to help diagnose this issue '
206 'by reproducing the task locally>\n')
maruela9cfd6f2015-09-15 11:03:15 -0700207 exit_code = 1
208 logging.info(
209 'Command finished with exit code %d (%s)',
210 exit_code, hex(0xffffffff & exit_code))
maruel6be7f9e2015-10-01 12:25:30 -0700211 return exit_code, had_hard_timeout
maruela9cfd6f2015-09-15 11:03:15 -0700212
213
214def delete_and_upload(storage, out_dir, leak_temp_dir):
215 """Deletes the temporary run directory and uploads results back.
216
217 Returns:
218 tuple(outputs_ref, success)
219 - outputs_ref is a dict referring to the results archived back to the
220 isolated server, if applicable.
221 - success is False if something occurred that means that the task must
222 forcibly be considered a failure, e.g. zombie processes were left behind.
223 """
224
225 # Upload out_dir and generate a .isolated file out of this directory. It is
226 # only done if files were written in the directory.
227 outputs_ref = None
maruel12e30012015-10-09 11:55:35 -0700228 if fs.isdir(out_dir) and fs.listdir(out_dir):
maruela9cfd6f2015-09-15 11:03:15 -0700229 with tools.Profiler('ArchiveOutput'):
230 try:
231 results = isolateserver.archive_files_to_storage(
232 storage, [out_dir], None)
233 outputs_ref = {
234 'isolated': results[0][0],
235 'isolatedserver': storage.location,
236 'namespace': storage.namespace,
237 }
238 except isolateserver.Aborted:
239 # This happens when a signal SIGTERM was received while uploading data.
240 # There is 2 causes:
241 # - The task was too slow and was about to be killed anyway due to
242 # exceeding the hard timeout.
243 # - The amount of data uploaded back is very large and took too much
244 # time to archive.
245 sys.stderr.write('Received SIGTERM while uploading')
246 # Re-raise, so it will be treated as an internal failure.
247 raise
248 try:
maruel12e30012015-10-09 11:55:35 -0700249 if (not leak_temp_dir and fs.isdir(out_dir) and
maruel6eeea7d2015-09-16 12:17:42 -0700250 not file_path.rmtree(out_dir)):
maruela9cfd6f2015-09-15 11:03:15 -0700251 logging.error('Had difficulties removing out_dir %s', out_dir)
252 return outputs_ref, False
253 except OSError as e:
254 # When this happens, it means there's a process error.
maruel12e30012015-10-09 11:55:35 -0700255 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700256 return outputs_ref, False
257 return outputs_ref, True
258
259
marueleb5fbee2015-09-17 13:01:36 -0700260def map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700261 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
262 grace_period, extra_args):
maruela9cfd6f2015-09-15 11:03:15 -0700263 """Maps and run the command. Returns metadata about the result."""
264 # TODO(maruel): Include performance statistics.
265 result = {
266 'exit_code': None,
maruel6be7f9e2015-10-01 12:25:30 -0700267 'had_hard_timeout': False,
maruela9cfd6f2015-09-15 11:03:15 -0700268 'internal_failure': None,
269 'outputs_ref': None,
maruel6be7f9e2015-10-01 12:25:30 -0700270 'version': 2,
maruela9cfd6f2015-09-15 11:03:15 -0700271 }
marueleb5fbee2015-09-17 13:01:36 -0700272 if root_dir:
maruel12e30012015-10-09 11:55:35 -0700273 if not fs.isdir(root_dir):
274 fs.makedirs(root_dir, 0700)
marueleb5fbee2015-09-17 13:01:36 -0700275 prefix = u''
276 else:
277 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
278 prefix = u'isolated_'
279 run_dir = make_temp_dir(prefix + u'run', root_dir)
280 out_dir = make_temp_dir(prefix + u'out', root_dir)
281 tmp_dir = make_temp_dir(prefix + u'tmp', root_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700282 try:
maruela72f46e2016-02-24 11:05:45 -0800283 try:
284 bundle = isolateserver.fetch_isolated(
285 isolated_hash=isolated_hash,
286 storage=storage,
287 cache=cache,
288 outdir=run_dir,
289 require_command=True)
290 except isolateserver.IsolatedErrorNoCommand:
291 # Handle this as a task failure, not an internal failure.
292 sys.stderr.write(
293 '<The .isolated doesn\'t declare any command to run!>\n'
294 '<Check your .isolate for missing \'command\' variable>\n')
295 if os.environ.get('SWARMING_TASK_ID'):
296 # Give an additional hint when running as a swarming task.
297 sys.stderr.write('<This occurs at the \'isolate\' step>\n')
298 result['exit_code'] = 1
299 return result
maruela9cfd6f2015-09-15 11:03:15 -0700300
301 change_tree_read_only(run_dir, bundle.read_only)
302 cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
303 command = bundle.command + extra_args
304 file_path.ensure_command_has_abs_path(command, cwd)
maruel6be7f9e2015-10-01 12:25:30 -0700305 result['exit_code'], result['had_hard_timeout'] = run_command(
306 process_command(command, out_dir), cwd, tmp_dir, hard_timeout,
307 grace_period)
maruela9cfd6f2015-09-15 11:03:15 -0700308 except Exception as e:
309 # An internal error occured. Report accordingly so the swarming task will be
310 # retried automatically.
maruel12e30012015-10-09 11:55:35 -0700311 logging.exception('internal failure: %s', e)
maruela9cfd6f2015-09-15 11:03:15 -0700312 result['internal_failure'] = str(e)
313 on_error.report(None)
314 finally:
315 try:
316 if leak_temp_dir:
317 logging.warning(
318 'Deliberately leaking %s for later examination', run_dir)
marueleb5fbee2015-09-17 13:01:36 -0700319 else:
maruel84537cb2015-10-16 14:21:28 -0700320 # On Windows rmtree(run_dir) call above has a synchronization effect: it
321 # finishes only when all task child processes terminate (since a running
322 # process locks *.exe file). Examine out_dir only after that call
323 # completes (since child processes may write to out_dir too and we need
324 # to wait for them to finish).
325 if fs.isdir(run_dir):
326 try:
327 success = file_path.rmtree(run_dir)
328 except OSError as e:
329 logging.error('Failure with %s', e)
330 success = False
331 if not success:
332 print >> sys.stderr, (
333 'Failed to delete the run directory, forcibly failing\n'
334 'the task because of it. No zombie process can outlive a\n'
335 'successful task run and still be marked as successful.\n'
336 'Fix your stuff.')
337 if result['exit_code'] == 0:
338 result['exit_code'] = 1
339 if fs.isdir(tmp_dir):
340 try:
341 success = file_path.rmtree(tmp_dir)
342 except OSError as e:
343 logging.error('Failure with %s', e)
344 success = False
345 if not success:
346 print >> sys.stderr, (
347 'Failed to delete the temporary directory, forcibly failing\n'
348 'the task because of it. No zombie process can outlive a\n'
349 'successful task run and still be marked as successful.\n'
350 'Fix your stuff.')
351 if result['exit_code'] == 0:
352 result['exit_code'] = 1
maruela9cfd6f2015-09-15 11:03:15 -0700353
marueleb5fbee2015-09-17 13:01:36 -0700354 # This deletes out_dir if leak_temp_dir is not set.
maruela9cfd6f2015-09-15 11:03:15 -0700355 result['outputs_ref'], success = delete_and_upload(
356 storage, out_dir, leak_temp_dir)
357 if not success and result['exit_code'] == 0:
358 result['exit_code'] = 1
359 except Exception as e:
360 # Swallow any exception in the main finally clause.
maruel12e30012015-10-09 11:55:35 -0700361 logging.exception('Leaking out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -0700362 result['internal_failure'] = str(e)
363 return result
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500364
365
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400366def run_tha_test(
marueleb5fbee2015-09-17 13:01:36 -0700367 isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir,
maruel6be7f9e2015-10-01 12:25:30 -0700368 hard_timeout, grace_period, extra_args):
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500369 """Downloads the dependencies in the cache, hardlinks them into a temporary
370 directory and runs the executable from there.
371
372 A temporary directory is created to hold the output files. The content inside
373 this directory will be uploaded back to |storage| packaged as a .isolated
374 file.
375
376 Arguments:
Marc-Antoine Ruel35b58432014-12-08 17:40:40 -0500377 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500378 recreate the tree of files to run the target executable.
379 storage: an isolateserver.Storage object to retrieve remote objects. This
380 object has a reference to an isolateserver.StorageApi, which does
381 the actual I/O.
382 cache: an isolateserver.LocalCache to keep from retrieving the same objects
383 constantly by caching the objects retrieved. Can be on-disk or
384 in-memory.
Kenneth Russell61d42352014-09-15 11:41:16 -0700385 leak_temp_dir: if true, the temporary directory will be deliberately leaked
386 for later examination.
maruela9cfd6f2015-09-15 11:03:15 -0700387 result_json: file path to dump result metadata into. If set, the process
388 exit code is always 0 unless an internal error occured.
marueleb5fbee2015-09-17 13:01:36 -0700389 root_dir: directory to the path to use to create the temporary directory. If
390 not specified, a random temporary directory is created.
maruel6be7f9e2015-10-01 12:25:30 -0700391 hard_timeout: kills the process if it lasts more than this amount of
392 seconds.
393 grace_period: number of seconds to wait between SIGTERM and SIGKILL.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500394 extra_args: optional arguments to add to the command stated in the .isolate
395 file.
maruela9cfd6f2015-09-15 11:03:15 -0700396
397 Returns:
398 Process exit code that should be used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000399 """
maruela76b9ee2015-12-15 06:18:08 -0800400 if result_json:
401 # Write a json output file right away in case we get killed.
402 result = {
403 'exit_code': None,
404 'had_hard_timeout': False,
405 'internal_failure': 'Was terminated before completion',
406 'outputs_ref': None,
407 'version': 2,
408 }
409 tools.write_json(result_json, result, dense=True)
410
maruela9cfd6f2015-09-15 11:03:15 -0700411 # run_isolated exit code. Depends on if result_json is used or not.
412 result = map_and_run(
maruel6be7f9e2015-10-01 12:25:30 -0700413 isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
414 grace_period, extra_args)
maruela9cfd6f2015-09-15 11:03:15 -0700415 logging.info('Result:\n%s', tools.format_json(result, dense=True))
416 if result_json:
maruel05d5a882015-09-21 13:59:02 -0700417 # We've found tests to delete 'work' when quitting, causing an exception
418 # here. Try to recreate the directory if necessary.
419 work_dir = os.path.dirname(result_json)
maruel12e30012015-10-09 11:55:35 -0700420 if not fs.isdir(work_dir):
421 fs.mkdir(work_dir)
maruela9cfd6f2015-09-15 11:03:15 -0700422 tools.write_json(result_json, result, dense=True)
423 # Only return 1 if there was an internal error.
424 return int(bool(result['internal_failure']))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000425
maruela9cfd6f2015-09-15 11:03:15 -0700426 # Marshall into old-style inline output.
427 if result['outputs_ref']:
428 data = {
429 'hash': result['outputs_ref']['isolated'],
430 'namespace': result['outputs_ref']['namespace'],
431 'storage': result['outputs_ref']['isolatedserver'],
432 }
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -0500433 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700434 print(
435 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
436 tools.format_json(data, dense=True))
maruelb76604c2015-11-11 11:53:44 -0800437 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -0700438 return result['exit_code'] or int(bool(result['internal_failure']))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000439
440
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500441def main(args):
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400442 parser = logging_utils.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000443 usage='%prog <options>',
444 version=__version__,
445 log_file=RUN_ISOLATED_LOG_FILE)
maruela9cfd6f2015-09-15 11:03:15 -0700446 parser.add_option(
447 '--json',
448 help='dump output metadata to json file. When used, run_isolated returns '
449 'non-zero only on internal failure')
maruel6be7f9e2015-10-01 12:25:30 -0700450 parser.add_option(
maruel5c9e47b2015-12-18 13:02:30 -0800451 '--hard-timeout', type='float', help='Enforce hard timeout in execution')
maruel6be7f9e2015-10-01 12:25:30 -0700452 parser.add_option(
maruel5c9e47b2015-12-18 13:02:30 -0800453 '--grace-period', type='float',
maruel6be7f9e2015-10-01 12:25:30 -0700454 help='Grace period between SIGTERM and SIGKILL')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500455 data_group = optparse.OptionGroup(parser, 'Data source')
456 data_group.add_option(
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500457 '-s', '--isolated',
458 help='Hash of the .isolated to grab from the isolate server')
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500459 isolateserver.add_isolate_server_options(data_group)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500460 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000461
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400462 isolateserver.add_cache_options(parser)
463 parser.set_defaults(cache='cache')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000464
Kenneth Russell61d42352014-09-15 11:41:16 -0700465 debug_group = optparse.OptionGroup(parser, 'Debugging')
466 debug_group.add_option(
467 '--leak-temp-dir',
468 action='store_true',
469 help='Deliberately leak isolate\'s temp dir for later examination '
470 '[default: %default]')
marueleb5fbee2015-09-17 13:01:36 -0700471 debug_group.add_option(
472 '--root-dir', help='Use a directory instead of a random one')
Kenneth Russell61d42352014-09-15 11:41:16 -0700473 parser.add_option_group(debug_group)
474
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800475 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500476 options, args = parser.parse_args(args)
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -0500477 if not options.isolated:
478 parser.error('--isolated is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800479 auth.process_auth_options(parser, options)
Marc-Antoine Ruele290ada2014-12-10 19:48:49 -0500480 isolateserver.process_isolate_server_options(parser, options, True)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000481
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -0400482 cache = isolateserver.process_cache_options(options)
maruel12e30012015-10-09 11:55:35 -0700483 if options.root_dir:
484 options.root_dir = unicode(os.path.abspath(options.root_dir))
485 if options.json:
486 options.json = unicode(os.path.abspath(options.json))
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -0500487 with isolateserver.get_storage(
488 options.isolate_server, options.namespace) as storage:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400489 # Hashing schemes used by |storage| and |cache| MUST match.
490 assert storage.hash_algo == cache.hash_algo
491 return run_tha_test(
Marc-Antoine Ruel0ec868b2015-08-12 14:12:46 -0400492 options.isolated, storage, cache, options.leak_temp_dir, options.json,
maruel6be7f9e2015-10-01 12:25:30 -0700493 options.root_dir, options.hard_timeout, options.grace_period, args)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000494
495
496if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000497 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000498 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500499 sys.exit(main(sys.argv[1:]))