blob: e1315ecc34917ef6ac04bfcb47721b0bd2c78579 [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
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040017__version__ = '0.3.2'
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000018
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000019import ctypes
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000020import logging
21import optparse
22import os
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000023import re
24import shutil
25import stat
26import subprocess
27import sys
28import tempfile
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000029import time
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000030
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000031from third_party.depot_tools import fix_encoding
32
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +000033from utils import lru
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040034from utils import on_error
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +000035from utils import threading_utils
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000036from utils import tools
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +000037from utils import zip_package
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000038
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080039import auth
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000040import isolateserver
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000041
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000042
vadimsh@chromium.org85071062013-08-21 23:37:45 +000043# Absolute path to this file (can be None if running from zip on Mac).
44THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000045
46# Directory that contains this file (might be inside zip package).
vadimsh@chromium.org85071062013-08-21 23:37:45 +000047BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000048
49# Directory that contains currently running script file.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000050if zip_package.get_main_script_path():
51 MAIN_DIR = os.path.dirname(
52 os.path.abspath(zip_package.get_main_script_path()))
53else:
54 # This happens when 'import run_isolated' is executed at the python
55 # interactive prompt, in that case __file__ is undefined.
56 MAIN_DIR = None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000057
maruel@chromium.org6b365dc2012-10-18 19:17:56 +000058# Types of action accepted by link_file().
maruel@chromium.orgba6489b2013-07-11 20:23:33 +000059HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY = range(1, 5)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000060
csharp@chromium.orgff2a4662012-11-21 20:49:32 +000061# The name of the log file to use.
62RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
63
csharp@chromium.orge217f302012-11-22 16:51:53 +000064# The name of the log to use for the run_test_cases.py command
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000065RUN_TEST_CASES_LOG = 'run_test_cases.log'
csharp@chromium.orge217f302012-11-22 16:51:53 +000066
vadimsh@chromium.org87d63262013-04-04 19:34:21 +000067
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000068def get_as_zip_package(executable=True):
69 """Returns ZipPackage with this module and all its dependencies.
70
71 If |executable| is True will store run_isolated.py as __main__.py so that
72 zip package is directly executable be python.
73 """
74 # Building a zip package when running from another zip package is
75 # unsupported and probably unneeded.
76 assert not zip_package.is_zipped_module(sys.modules[__name__])
vadimsh@chromium.org85071062013-08-21 23:37:45 +000077 assert THIS_FILE_PATH
78 assert BASE_DIR
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000079 package = zip_package.ZipPackage(root=BASE_DIR)
80 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None)
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000081 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py'))
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080082 package.add_python_file(os.path.join(BASE_DIR, 'auth.py'))
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000083 package.add_directory(os.path.join(BASE_DIR, 'third_party'))
84 package.add_directory(os.path.join(BASE_DIR, 'utils'))
85 return package
86
87
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -050088def hardlink(source, link_name):
89 """Hardlinks a file.
90
91 Add support for os.link() on Windows.
92 """
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000093 if sys.platform == 'win32':
94 if not ctypes.windll.kernel32.CreateHardLinkW(
95 unicode(link_name), unicode(source), 0):
96 raise OSError()
97 else:
98 os.link(source, link_name)
99
100
101def readable_copy(outfile, infile):
102 """Makes a copy of the file that is readable by everyone."""
csharp@chromium.org59d116d2013-07-05 18:04:08 +0000103 shutil.copy2(infile, outfile)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000104 read_enabled_mode = (os.stat(outfile).st_mode | stat.S_IRUSR |
105 stat.S_IRGRP | stat.S_IROTH)
106 os.chmod(outfile, read_enabled_mode)
107
108
109def link_file(outfile, infile, action):
110 """Links a file. The type of link depends on |action|."""
111 logging.debug('Mapping %s to %s' % (infile, outfile))
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000112 if action not in (HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000113 raise ValueError('Unknown mapping action %s' % action)
114 if not os.path.isfile(infile):
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000115 raise isolateserver.MappingError('%s is missing' % infile)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000116 if os.path.isfile(outfile):
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000117 raise isolateserver.MappingError(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000118 '%s already exist; insize:%d; outsize:%d' %
119 (outfile, os.stat(infile).st_size, os.stat(outfile).st_size))
120
121 if action == COPY:
122 readable_copy(outfile, infile)
123 elif action == SYMLINK and sys.platform != 'win32':
124 # On windows, symlink are converted to hardlink and fails over to copy.
maruel@chromium.orgf43e68b2012-10-15 20:23:10 +0000125 os.symlink(infile, outfile) # pylint: disable=E1101
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000126 else:
127 try:
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500128 hardlink(infile, outfile)
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000129 except OSError as e:
130 if action == HARDLINK:
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000131 raise isolateserver.MappingError(
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000132 'Failed to hardlink %s to %s: %s' % (infile, outfile, e))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000133 # Probably a different file system.
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000134 logging.warning(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000135 'Failed to hardlink, failing back to copy %s to %s' % (
136 infile, outfile))
137 readable_copy(outfile, infile)
138
139
Marc-Antoine Rueld2d4d4f2013-11-10 14:32:38 -0500140def set_read_only(path, read_only):
141 """Sets or resets the write bit on a file or directory.
142
143 Zaps out access to 'group' and 'others'.
144 """
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500145 assert isinstance(read_only, bool), read_only
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000146 mode = os.lstat(path).st_mode
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500147 # TODO(maruel): Stop removing GO bits.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000148 if read_only:
149 mode = mode & 0500
150 else:
151 mode = mode | 0200
152 if hasattr(os, 'lchmod'):
153 os.lchmod(path, mode) # pylint: disable=E1101
154 else:
155 if stat.S_ISLNK(mode):
156 # Skip symlink without lchmod() support.
Marc-Antoine Ruel45dc2902013-12-05 14:54:20 -0500157 logging.debug(
158 'Can\'t change %sw bit on symlink %s',
159 '-' if read_only else '+', path)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000160 return
161
162 # TODO(maruel): Implement proper DACL modification on Windows.
163 os.chmod(path, mode)
164
165
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500166def make_tree_read_only(root):
167 """Makes all the files in the directories read only.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000168
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500169 Also makes the directories read only, only if it makes sense on the platform.
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500170
171 This means no file can be created or deleted.
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500172 """
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500173 logging.debug('make_tree_read_only(%s)', root)
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500174 assert os.path.isabs(root), root
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500175 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
176 for filename in filenames:
177 set_read_only(os.path.join(dirpath, filename), True)
178 if sys.platform != 'win32':
179 # It must not be done on Windows.
180 for dirname in dirnames:
181 set_read_only(os.path.join(dirpath, dirname), True)
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500182 if sys.platform != 'win32':
183 set_read_only(root, True)
184
185
186def make_tree_files_read_only(root):
187 """Makes all the files in the directories read only but not the directories
188 themselves.
189
190 This means files can be created or deleted.
191 """
192 logging.debug('make_tree_files_read_only(%s)', root)
193 assert os.path.isabs(root), root
194 if sys.platform != 'win32':
195 set_read_only(root, False)
196 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
197 for filename in filenames:
198 set_read_only(os.path.join(dirpath, filename), True)
199 if sys.platform != 'win32':
200 # It must not be done on Windows.
201 for dirname in dirnames:
202 set_read_only(os.path.join(dirpath, dirname), False)
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500203
204
205def make_tree_writeable(root):
206 """Makes all the files in the directories writeable.
207
208 Also makes the directories writeable, only if it makes sense on the platform.
209
210 It is different from make_tree_deleteable() because it unconditionally affects
211 the files.
212 """
213 logging.debug('make_tree_writeable(%s)', root)
214 assert os.path.isabs(root), root
215 if sys.platform != 'win32':
216 set_read_only(root, False)
217 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
218 for filename in filenames:
219 set_read_only(os.path.join(dirpath, filename), False)
220 if sys.platform != 'win32':
221 # It must not be done on Windows.
222 for dirname in dirnames:
223 set_read_only(os.path.join(dirpath, dirname), False)
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500224
225
226def make_tree_deleteable(root):
227 """Changes the appropriate permissions so the files in the directories can be
228 deleted.
229
230 On Windows, the files are modified. On other platforms, modify the directory.
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500231 It only does the minimum so the files can be deleted safely.
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500232
233 Warning on Windows: since file permission is modified, the file node is
234 modified. This means that for hard-linked files, every directory entry for the
235 file node has its file permission modified.
236 """
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500237 logging.debug('make_tree_deleteable(%s)', root)
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500238 assert os.path.isabs(root), root
239 if sys.platform != 'win32':
240 set_read_only(root, False)
241 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
242 if sys.platform == 'win32':
243 for filename in filenames:
244 set_read_only(os.path.join(dirpath, filename), False)
245 else:
246 for dirname in dirnames:
247 set_read_only(os.path.join(dirpath, dirname), False)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000248
249
250def rmtree(root):
251 """Wrapper around shutil.rmtree() to retry automatically on Windows."""
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500252 make_tree_deleteable(root)
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500253 logging.info('rmtree(%s)', root)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000254 if sys.platform == 'win32':
Kenneth Russelldd806d62014-03-03 17:41:57 -0800255 for i in range(4):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000256 try:
257 shutil.rmtree(root)
258 break
259 except WindowsError: # pylint: disable=E0602
Kenneth Russelldd806d62014-03-03 17:41:57 -0800260 if i == 3:
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500261 raise
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000262 delay = (i+1)*2
263 print >> sys.stderr, (
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500264 'Failed to delete %s. Maybe the test has subprocess outliving it.'
265 ' Sleep %d seconds.' % (root, delay))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000266 time.sleep(delay)
267 else:
268 shutil.rmtree(root)
269
270
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000271def try_remove(filepath):
272 """Removes a file without crashing even if it doesn't exist."""
273 try:
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500274 # TODO(maruel): Not do it unless necessary since it slows this function
275 # down.
276 if sys.platform == 'win32':
277 # Deleting a read-only file will fail if it is read-only.
278 set_read_only(filepath, False)
279 else:
280 # Deleting a read-only file will fail if the directory is read-only.
281 set_read_only(os.path.dirname(filepath), False)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000282 os.remove(filepath)
283 except OSError:
284 pass
285
286
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000287def is_same_filesystem(path1, path2):
288 """Returns True if both paths are on the same filesystem.
289
290 This is required to enable the use of hardlinks.
291 """
292 assert os.path.isabs(path1), path1
293 assert os.path.isabs(path2), path2
294 if sys.platform == 'win32':
295 # If the drive letter mismatches, assume it's a separate partition.
296 # TODO(maruel): It should look at the underlying drive, a drive letter could
297 # be a mount point to a directory on another drive.
298 assert re.match(r'^[a-zA-Z]\:\\.*', path1), path1
299 assert re.match(r'^[a-zA-Z]\:\\.*', path2), path2
300 if path1[0].lower() != path2[0].lower():
301 return False
302 return os.stat(path1).st_dev == os.stat(path2).st_dev
303
304
305def get_free_space(path):
306 """Returns the number of free bytes."""
307 if sys.platform == 'win32':
308 free_bytes = ctypes.c_ulonglong(0)
309 ctypes.windll.kernel32.GetDiskFreeSpaceExW(
310 ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
311 return free_bytes.value
maruel@chromium.orgf43e68b2012-10-15 20:23:10 +0000312 # For OSes other than Windows.
313 f = os.statvfs(path) # pylint: disable=E1101
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000314 return f.f_bfree * f.f_frsize
315
316
317def make_temp_dir(prefix, root_dir):
318 """Returns a temporary directory on the same file system as root_dir."""
319 base_temp_dir = None
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500320 if root_dir and not is_same_filesystem(root_dir, tempfile.gettempdir()):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000321 base_temp_dir = os.path.dirname(root_dir)
322 return tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir)
323
324
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000325class CachePolicies(object):
326 def __init__(self, max_cache_size, min_free_space, max_items):
327 """
328 Arguments:
329 - max_cache_size: Trim if the cache gets larger than this value. If 0, the
330 cache is effectively a leak.
331 - min_free_space: Trim if disk free space becomes lower than this value. If
332 0, it unconditionally fill the disk.
333 - max_items: Maximum number of items to keep in the cache. If 0, do not
334 enforce a limit.
335 """
336 self.max_cache_size = max_cache_size
337 self.min_free_space = min_free_space
338 self.max_items = max_items
339
340
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000341class DiskCache(isolateserver.LocalCache):
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000342 """Stateful LRU cache in a flat hash table in a directory.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000343
344 Saves its state as json file.
345 """
346 STATE_FILE = 'state.json'
347
Vadim Shtayurae0ab1902014-04-29 10:55:27 -0700348 def __init__(self, cache_dir, policies, hash_algo):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000349 """
350 Arguments:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000351 cache_dir: directory where to place the cache.
352 policies: cache retention policies.
353 algo: hashing algorithm used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000354 """
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000355 super(DiskCache, self).__init__()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000356 self.cache_dir = cache_dir
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000357 self.policies = policies
Vadim Shtayurae0ab1902014-04-29 10:55:27 -0700358 self.hash_algo = hash_algo
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000359 self.state_file = os.path.join(cache_dir, self.STATE_FILE)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000360
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000361 # All protected methods (starting with '_') except _path should be called
362 # with this lock locked.
363 self._lock = threading_utils.LockWithAssert()
364 self._lru = lru.LRUDict()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000365
366 # Profiling values.
367 self._added = []
368 self._removed = []
369 self._free_disk = 0
370
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000371 with tools.Profiler('Setup'):
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000372 with self._lock:
373 self._load()
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000374
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000375 def __enter__(self):
376 return self
377
378 def __exit__(self, _exc_type, _exec_value, _traceback):
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000379 with tools.Profiler('CleanupTrimming'):
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000380 with self._lock:
381 self._trim()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000382
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000383 logging.info(
384 '%5d (%8dkb) added',
385 len(self._added), sum(self._added) / 1024)
386 logging.info(
387 '%5d (%8dkb) current',
388 len(self._lru),
389 sum(self._lru.itervalues()) / 1024)
390 logging.info(
391 '%5d (%8dkb) removed',
392 len(self._removed), sum(self._removed) / 1024)
393 logging.info(
394 ' %8dkb free',
395 self._free_disk / 1024)
maruel@chromium.org41601642013-09-18 19:40:46 +0000396 return False
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000397
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000398 def cached_set(self):
399 with self._lock:
400 return self._lru.keys_set()
401
402 def touch(self, digest, size):
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500403 """Verifies an actual file is valid.
404
405 Note that is doesn't compute the hash so it could still be corrupted if the
406 file size didn't change.
407
408 TODO(maruel): More stringent verification while keeping the check fast.
409 """
410 # Do the check outside the lock.
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000411 if not isolateserver.is_valid_file(self._path(digest), size):
412 return False
413
414 # Update it's LRU position.
415 with self._lock:
416 if digest not in self._lru:
417 return False
418 self._lru.touch(digest)
419 return True
420
421 def evict(self, digest):
422 with self._lock:
423 self._lru.pop(digest)
424 self._delete_file(digest, isolateserver.UNKNOWN_FILE_SIZE)
425
426 def read(self, digest):
427 with open(self._path(digest), 'rb') as f:
428 return f.read()
429
430 def write(self, digest, content):
431 path = self._path(digest)
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500432 # A stale broken file may remain. It is possible for the file to have write
433 # access bit removed which would cause the file_write() call to fail to open
434 # in write mode. Take no chance here.
435 try_remove(path)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000436 try:
437 size = isolateserver.file_write(path, content)
438 except:
439 # There are two possible places were an exception can occur:
440 # 1) Inside |content| generator in case of network or unzipping errors.
441 # 2) Inside file_write itself in case of disk IO errors.
442 # In any case delete an incomplete file and propagate the exception to
443 # caller, it will be logged there.
444 try_remove(path)
445 raise
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500446 # Make the file read-only in the cache. This has a few side-effects since
447 # the file node is modified, so every directory entries to this file becomes
448 # read-only. It's fine here because it is a new file.
449 set_read_only(path, True)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000450 with self._lock:
451 self._add(digest, size)
452
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500453 def hardlink(self, digest, dest, file_mode):
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500454 """Hardlinks the file to |dest|.
455
456 Note that the file permission bits are on the file node, not the directory
457 entry, so changing the access bit on any of the directory entries for the
458 file node will affect them all.
459 """
460 path = self._path(digest)
461 link_file(dest, path, HARDLINK)
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500462 if file_mode is not None:
463 # Ignores all other bits.
464 os.chmod(dest, file_mode & 0500)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000465
466 def _load(self):
467 """Loads state of the cache from json file."""
468 self._lock.assert_locked()
469
470 if not os.path.isdir(self.cache_dir):
471 os.makedirs(self.cache_dir)
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500472 else:
473 # Make sure the cache is read-only.
474 # TODO(maruel): Calculate the cost and optimize the performance
475 # accordingly.
476 make_tree_read_only(self.cache_dir)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000477
478 # Load state of the cache.
479 if os.path.isfile(self.state_file):
480 try:
481 self._lru = lru.LRUDict.load(self.state_file)
482 except ValueError as err:
483 logging.error('Failed to load cache state: %s' % (err,))
484 # Don't want to keep broken state file.
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500485 try_remove(self.state_file)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000486
487 # Ensure that all files listed in the state still exist and add new ones.
488 previous = self._lru.keys_set()
489 unknown = []
490 for filename in os.listdir(self.cache_dir):
491 if filename == self.STATE_FILE:
492 continue
493 if filename in previous:
494 previous.remove(filename)
495 continue
496 # An untracked file.
Vadim Shtayurae0ab1902014-04-29 10:55:27 -0700497 if not isolateserver.is_valid_hash(filename, self.hash_algo):
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000498 logging.warning('Removing unknown file %s from cache', filename)
499 try_remove(self._path(filename))
500 continue
501 # File that's not referenced in 'state.json'.
502 # TODO(vadimsh): Verify its SHA1 matches file name.
503 logging.warning('Adding unknown file %s to cache', filename)
504 unknown.append(filename)
505
506 if unknown:
507 # Add as oldest files. They will be deleted eventually if not accessed.
508 self._add_oldest_list(unknown)
509 logging.warning('Added back %d unknown files', len(unknown))
510
511 if previous:
512 # Filter out entries that were not found.
513 logging.warning('Removed %d lost files', len(previous))
514 for filename in previous:
515 self._lru.pop(filename)
516 self._trim()
517
518 def _save(self):
519 """Saves the LRU ordering."""
520 self._lock.assert_locked()
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500521 if sys.platform != 'win32':
522 d = os.path.dirname(self.state_file)
523 if os.path.isdir(d):
524 # Necessary otherwise the file can't be created.
525 set_read_only(d, False)
526 if os.path.isfile(self.state_file):
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500527 set_read_only(self.state_file, False)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000528 self._lru.save(self.state_file)
529
530 def _trim(self):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000531 """Trims anything we don't know, make sure enough free space exists."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000532 self._lock.assert_locked()
533
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000534 # Ensure maximum cache size.
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000535 if self.policies.max_cache_size:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000536 total_size = sum(self._lru.itervalues())
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000537 while total_size > self.policies.max_cache_size:
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000538 total_size -= self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000539
540 # Ensure maximum number of items in the cache.
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000541 if self.policies.max_items and len(self._lru) > self.policies.max_items:
542 for _ in xrange(len(self._lru) - self.policies.max_items):
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000543 self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000544
545 # Ensure enough free space.
546 self._free_disk = get_free_space(self.cache_dir)
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000547 trimmed_due_to_space = False
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000548 while (
549 self.policies.min_free_space and
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000550 self._lru and
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000551 self._free_disk < self.policies.min_free_space):
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000552 trimmed_due_to_space = True
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000553 self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000554 self._free_disk = get_free_space(self.cache_dir)
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000555 if trimmed_due_to_space:
Marc-Antoine Ruel2cbc1242014-07-16 11:05:04 -0400556 total_usage = sum(self._lru.itervalues())
557 usage_percent = 0.
558 if total_usage:
559 usage_percent = 100. * self.policies.max_cache_size / float(total_usage)
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000560 logging.warning(
561 'Trimmed due to not enough free disk space: %.1fkb free, %.1fkb '
562 'cache (%.1f%% of its maximum capacity)',
563 self._free_disk / 1024.,
Marc-Antoine Ruel2cbc1242014-07-16 11:05:04 -0400564 total_usage / 1024.,
565 usage_percent)
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000566 self._save()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000567
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000568 def _path(self, digest):
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000569 """Returns the path to one item."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000570 return os.path.join(self.cache_dir, digest)
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000571
572 def _remove_lru_file(self):
573 """Removes the last recently used file and returns its size."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000574 self._lock.assert_locked()
575 digest, size = self._lru.pop_oldest()
576 self._delete_file(digest, size)
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000577 return size
578
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000579 def _add(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000580 """Adds an item into LRU cache marking it as a newest one."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000581 self._lock.assert_locked()
582 if size == isolateserver.UNKNOWN_FILE_SIZE:
583 size = os.stat(self._path(digest)).st_size
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000584 self._added.append(size)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000585 self._lru.add(digest, size)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000586
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000587 def _add_oldest_list(self, digests):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000588 """Adds a bunch of items into LRU cache marking them as oldest ones."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000589 self._lock.assert_locked()
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000590 pairs = []
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000591 for digest in digests:
592 size = os.stat(self._path(digest)).st_size
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000593 self._added.append(size)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000594 pairs.append((digest, size))
595 self._lru.batch_insert_oldest(pairs)
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000596
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000597 def _delete_file(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000598 """Deletes cache file from the file system."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000599 self._lock.assert_locked()
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000600 try:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000601 if size == isolateserver.UNKNOWN_FILE_SIZE:
602 size = os.stat(self._path(digest)).st_size
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500603 try_remove(self._path(digest))
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000604 self._removed.append(size)
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000605 except OSError as e:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000606 logging.error('Error attempting to delete a file %s:\n%s' % (digest, e))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000607
608
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500609def change_tree_read_only(rootdir, read_only):
610 """Changes the tree read-only bits according to the read_only specification.
611
612 The flag can be 0, 1 or 2, which will affect the possibility to modify files
613 and create or delete files.
614 """
615 if read_only == 2:
616 # Files and directories (except on Windows) are marked read only. This
617 # inhibits modifying, creating or deleting files in the test directory,
618 # except on Windows where creating and deleting files is still possible.
619 make_tree_read_only(rootdir)
620 elif read_only == 1:
621 # Files are marked read only but not the directories. This inhibits
622 # modifying files but creating or deleting files is still possible.
623 make_tree_files_read_only(rootdir)
624 elif read_only in (0, None):
625 # Anything can be modified. This is the default in the .isolated file
626 # format.
627 #
628 # TODO(maruel): This is currently dangerous as long as DiskCache.touch()
629 # is not yet changed to verify the hash of the content of the files it is
630 # looking at, so that if a test modifies an input file, the file must be
631 # deleted.
632 make_tree_writeable(rootdir)
633 else:
634 raise ValueError(
635 'change_tree_read_only(%s, %s): Unknown flag %s' %
636 (rootdir, read_only, read_only))
637
638
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500639def process_command(command, out_dir):
640 """Replaces isolated specific variables in a command line."""
Vadim Shtayura51aba362014-05-14 15:39:23 -0700641 filtered = []
642 for arg in command:
643 if '${ISOLATED_OUTDIR}' in arg:
644 arg = arg.replace('${ISOLATED_OUTDIR}', out_dir).replace('/', os.sep)
645 filtered.append(arg)
646 return filtered
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500647
648
Vadim Shtayurae0ab1902014-04-29 10:55:27 -0700649def run_tha_test(isolated_hash, storage, cache, extra_args):
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500650 """Downloads the dependencies in the cache, hardlinks them into a temporary
651 directory and runs the executable from there.
652
653 A temporary directory is created to hold the output files. The content inside
654 this directory will be uploaded back to |storage| packaged as a .isolated
655 file.
656
657 Arguments:
658 isolated_hash: the sha-1 of the .isolated file that must be retrieved to
659 recreate the tree of files to run the target executable.
660 storage: an isolateserver.Storage object to retrieve remote objects. This
661 object has a reference to an isolateserver.StorageApi, which does
662 the actual I/O.
663 cache: an isolateserver.LocalCache to keep from retrieving the same objects
664 constantly by caching the objects retrieved. Can be on-disk or
665 in-memory.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500666 extra_args: optional arguments to add to the command stated in the .isolate
667 file.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000668 """
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500669 run_dir = make_temp_dir('run_tha_test', cache.cache_dir)
Vadim Shtayura51aba362014-05-14 15:39:23 -0700670 out_dir = unicode(make_temp_dir('isolated_out', cache.cache_dir))
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500671 result = 0
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000672 try:
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000673 try:
674 settings = isolateserver.fetch_isolated(
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000675 isolated_hash=isolated_hash,
676 storage=storage,
677 cache=cache,
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500678 outdir=run_dir,
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000679 require_command=True)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400680 except isolateserver.ConfigError:
681 on_error.report(None)
Vadim Shtayura51aba362014-05-14 15:39:23 -0700682 return 1
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000683
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500684 change_tree_read_only(run_dir, settings.read_only)
685 cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500686 command = settings.command + extra_args
Vadim Shtayurae4a780b2014-01-17 13:18:53 -0800687
688 # subprocess.call doesn't consider 'cwd' when searching for executable.
689 # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
690 # path if necessary.
691 if not os.path.isabs(command[0]):
692 command[0] = os.path.abspath(os.path.join(cwd, command[0]))
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500693 command = process_command(command, out_dir)
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500694 logging.info('Running %s, cwd=%s' % (command, cwd))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000695
696 # TODO(csharp): This should be specified somewhere else.
697 # TODO(vadimsh): Pass it via 'env_vars' in manifest.
698 # Add a rotating log file if one doesn't already exist.
699 env = os.environ.copy()
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000700 if MAIN_DIR:
701 env.setdefault('RUN_TEST_CASES_LOG_FILE',
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000702 os.path.join(MAIN_DIR, RUN_TEST_CASES_LOG))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000703 try:
Vadim Shtayura51aba362014-05-14 15:39:23 -0700704 sys.stdout.flush()
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000705 with tools.Profiler('RunTest'):
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500706 result = subprocess.call(command, cwd=cwd, env=env)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700707 logging.info(
708 'Command finished with exit code %d (%s)',
709 result, hex(0xffffffff & result))
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400710 except OSError:
711 on_error.report('Failed to run %s; cwd=%s' % (command, cwd))
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500712 result = 1
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500713
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000714 finally:
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500715 try:
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500716 try:
717 rmtree(run_dir)
718 except OSError:
719 logging.warning('Leaking %s', run_dir)
720 # Swallow the exception so it doesn't generate an infrastructure error.
721 #
722 # It usually happens on Windows when a child process is not properly
723 # terminated, usually because of a test case starting child processes
724 # that time out. This causes files to be locked and it becomes
725 # impossible to delete them.
726 #
727 # Only report an infrastructure error if the test didn't fail. This is
728 # because a swarming bot will likely not reboot. This situation will
729 # cause accumulation of temporary hardlink trees.
730 if not result:
731 raise
Vadim Shtayura51aba362014-05-14 15:39:23 -0700732
733 # HACK(vadimsh): On Windows rmtree(run_dir) call above has
734 # a synchronization effect: it finishes only when all task child processes
735 # terminate (since a running process locks *.exe file). Examine out_dir
736 # only after that call completes (since child processes may
737 # write to out_dir too and we need to wait for them to finish).
738
739 # Upload out_dir and generate a .isolated file out of this directory.
740 # It is only done if files were written in the directory.
741 if os.listdir(out_dir):
742 with tools.Profiler('ArchiveOutput'):
743 results = isolateserver.archive_files_to_storage(
744 storage, [out_dir], None)
745 # TODO(maruel): Implement side-channel to publish this information.
746 output_data = {
747 'hash': results[0][0],
748 'namespace': storage.namespace,
749 'storage': storage.location,
750 }
751 sys.stdout.flush()
752 print(
753 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
754 tools.format_json(output_data, dense=True))
755
756 finally:
757 rmtree(out_dir)
758
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500759 return result
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000760
761
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500762def main(args):
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000763 tools.disable_buffering()
764 parser = tools.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000765 usage='%prog <options>',
766 version=__version__,
767 log_file=RUN_ISOLATED_LOG_FILE)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000768
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500769 data_group = optparse.OptionGroup(parser, 'Data source')
770 data_group.add_option(
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000771 '-s', '--isolated',
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000772 metavar='FILE',
773 help='File/url describing what to map or run')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500774 data_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000775 '-H', '--hash',
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000776 help='Hash of the .isolated to grab from the hash table')
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500777 isolateserver.add_isolate_server_options(data_group, True)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500778 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000779
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500780 cache_group = optparse.OptionGroup(parser, 'Cache management')
781 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000782 '--cache',
783 default='cache',
784 metavar='DIR',
785 help='Cache directory, default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500786 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000787 '--max-cache-size',
788 type='int',
789 metavar='NNN',
790 default=20*1024*1024*1024,
791 help='Trim if the cache gets larger than this value, default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500792 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000793 '--min-free-space',
794 type='int',
795 metavar='NNN',
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000796 default=2*1024*1024*1024,
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000797 help='Trim if disk free space becomes lower than this value, '
798 'default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500799 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000800 '--max-items',
801 type='int',
802 metavar='NNN',
803 default=100000,
804 help='Trim if more than this number of items are in the cache '
805 'default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500806 parser.add_option_group(cache_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000807
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800808 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500809 options, args = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800810 auth.process_auth_options(parser, options)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500811 isolateserver.process_isolate_server_options(data_group, options)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000812
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000813 if bool(options.isolated) == bool(options.hash):
maruel@chromium.org5dd75dd2012-12-03 15:11:32 +0000814 logging.debug('One and only one of --isolated or --hash is required.')
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000815 parser.error('One and only one of --isolated or --hash is required.')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000816
csharp@chromium.orgffd8cf02013-01-09 21:57:38 +0000817 options.cache = os.path.abspath(options.cache)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000818 policies = CachePolicies(
819 options.max_cache_size, options.min_free_space, options.max_items)
csharp@chromium.orgffd8cf02013-01-09 21:57:38 +0000820
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400821 # |options.cache| path may not exist until DiskCache() instance is created.
822 cache = DiskCache(
823 options.cache, policies, isolateserver.get_hash_algo(options.namespace))
824 remote = options.isolate_server or options.indir
825 with isolateserver.get_storage(remote, options.namespace) as storage:
826 # Hashing schemes used by |storage| and |cache| MUST match.
827 assert storage.hash_algo == cache.hash_algo
828 return run_tha_test(
829 options.isolated or options.hash, storage, cache, args)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000830
831
832if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000833 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000834 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500835 sys.exit(main(sys.argv[1:]))