blob: 8682512c1418eb0d7d0212f05deafe29ac0f4fa5 [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 Ruel8806e622014-02-12 14:15:53 -050017__version__ = '0.3.1'
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
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +000034from utils import threading_utils
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000035from utils import tools
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +000036from utils import zip_package
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000037
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080038import auth
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000039import isolateserver
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000040
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000041
vadimsh@chromium.org85071062013-08-21 23:37:45 +000042# Absolute path to this file (can be None if running from zip on Mac).
43THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000044
45# Directory that contains this file (might be inside zip package).
vadimsh@chromium.org85071062013-08-21 23:37:45 +000046BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000047
48# Directory that contains currently running script file.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000049if zip_package.get_main_script_path():
50 MAIN_DIR = os.path.dirname(
51 os.path.abspath(zip_package.get_main_script_path()))
52else:
53 # This happens when 'import run_isolated' is executed at the python
54 # interactive prompt, in that case __file__ is undefined.
55 MAIN_DIR = None
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000056
maruel@chromium.org6b365dc2012-10-18 19:17:56 +000057# Types of action accepted by link_file().
maruel@chromium.orgba6489b2013-07-11 20:23:33 +000058HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY = range(1, 5)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000059
csharp@chromium.orgff2a4662012-11-21 20:49:32 +000060# The name of the log file to use.
61RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
62
csharp@chromium.orge217f302012-11-22 16:51:53 +000063# The name of the log to use for the run_test_cases.py command
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000064RUN_TEST_CASES_LOG = 'run_test_cases.log'
csharp@chromium.orge217f302012-11-22 16:51:53 +000065
vadimsh@chromium.org87d63262013-04-04 19:34:21 +000066
maruel@chromium.org9e9ceaa2013-04-05 15:42:42 +000067# Used by get_flavor().
68FLAVOR_MAPPING = {
69 'cygwin': 'win',
70 'win32': 'win',
71 'darwin': 'mac',
72 'sunos5': 'solaris',
73 'freebsd7': 'freebsd',
74 'freebsd8': 'freebsd',
75}
76
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000077
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000078def get_as_zip_package(executable=True):
79 """Returns ZipPackage with this module and all its dependencies.
80
81 If |executable| is True will store run_isolated.py as __main__.py so that
82 zip package is directly executable be python.
83 """
84 # Building a zip package when running from another zip package is
85 # unsupported and probably unneeded.
86 assert not zip_package.is_zipped_module(sys.modules[__name__])
vadimsh@chromium.org85071062013-08-21 23:37:45 +000087 assert THIS_FILE_PATH
88 assert BASE_DIR
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000089 package = zip_package.ZipPackage(root=BASE_DIR)
90 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None)
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000091 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py'))
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080092 package.add_python_file(os.path.join(BASE_DIR, 'auth.py'))
vadimsh@chromium.org8b9d56b2013-08-21 22:24:35 +000093 package.add_directory(os.path.join(BASE_DIR, 'third_party'))
94 package.add_directory(os.path.join(BASE_DIR, 'utils'))
95 return package
96
97
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000098def get_flavor():
99 """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py."""
maruel@chromium.org9e9ceaa2013-04-05 15:42:42 +0000100 return FLAVOR_MAPPING.get(sys.platform, 'linux')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000101
102
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500103def hardlink(source, link_name):
104 """Hardlinks a file.
105
106 Add support for os.link() on Windows.
107 """
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000108 if sys.platform == 'win32':
109 if not ctypes.windll.kernel32.CreateHardLinkW(
110 unicode(link_name), unicode(source), 0):
111 raise OSError()
112 else:
113 os.link(source, link_name)
114
115
116def readable_copy(outfile, infile):
117 """Makes a copy of the file that is readable by everyone."""
csharp@chromium.org59d116d2013-07-05 18:04:08 +0000118 shutil.copy2(infile, outfile)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000119 read_enabled_mode = (os.stat(outfile).st_mode | stat.S_IRUSR |
120 stat.S_IRGRP | stat.S_IROTH)
121 os.chmod(outfile, read_enabled_mode)
122
123
124def link_file(outfile, infile, action):
125 """Links a file. The type of link depends on |action|."""
126 logging.debug('Mapping %s to %s' % (infile, outfile))
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000127 if action not in (HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000128 raise ValueError('Unknown mapping action %s' % action)
129 if not os.path.isfile(infile):
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000130 raise isolateserver.MappingError('%s is missing' % infile)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000131 if os.path.isfile(outfile):
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000132 raise isolateserver.MappingError(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000133 '%s already exist; insize:%d; outsize:%d' %
134 (outfile, os.stat(infile).st_size, os.stat(outfile).st_size))
135
136 if action == COPY:
137 readable_copy(outfile, infile)
138 elif action == SYMLINK and sys.platform != 'win32':
139 # On windows, symlink are converted to hardlink and fails over to copy.
maruel@chromium.orgf43e68b2012-10-15 20:23:10 +0000140 os.symlink(infile, outfile) # pylint: disable=E1101
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000141 else:
142 try:
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500143 hardlink(infile, outfile)
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000144 except OSError as e:
145 if action == HARDLINK:
maruel@chromium.org9958e4a2013-09-17 00:01:48 +0000146 raise isolateserver.MappingError(
maruel@chromium.orgba6489b2013-07-11 20:23:33 +0000147 'Failed to hardlink %s to %s: %s' % (infile, outfile, e))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000148 # Probably a different file system.
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000149 logging.warning(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000150 'Failed to hardlink, failing back to copy %s to %s' % (
151 infile, outfile))
152 readable_copy(outfile, infile)
153
154
Marc-Antoine Rueld2d4d4f2013-11-10 14:32:38 -0500155def set_read_only(path, read_only):
156 """Sets or resets the write bit on a file or directory.
157
158 Zaps out access to 'group' and 'others'.
159 """
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500160 assert isinstance(read_only, bool), read_only
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000161 mode = os.lstat(path).st_mode
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500162 # TODO(maruel): Stop removing GO bits.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000163 if read_only:
164 mode = mode & 0500
165 else:
166 mode = mode | 0200
167 if hasattr(os, 'lchmod'):
168 os.lchmod(path, mode) # pylint: disable=E1101
169 else:
170 if stat.S_ISLNK(mode):
171 # Skip symlink without lchmod() support.
Marc-Antoine Ruel45dc2902013-12-05 14:54:20 -0500172 logging.debug(
173 'Can\'t change %sw bit on symlink %s',
174 '-' if read_only else '+', path)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000175 return
176
177 # TODO(maruel): Implement proper DACL modification on Windows.
178 os.chmod(path, mode)
179
180
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500181def make_tree_read_only(root):
182 """Makes all the files in the directories read only.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000183
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500184 Also makes the directories read only, only if it makes sense on the platform.
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500185
186 This means no file can be created or deleted.
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500187 """
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500188 logging.debug('make_tree_read_only(%s)', root)
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500189 assert os.path.isabs(root), root
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500190 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
191 for filename in filenames:
192 set_read_only(os.path.join(dirpath, filename), True)
193 if sys.platform != 'win32':
194 # It must not be done on Windows.
195 for dirname in dirnames:
196 set_read_only(os.path.join(dirpath, dirname), True)
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500197 if sys.platform != 'win32':
198 set_read_only(root, True)
199
200
201def make_tree_files_read_only(root):
202 """Makes all the files in the directories read only but not the directories
203 themselves.
204
205 This means files can be created or deleted.
206 """
207 logging.debug('make_tree_files_read_only(%s)', root)
208 assert os.path.isabs(root), root
209 if sys.platform != 'win32':
210 set_read_only(root, False)
211 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
212 for filename in filenames:
213 set_read_only(os.path.join(dirpath, filename), True)
214 if sys.platform != 'win32':
215 # It must not be done on Windows.
216 for dirname in dirnames:
217 set_read_only(os.path.join(dirpath, dirname), False)
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500218
219
220def make_tree_writeable(root):
221 """Makes all the files in the directories writeable.
222
223 Also makes the directories writeable, only if it makes sense on the platform.
224
225 It is different from make_tree_deleteable() because it unconditionally affects
226 the files.
227 """
228 logging.debug('make_tree_writeable(%s)', root)
229 assert os.path.isabs(root), root
230 if sys.platform != 'win32':
231 set_read_only(root, False)
232 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
233 for filename in filenames:
234 set_read_only(os.path.join(dirpath, filename), False)
235 if sys.platform != 'win32':
236 # It must not be done on Windows.
237 for dirname in dirnames:
238 set_read_only(os.path.join(dirpath, dirname), False)
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500239
240
241def make_tree_deleteable(root):
242 """Changes the appropriate permissions so the files in the directories can be
243 deleted.
244
245 On Windows, the files are modified. On other platforms, modify the directory.
Marc-Antoine Ruel4727bd52013-11-22 14:44:56 -0500246 It only does the minimum so the files can be deleted safely.
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500247
248 Warning on Windows: since file permission is modified, the file node is
249 modified. This means that for hard-linked files, every directory entry for the
250 file node has its file permission modified.
251 """
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500252 logging.debug('make_tree_deleteable(%s)', root)
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500253 assert os.path.isabs(root), root
254 if sys.platform != 'win32':
255 set_read_only(root, False)
256 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
257 if sys.platform == 'win32':
258 for filename in filenames:
259 set_read_only(os.path.join(dirpath, filename), False)
260 else:
261 for dirname in dirnames:
262 set_read_only(os.path.join(dirpath, dirname), False)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000263
264
265def rmtree(root):
266 """Wrapper around shutil.rmtree() to retry automatically on Windows."""
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500267 make_tree_deleteable(root)
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500268 logging.info('rmtree(%s)', root)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000269 if sys.platform == 'win32':
270 for i in range(3):
271 try:
272 shutil.rmtree(root)
273 break
274 except WindowsError: # pylint: disable=E0602
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500275 if i == 2:
276 raise
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000277 delay = (i+1)*2
278 print >> sys.stderr, (
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500279 'Failed to delete %s. Maybe the test has subprocess outliving it.'
280 ' Sleep %d seconds.' % (root, delay))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000281 time.sleep(delay)
282 else:
283 shutil.rmtree(root)
284
285
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000286def try_remove(filepath):
287 """Removes a file without crashing even if it doesn't exist."""
288 try:
Marc-Antoine Rueldebee212013-11-11 14:51:03 -0500289 # TODO(maruel): Not do it unless necessary since it slows this function
290 # down.
291 if sys.platform == 'win32':
292 # Deleting a read-only file will fail if it is read-only.
293 set_read_only(filepath, False)
294 else:
295 # Deleting a read-only file will fail if the directory is read-only.
296 set_read_only(os.path.dirname(filepath), False)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000297 os.remove(filepath)
298 except OSError:
299 pass
300
301
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000302def is_same_filesystem(path1, path2):
303 """Returns True if both paths are on the same filesystem.
304
305 This is required to enable the use of hardlinks.
306 """
307 assert os.path.isabs(path1), path1
308 assert os.path.isabs(path2), path2
309 if sys.platform == 'win32':
310 # If the drive letter mismatches, assume it's a separate partition.
311 # TODO(maruel): It should look at the underlying drive, a drive letter could
312 # be a mount point to a directory on another drive.
313 assert re.match(r'^[a-zA-Z]\:\\.*', path1), path1
314 assert re.match(r'^[a-zA-Z]\:\\.*', path2), path2
315 if path1[0].lower() != path2[0].lower():
316 return False
317 return os.stat(path1).st_dev == os.stat(path2).st_dev
318
319
320def get_free_space(path):
321 """Returns the number of free bytes."""
322 if sys.platform == 'win32':
323 free_bytes = ctypes.c_ulonglong(0)
324 ctypes.windll.kernel32.GetDiskFreeSpaceExW(
325 ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
326 return free_bytes.value
maruel@chromium.orgf43e68b2012-10-15 20:23:10 +0000327 # For OSes other than Windows.
328 f = os.statvfs(path) # pylint: disable=E1101
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000329 return f.f_bfree * f.f_frsize
330
331
332def make_temp_dir(prefix, root_dir):
333 """Returns a temporary directory on the same file system as root_dir."""
334 base_temp_dir = None
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500335 if root_dir and not is_same_filesystem(root_dir, tempfile.gettempdir()):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000336 base_temp_dir = os.path.dirname(root_dir)
337 return tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir)
338
339
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000340class CachePolicies(object):
341 def __init__(self, max_cache_size, min_free_space, max_items):
342 """
343 Arguments:
344 - max_cache_size: Trim if the cache gets larger than this value. If 0, the
345 cache is effectively a leak.
346 - min_free_space: Trim if disk free space becomes lower than this value. If
347 0, it unconditionally fill the disk.
348 - max_items: Maximum number of items to keep in the cache. If 0, do not
349 enforce a limit.
350 """
351 self.max_cache_size = max_cache_size
352 self.min_free_space = min_free_space
353 self.max_items = max_items
354
355
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000356class DiskCache(isolateserver.LocalCache):
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000357 """Stateful LRU cache in a flat hash table in a directory.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000358
359 Saves its state as json file.
360 """
361 STATE_FILE = 'state.json'
362
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000363 def __init__(self, cache_dir, policies, algo):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000364 """
365 Arguments:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000366 cache_dir: directory where to place the cache.
367 policies: cache retention policies.
368 algo: hashing algorithm used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000369 """
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000370 super(DiskCache, self).__init__()
371 self.algo = algo
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000372 self.cache_dir = cache_dir
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000373 self.policies = policies
374 self.state_file = os.path.join(cache_dir, self.STATE_FILE)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000375
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000376 # All protected methods (starting with '_') except _path should be called
377 # with this lock locked.
378 self._lock = threading_utils.LockWithAssert()
379 self._lru = lru.LRUDict()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000380
381 # Profiling values.
382 self._added = []
383 self._removed = []
384 self._free_disk = 0
385
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000386 with tools.Profiler('Setup'):
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000387 with self._lock:
388 self._load()
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000389
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000390 def __enter__(self):
391 return self
392
393 def __exit__(self, _exc_type, _exec_value, _traceback):
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000394 with tools.Profiler('CleanupTrimming'):
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000395 with self._lock:
396 self._trim()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000397
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000398 logging.info(
399 '%5d (%8dkb) added',
400 len(self._added), sum(self._added) / 1024)
401 logging.info(
402 '%5d (%8dkb) current',
403 len(self._lru),
404 sum(self._lru.itervalues()) / 1024)
405 logging.info(
406 '%5d (%8dkb) removed',
407 len(self._removed), sum(self._removed) / 1024)
408 logging.info(
409 ' %8dkb free',
410 self._free_disk / 1024)
maruel@chromium.org41601642013-09-18 19:40:46 +0000411 return False
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000412
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000413 def cached_set(self):
414 with self._lock:
415 return self._lru.keys_set()
416
417 def touch(self, digest, size):
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500418 """Verifies an actual file is valid.
419
420 Note that is doesn't compute the hash so it could still be corrupted if the
421 file size didn't change.
422
423 TODO(maruel): More stringent verification while keeping the check fast.
424 """
425 # Do the check outside the lock.
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000426 if not isolateserver.is_valid_file(self._path(digest), size):
427 return False
428
429 # Update it's LRU position.
430 with self._lock:
431 if digest not in self._lru:
432 return False
433 self._lru.touch(digest)
434 return True
435
436 def evict(self, digest):
437 with self._lock:
438 self._lru.pop(digest)
439 self._delete_file(digest, isolateserver.UNKNOWN_FILE_SIZE)
440
441 def read(self, digest):
442 with open(self._path(digest), 'rb') as f:
443 return f.read()
444
445 def write(self, digest, content):
446 path = self._path(digest)
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500447 # A stale broken file may remain. It is possible for the file to have write
448 # access bit removed which would cause the file_write() call to fail to open
449 # in write mode. Take no chance here.
450 try_remove(path)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000451 try:
452 size = isolateserver.file_write(path, content)
453 except:
454 # There are two possible places were an exception can occur:
455 # 1) Inside |content| generator in case of network or unzipping errors.
456 # 2) Inside file_write itself in case of disk IO errors.
457 # In any case delete an incomplete file and propagate the exception to
458 # caller, it will be logged there.
459 try_remove(path)
460 raise
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500461 # Make the file read-only in the cache. This has a few side-effects since
462 # the file node is modified, so every directory entries to this file becomes
463 # read-only. It's fine here because it is a new file.
464 set_read_only(path, True)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000465 with self._lock:
466 self._add(digest, size)
467
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500468 def hardlink(self, digest, dest, file_mode):
Marc-Antoine Ruelccafe0e2013-11-08 16:15:36 -0500469 """Hardlinks the file to |dest|.
470
471 Note that the file permission bits are on the file node, not the directory
472 entry, so changing the access bit on any of the directory entries for the
473 file node will affect them all.
474 """
475 path = self._path(digest)
476 link_file(dest, path, HARDLINK)
Marc-Antoine Ruelfb199cf2013-11-12 15:38:12 -0500477 if file_mode is not None:
478 # Ignores all other bits.
479 os.chmod(dest, file_mode & 0500)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000480
481 def _load(self):
482 """Loads state of the cache from json file."""
483 self._lock.assert_locked()
484
485 if not os.path.isdir(self.cache_dir):
486 os.makedirs(self.cache_dir)
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500487 else:
488 # Make sure the cache is read-only.
489 # TODO(maruel): Calculate the cost and optimize the performance
490 # accordingly.
491 make_tree_read_only(self.cache_dir)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000492
493 # Load state of the cache.
494 if os.path.isfile(self.state_file):
495 try:
496 self._lru = lru.LRUDict.load(self.state_file)
497 except ValueError as err:
498 logging.error('Failed to load cache state: %s' % (err,))
499 # Don't want to keep broken state file.
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500500 try_remove(self.state_file)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000501
502 # Ensure that all files listed in the state still exist and add new ones.
503 previous = self._lru.keys_set()
504 unknown = []
505 for filename in os.listdir(self.cache_dir):
506 if filename == self.STATE_FILE:
507 continue
508 if filename in previous:
509 previous.remove(filename)
510 continue
511 # An untracked file.
512 if not isolateserver.is_valid_hash(filename, self.algo):
513 logging.warning('Removing unknown file %s from cache', filename)
514 try_remove(self._path(filename))
515 continue
516 # File that's not referenced in 'state.json'.
517 # TODO(vadimsh): Verify its SHA1 matches file name.
518 logging.warning('Adding unknown file %s to cache', filename)
519 unknown.append(filename)
520
521 if unknown:
522 # Add as oldest files. They will be deleted eventually if not accessed.
523 self._add_oldest_list(unknown)
524 logging.warning('Added back %d unknown files', len(unknown))
525
526 if previous:
527 # Filter out entries that were not found.
528 logging.warning('Removed %d lost files', len(previous))
529 for filename in previous:
530 self._lru.pop(filename)
531 self._trim()
532
533 def _save(self):
534 """Saves the LRU ordering."""
535 self._lock.assert_locked()
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500536 if sys.platform != 'win32':
537 d = os.path.dirname(self.state_file)
538 if os.path.isdir(d):
539 # Necessary otherwise the file can't be created.
540 set_read_only(d, False)
541 if os.path.isfile(self.state_file):
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500542 set_read_only(self.state_file, False)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000543 self._lru.save(self.state_file)
544
545 def _trim(self):
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000546 """Trims anything we don't know, make sure enough free space exists."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000547 self._lock.assert_locked()
548
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000549 # Ensure maximum cache size.
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000550 if self.policies.max_cache_size:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000551 total_size = sum(self._lru.itervalues())
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000552 while total_size > self.policies.max_cache_size:
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000553 total_size -= self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000554
555 # Ensure maximum number of items in the cache.
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000556 if self.policies.max_items and len(self._lru) > self.policies.max_items:
557 for _ in xrange(len(self._lru) - self.policies.max_items):
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000558 self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000559
560 # Ensure enough free space.
561 self._free_disk = get_free_space(self.cache_dir)
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000562 trimmed_due_to_space = False
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000563 while (
564 self.policies.min_free_space and
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000565 self._lru and
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000566 self._free_disk < self.policies.min_free_space):
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000567 trimmed_due_to_space = True
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000568 self._remove_lru_file()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000569 self._free_disk = get_free_space(self.cache_dir)
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000570 if trimmed_due_to_space:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000571 total = sum(self._lru.itervalues())
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000572 logging.warning(
573 'Trimmed due to not enough free disk space: %.1fkb free, %.1fkb '
574 'cache (%.1f%% of its maximum capacity)',
575 self._free_disk / 1024.,
576 total / 1024.,
577 100. * self.policies.max_cache_size / float(total),
578 )
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000579 self._save()
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000580
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000581 def _path(self, digest):
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000582 """Returns the path to one item."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000583 return os.path.join(self.cache_dir, digest)
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000584
585 def _remove_lru_file(self):
586 """Removes the last recently used file and returns its size."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000587 self._lock.assert_locked()
588 digest, size = self._lru.pop_oldest()
589 self._delete_file(digest, size)
maruel@chromium.orge45728d2013-09-16 23:23:22 +0000590 return size
591
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000592 def _add(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000593 """Adds an item into LRU cache marking it as a newest one."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000594 self._lock.assert_locked()
595 if size == isolateserver.UNKNOWN_FILE_SIZE:
596 size = os.stat(self._path(digest)).st_size
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000597 self._added.append(size)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000598 self._lru.add(digest, size)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000599
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000600 def _add_oldest_list(self, digests):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000601 """Adds a bunch of items into LRU cache marking them as oldest ones."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000602 self._lock.assert_locked()
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000603 pairs = []
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000604 for digest in digests:
605 size = os.stat(self._path(digest)).st_size
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000606 self._added.append(size)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000607 pairs.append((digest, size))
608 self._lru.batch_insert_oldest(pairs)
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000609
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000610 def _delete_file(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000611 """Deletes cache file from the file system."""
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000612 self._lock.assert_locked()
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000613 try:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000614 if size == isolateserver.UNKNOWN_FILE_SIZE:
615 size = os.stat(self._path(digest)).st_size
Marc-Antoine Ruelbcdf6d62013-11-11 16:45:14 -0500616 try_remove(self._path(digest))
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000617 self._removed.append(size)
vadimsh@chromium.org3e97deb2013-08-24 00:56:44 +0000618 except OSError as e:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000619 logging.error('Error attempting to delete a file %s:\n%s' % (digest, e))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000620
621
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500622def change_tree_read_only(rootdir, read_only):
623 """Changes the tree read-only bits according to the read_only specification.
624
625 The flag can be 0, 1 or 2, which will affect the possibility to modify files
626 and create or delete files.
627 """
628 if read_only == 2:
629 # Files and directories (except on Windows) are marked read only. This
630 # inhibits modifying, creating or deleting files in the test directory,
631 # except on Windows where creating and deleting files is still possible.
632 make_tree_read_only(rootdir)
633 elif read_only == 1:
634 # Files are marked read only but not the directories. This inhibits
635 # modifying files but creating or deleting files is still possible.
636 make_tree_files_read_only(rootdir)
637 elif read_only in (0, None):
638 # Anything can be modified. This is the default in the .isolated file
639 # format.
640 #
641 # TODO(maruel): This is currently dangerous as long as DiskCache.touch()
642 # is not yet changed to verify the hash of the content of the files it is
643 # looking at, so that if a test modifies an input file, the file must be
644 # deleted.
645 make_tree_writeable(rootdir)
646 else:
647 raise ValueError(
648 'change_tree_read_only(%s, %s): Unknown flag %s' %
649 (rootdir, read_only, read_only))
650
651
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500652def process_command(command, out_dir):
653 """Replaces isolated specific variables in a command line."""
654 return [c.replace('${ISOLATED_OUTDIR}', out_dir) for c in command]
655
656
657def run_tha_test(isolated_hash, storage, cache, algo, extra_args):
658 """Downloads the dependencies in the cache, hardlinks them into a temporary
659 directory and runs the executable from there.
660
661 A temporary directory is created to hold the output files. The content inside
662 this directory will be uploaded back to |storage| packaged as a .isolated
663 file.
664
665 Arguments:
666 isolated_hash: the sha-1 of the .isolated file that must be retrieved to
667 recreate the tree of files to run the target executable.
668 storage: an isolateserver.Storage object to retrieve remote objects. This
669 object has a reference to an isolateserver.StorageApi, which does
670 the actual I/O.
671 cache: an isolateserver.LocalCache to keep from retrieving the same objects
672 constantly by caching the objects retrieved. Can be on-disk or
673 in-memory.
674 algo: an hashlib class to hash content. Usually hashlib.sha1.
675 extra_args: optional arguments to add to the command stated in the .isolate
676 file.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000677 """
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500678 run_dir = make_temp_dir('run_tha_test', cache.cache_dir)
679 out_dir = unicode(tempfile.mkdtemp(prefix='run_tha_test'))
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500680 result = 0
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000681 try:
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000682 try:
683 settings = isolateserver.fetch_isolated(
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000684 isolated_hash=isolated_hash,
685 storage=storage,
686 cache=cache,
687 algo=algo,
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500688 outdir=run_dir,
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000689 os_flavor=get_flavor(),
690 require_command=True)
maruel@chromium.org4f2ebe42013-09-19 13:09:08 +0000691 except isolateserver.ConfigError as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000692 tools.report_error(e)
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500693 result = 1
694 return result
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000695
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500696 change_tree_read_only(run_dir, settings.read_only)
697 cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500698 command = settings.command + extra_args
Vadim Shtayurae4a780b2014-01-17 13:18:53 -0800699
700 # subprocess.call doesn't consider 'cwd' when searching for executable.
701 # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
702 # path if necessary.
703 if not os.path.isabs(command[0]):
704 command[0] = os.path.abspath(os.path.join(cwd, command[0]))
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500705 command = process_command(command, out_dir)
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500706 logging.info('Running %s, cwd=%s' % (command, cwd))
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000707
708 # TODO(csharp): This should be specified somewhere else.
709 # TODO(vadimsh): Pass it via 'env_vars' in manifest.
710 # Add a rotating log file if one doesn't already exist.
711 env = os.environ.copy()
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000712 if MAIN_DIR:
713 env.setdefault('RUN_TEST_CASES_LOG_FILE',
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000714 os.path.join(MAIN_DIR, RUN_TEST_CASES_LOG))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000715 try:
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000716 with tools.Profiler('RunTest'):
Marc-Antoine Rueldef5b802014-01-08 20:57:12 -0500717 result = subprocess.call(command, cwd=cwd, env=env)
Vadim Shtayurae4a780b2014-01-17 13:18:53 -0800718 except OSError as e:
719 tools.report_error('Failed to run %s; cwd=%s: %s' % (command, cwd, e))
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500720 result = 1
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500721
722 # Upload out_dir and generate a .isolated file out of this directory. It is
723 # only done if files were written in the directory.
724 if os.listdir(out_dir):
725 with tools.Profiler('ArchiveOutput'):
Marc-Antoine Ruel488ce8f2014-02-09 11:25:04 -0500726 results = isolateserver.archive_files_to_storage(
727 storage, algo, [out_dir], None)
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500728 # TODO(maruel): Implement side-channel to publish this information.
729 print('run_isolated output: %s' % results[0][0])
730
maruel@chromium.org781ccf62013-09-17 19:39:47 +0000731 finally:
Marc-Antoine Ruel7124e392014-01-09 11:49:21 -0500732 try:
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500733 rmtree(out_dir)
734 finally:
735 try:
736 rmtree(run_dir)
737 except OSError:
738 logging.warning('Leaking %s', run_dir)
739 # Swallow the exception so it doesn't generate an infrastructure error.
740 #
741 # It usually happens on Windows when a child process is not properly
742 # terminated, usually because of a test case starting child processes
743 # that time out. This causes files to be locked and it becomes
744 # impossible to delete them.
745 #
746 # Only report an infrastructure error if the test didn't fail. This is
747 # because a swarming bot will likely not reboot. This situation will
748 # cause accumulation of temporary hardlink trees.
749 if not result:
750 raise
Marc-Antoine Ruel3a963792013-12-11 11:33:49 -0500751 return result
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000752
753
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500754def main(args):
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000755 tools.disable_buffering()
756 parser = tools.OptionParserWithLogging(
maruel@chromium.orgdedbf492013-09-12 20:42:11 +0000757 usage='%prog <options>',
758 version=__version__,
759 log_file=RUN_ISOLATED_LOG_FILE)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000760
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500761 data_group = optparse.OptionGroup(parser, 'Data source')
762 data_group.add_option(
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000763 '-s', '--isolated',
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000764 metavar='FILE',
765 help='File/url describing what to map or run')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500766 data_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000767 '-H', '--hash',
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000768 help='Hash of the .isolated to grab from the hash table')
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500769 isolateserver.add_isolate_server_options(data_group, True)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500770 parser.add_option_group(data_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000771
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500772 cache_group = optparse.OptionGroup(parser, 'Cache management')
773 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000774 '--cache',
775 default='cache',
776 metavar='DIR',
777 help='Cache directory, default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500778 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000779 '--max-cache-size',
780 type='int',
781 metavar='NNN',
782 default=20*1024*1024*1024,
783 help='Trim if the cache gets larger than this value, default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500784 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000785 '--min-free-space',
786 type='int',
787 metavar='NNN',
maruel@chromium.org9e98e432013-05-31 17:06:51 +0000788 default=2*1024*1024*1024,
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000789 help='Trim if disk free space becomes lower than this value, '
790 'default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500791 cache_group.add_option(
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000792 '--max-items',
793 type='int',
794 metavar='NNN',
795 default=100000,
796 help='Trim if more than this number of items are in the cache '
797 'default=%default')
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500798 parser.add_option_group(cache_group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000799
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800800 auth.add_auth_options(parser)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500801 options, args = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800802 auth.process_auth_options(parser, options)
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500803 isolateserver.process_isolate_server_options(data_group, options)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000804
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000805 if bool(options.isolated) == bool(options.hash):
maruel@chromium.org5dd75dd2012-12-03 15:11:32 +0000806 logging.debug('One and only one of --isolated or --hash is required.')
maruel@chromium.org0cd0b182012-10-22 13:34:15 +0000807 parser.error('One and only one of --isolated or --hash is required.')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000808
csharp@chromium.orgffd8cf02013-01-09 21:57:38 +0000809 options.cache = os.path.abspath(options.cache)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000810 policies = CachePolicies(
811 options.max_cache_size, options.min_free_space, options.max_items)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000812 algo = isolateserver.get_hash_algo(options.namespace)
csharp@chromium.orgffd8cf02013-01-09 21:57:38 +0000813
maruel@chromium.org3e42ce82013-09-12 18:36:59 +0000814 try:
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000815 # |options.cache| may not exist until DiskCache() instance is created.
816 cache = DiskCache(options.cache, policies, algo)
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500817 remote = options.isolate_server or options.indir
818 with isolateserver.get_storage(remote, options.namespace) as storage:
Vadim Shtayura3172be52013-12-03 12:49:05 -0800819 return run_tha_test(
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -0500820 options.isolated or options.hash, storage, cache, algo, args)
vadimsh@chromium.org7b5dae32013-10-03 16:59:59 +0000821 except Exception as e:
maruel@chromium.org3e42ce82013-09-12 18:36:59 +0000822 # Make sure any exception is logged.
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000823 tools.report_error(e)
maruel@chromium.org3e42ce82013-09-12 18:36:59 +0000824 logging.exception(e)
825 return 1
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000826
827
828if __name__ == '__main__':
csharp@chromium.orgbfb98742013-03-26 20:28:36 +0000829 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +0000830 fix_encoding.fix_encoding()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -0500831 sys.exit(main(sys.argv[1:]))