Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(813)

Unified Diff: client/run_isolated.py

Issue 1932143003: run_isolated: support non-isolated commands (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@run-isolated-download-stats
Patch Set: rebased Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: client/run_isolated.py
diff --git a/client/run_isolated.py b/client/run_isolated.py
index f74a8391dca5839bdb1fa94093c4aad20b49c584..8dafaa493c355d06aaad04242b8737870fb56a35 100755
--- a/client/run_isolated.py
+++ b/client/run_isolated.py
@@ -3,10 +3,15 @@
# Use of this source code is governed by the Apache v2.0 license that can be
# found in the LICENSE file.
-"""Reads a .isolated, creates a tree of hardlinks and runs the test.
+"""Runs a command with optional isolated input/output.
-To improve performance, it keeps a local cache. The local cache can safely be
-deleted.
+Despite name "run_isolated", can run a generic non-isolated command specified as
+args.
+
+If input isolated hash is provided, fetches it, creates a tree of hard links,
+appends args to the command in the fetched isolated and runs it.
+To improve performance, keeps a local cache.
+The local cache can safely be deleted.
Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a
temporary directory upon execution of the command specified in the .isolated
@@ -14,7 +19,7 @@ file. All content written to this directory will be uploaded upon termination
and the .isolated file describing this directory will be printed to stdout.
"""
-__version__ = '0.6.1'
+__version__ = '0.7.0'
import base64
import logging
@@ -36,10 +41,11 @@ from utils import tools
from utils import zip_package
import auth
-import isolated_format
import isolateserver
+ISOLATED_OUTDIR_PARAMETER = '${ISOLATED_OUTDIR}'
+
# Absolute path to this file (can be None if running from zip on Mac).
THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
@@ -136,12 +142,8 @@ def change_tree_read_only(rootdir, read_only):
def process_command(command, out_dir):
"""Replaces isolated specific variables in a command line."""
- def fix(arg):
- if '${ISOLATED_OUTDIR}' in arg:
- return arg.replace('${ISOLATED_OUTDIR}', out_dir).replace('/', os.sep)
- return arg
-
- return [fix(arg) for arg in command]
+ out_dir = out_dir.replace('/', os.sep)
+ return [arg.replace(ISOLATED_OUTDIR_PARAMETER, out_dir) for arg in command]
M-A Ruel 2016/05/02 14:18:14 The reason for the previous code was things like:
nodir 2016/05/02 17:43:22 got it, reverted, added comment
def run_command(command, cwd, tmp_dir, hard_timeout, grace_period):
@@ -302,9 +304,14 @@ def delete_and_upload(storage, out_dir, leak_temp_dir):
def map_and_run(
- isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
- grace_period, extra_args):
- """Maps and run the command. Returns metadata about the result."""
+ isolated_hash, args, storage, cache, leak_temp_dir, root_dir, hard_timeout,
+ grace_period):
+ """Runs a command with optional isolated input/output.
+
+ See run_tha_test for argument documentation.
+
+ Returns metadata about the result.
+ """
result = {
'duration': None,
'exit_code': None,
@@ -336,26 +343,30 @@ def map_and_run(
run_dir = make_temp_dir(prefix + u'run', root_dir)
out_dir = make_temp_dir(prefix + u'out', root_dir)
tmp_dir = make_temp_dir(prefix + u'tmp', root_dir)
+ cwd = run_dir
+
try:
- bundle, result['stats']['download'] = fetch_and_measure(
- isolated_hash=isolated_hash,
- storage=storage,
- cache=cache,
- outdir=run_dir)
- if not bundle.command:
- # Handle this as a task failure, not an internal failure.
- sys.stderr.write(
- '<The .isolated doesn\'t declare any command to run!>\n'
- '<Check your .isolate for missing \'command\' variable>\n')
- if os.environ.get('SWARMING_TASK_ID'):
- # Give an additional hint when running as a swarming task.
- sys.stderr.write('<This occurs at the \'isolate\' step>\n')
- result['exit_code'] = 1
- return result
+ command = args
+ if isolated_hash:
+ bundle, result['stats']['download'] = fetch_and_measure(
+ isolated_hash=isolated_hash,
+ storage=storage,
+ cache=cache,
+ outdir=run_dir)
+ if not bundle.command:
+ # Handle this as a task failure, not an internal failure.
+ sys.stderr.write(
+ '<The .isolated doesn\'t declare any command to run!>\n'
+ '<Check your .isolate for missing \'command\' variable>\n')
+ if os.environ.get('SWARMING_TASK_ID'):
+ # Give an additional hint when running as a swarming task.
+ sys.stderr.write('<This occurs at the \'isolate\' step>\n')
+ result['exit_code'] = 1
+ return result
- change_tree_read_only(run_dir, bundle.read_only)
- cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
- command = bundle.command + extra_args
+ change_tree_read_only(run_dir, bundle.read_only)
+ cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd))
+ command = bundle.command + args
file_path.ensure_command_has_abs_path(command, cwd)
sys.stdout.flush()
start = time.time()
@@ -424,18 +435,25 @@ def map_and_run(
def run_tha_test(
- isolated_hash, storage, cache, leak_temp_dir, result_json, root_dir,
- hard_timeout, grace_period, extra_args):
- """Downloads the dependencies in the cache, hardlinks them into a temporary
- directory and runs the executable from there.
+ isolated_hash, args, storage, cache, leak_temp_dir, result_json, root_dir,
+ hard_timeout, grace_period):
+ """Runs an executable and records execution metadata.
+
+ If isolated_hash is specified, downloads the dependencies in the cache,
+ hardlinks them into a temporary directory and runs the command specified in
+ the .isolated.
A temporary directory is created to hold the output files. The content inside
this directory will be uploaded back to |storage| packaged as a .isolated
file.
Arguments:
- isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
- recreate the tree of files to run the target executable.
+ isolated_hash: if not empty, the SHA-1 of the .isolated file that must be
+ retrieved to recreate the tree of files to run the target
+ executable. The command specified in the .isolated is
+ executed.
+ args: if isolated_hash is not empty, arguments to append to the command in
+ the .isolated file. Otherwise, the command line to execute.
M-A Ruel 2016/05/02 14:51:17 I'm not a fan of this ambivalence.
nodir 2016/05/02 17:43:22 fixed
storage: an isolateserver.Storage object to retrieve remote objects. This
object has a reference to an isolateserver.StorageApi, which does
the actual I/O.
@@ -451,12 +469,13 @@ def run_tha_test(
hard_timeout: kills the process if it lasts more than this amount of
seconds.
grace_period: number of seconds to wait between SIGTERM and SIGKILL.
- extra_args: optional arguments to add to the command stated in the .isolate
- file.
Returns:
Process exit code that should be used.
"""
+ if any(ISOLATED_OUTDIR_PARAMETER in a for a in args):
+ assert storage is not None, 'storage is None although outdir is specified'
+
if result_json:
# Write a json output file right away in case we get killed.
result = {
@@ -470,8 +489,8 @@ def run_tha_test(
# run_isolated exit code. Depends on if result_json is used or not.
result = map_and_run(
- isolated_hash, storage, cache, leak_temp_dir, root_dir, hard_timeout,
- grace_period, extra_args)
+ isolated_hash, args, storage, cache, leak_temp_dir, root_dir,
+ hard_timeout, grace_period)
logging.info('Result:\n%s', tools.format_json(result, dense=True))
if result_json:
# We've found tests to delete 'work' when quitting, causing an exception
@@ -498,7 +517,7 @@ def run_tha_test(
def main(args):
parser = logging_utils.OptionParserWithLogging(
- usage='%prog <options>',
+ usage='%prog <options> [args]',
M-A Ruel 2016/05/02 14:51:17 [command to run or extra args]
nodir 2016/05/02 17:43:22 Done.
version=__version__,
log_file=RUN_ISOLATED_LOG_FILE)
parser.add_option(
@@ -518,7 +537,7 @@ def main(args):
data_group = optparse.OptionGroup(parser, 'Data source')
data_group.add_option(
'-s', '--isolated',
- help='Hash of the .isolated to grab from the isolate server')
+ help='Hash of the .isolated to grab from the isolate server.')
isolateserver.add_isolate_server_options(data_group)
parser.add_option_group(data_group)
@@ -549,22 +568,37 @@ def main(args):
cache.cleanup()
return 0
+ if not options.isolated and not args:
+ parser.error('--isolated or args is required.')
M-A Ruel 2016/05/02 14:51:17 s/args/command to run/ ?
nodir 2016/05/02 17:43:22 Done.
+
auth.process_auth_options(parser, options)
- isolateserver.process_isolate_server_options(parser, options, True)
+
+ isolateserver.process_isolate_server_options(
+ parser, options, True, required=False)
+ if not options.isolate_server:
+ if options.isolated:
+ parser.error('--isolated requires --isolate-server')
+ if ISOLATED_OUTDIR_PARAMETER in args:
+ parser.error(
+ '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER)
if options.root_dir:
options.root_dir = unicode(os.path.abspath(options.root_dir))
if options.json:
options.json = unicode(os.path.abspath(options.json))
- if not options.isolated:
- parser.error('--isolated is required.')
- with isolateserver.get_storage(
- options.isolate_server, options.namespace) as storage:
- # Hashing schemes used by |storage| and |cache| MUST match.
+
+ storage = None
+ if options.isolate_server:
+ storage = isolateserver.get_storage(
+ options.isolate_server, options.namespace)
assert storage.hash_algo == cache.hash_algo
+
+ with storage or tools.noop_context():
M-A Ruel 2016/05/02 14:51:17 while I understand the goal to not copy paste the
nodir 2016/05/02 17:43:22 Done.
+ # Hashing schemes used by |storage| and |cache| MUST match.
return run_tha_test(
- options.isolated, storage, cache, options.leak_temp_dir, options.json,
- options.root_dir, options.hard_timeout, options.grace_period, args)
+ options.isolated, args, storage, cache, options.leak_temp_dir,
+ options.json, options.root_dir, options.hard_timeout,
+ options.grace_period)
if __name__ == '__main__':

Powered by Google App Engine
This is Rietveld 408576698