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

Side by Side Diff: tools/isolate/run_test_from_archive.py

Issue 10880009: Enforces strict manifest content. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix Created 8 years, 3 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tools/isolate/run_test_from_archive_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Reads a manifest, creates a tree of hardlinks and runs the test. 6 """Reads a manifest, creates a tree of hardlinks and runs the test.
7 7
8 Keeps a local cache. 8 Keeps a local cache.
9 """ 9 """
10 10
11 import ctypes 11 import ctypes
12 import json 12 import json
13 import logging 13 import logging
14 import optparse 14 import optparse
15 import os 15 import os
16 import re 16 import re
17 import shutil 17 import shutil
18 import stat 18 import stat
19 import subprocess 19 import subprocess
20 import sys 20 import sys
21 import tempfile 21 import tempfile
22 import time 22 import time
23 import urllib 23 import urllib
24 24
25 25
26 # Types of action accepted by recreate_tree(). 26 # Types of action accepted by recreate_tree().
27 HARDLINK, SYMLINK, COPY = range(1, 4) 27 HARDLINK, SYMLINK, COPY = range(1, 4)
28 28
29 RE_IS_SHA1 = re.compile(r'^[a-fA-F0-9]{40}$')
30
31
32 class ConfigError(ValueError):
33 """Generic failure to load a manifest."""
34 pass
35
29 36
30 class MappingError(OSError): 37 class MappingError(OSError):
31 """Failed to recreate the tree.""" 38 """Failed to recreate the tree."""
32 pass 39 pass
33 40
34 41
35 def os_link(source, link_name): 42 def os_link(source, link_name):
36 """Add support for os.link() on Windows.""" 43 """Add support for os.link() on Windows."""
37 if sys.platform == 'win32': 44 if sys.platform == 'win32':
38 if not ctypes.windll.kernel32.CreateHardLinkW( 45 if not ctypes.windll.kernel32.CreateHardLinkW(
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 172
166 173
167 def make_temp_dir(prefix, root_dir): 174 def make_temp_dir(prefix, root_dir):
168 """Returns a temporary directory on the same file system than root_dir.""" 175 """Returns a temporary directory on the same file system than root_dir."""
169 base_temp_dir = None 176 base_temp_dir = None
170 if not is_same_filesystem(root_dir, tempfile.gettempdir()): 177 if not is_same_filesystem(root_dir, tempfile.gettempdir()):
171 base_temp_dir = os.path.dirname(root_dir) 178 base_temp_dir = os.path.dirname(root_dir)
172 return tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir) 179 return tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir)
173 180
174 181
182 def assert_is_str(value):
183 if not isinstance(value, basestring):
184 raise ConfigError('Expected string, got %r' % value)
185
186
187 def assert_is_int(value):
188 if not isinstance(value, int):
189 raise ConfigError('Expected int, got %r' % value)
190
191
192 def assert_is_bool(value):
193 if value not in (None, True, False):
194 raise ConfigError('Expected bool, got %r' % value)
195
196
197 def assert_is_sha1(value):
198 assert_is_str(value)
199 if not RE_IS_SHA1.match(value):
200 raise ConfigError('Expected sha-1, got %r' % value)
201
202
203 def get_assert_is_list(check):
204 """Returns a function that asserts the value is a list."""
205 def is_list_item(value):
206 if not isinstance(value, list):
207 raise ConfigError('Expected list, got %r' % value)
208 any(check(item) for item in value)
209 return is_list_item
210
211
212 def get_assert_is_dict(key_check, values_check):
213 """Returns a function that ensure the value is a dict.
214
215 It checks all the keys passes key_check and values_check is a dict of each
216 checks for each key.
217
218 If values_check contains a None entry, keys not in values_check will be
219 checked with this key. If None is not present in values_check, unknown keys
220 will be refused.
221 """
222 def is_dict_item(value):
223 if not isinstance(value, dict):
224 raise ConfigError('Expected dict, got %r' % value)
225
226 for key in value:
227 key_check(key)
228
229 # values_check is the set of valid keys.
230 unknown = set(value) - set(values_check)
231 if unknown and not None in values_check:
232 raise ConfigError(
233 'Got unknown key(s) %s in %r; expected only %s' %
234 (', '.join(unknown), value, ', '.join(values_check)))
235 for k, v in value.iteritems():
236 if k in values_check:
237 values_check[k](v)
238 else:
239 values_check[None](v)
240 return is_dict_item
241
242
243 def load_manifest(content):
244 """Verifies the manifest is valid and loads this object with the json data.
245 """
246 data = json.loads(content)
247
248 file_keys = {
249 'link': assert_is_str,
250 'mode': assert_is_int,
251 'sha-1': assert_is_sha1,
252 'timestamp': assert_is_int,
253 }
254
255 files_key = {
256 # Any file has to fit files_keys specification.
257 None: get_assert_is_dict(assert_is_str, file_keys),
258 }
259
260 keys = {
261 'command': get_assert_is_list(assert_is_str),
262 # Could use assert_is_valid_filename instead of assert_is_str.
263 'files': get_assert_is_dict(assert_is_str, files_key),
264 'read_only': assert_is_bool,
265 'relative_cwd': assert_is_str,
266 }
267
268 get_assert_is_dict(assert_is_str, keys)(data)
cmp 2012/08/28 17:38:29 this code requires a lot of state to be in-mind to
269 # Add a special verification.
270 for value in data.get('files', {}).itervalues():
271 if bool('sha-1' in value) and bool('link' in value):
272 raise ConfigError(
273 'Did not expect both \'sha-1\' and \'link\', got: %r' % value)
274 return data
275
276
175 def fix_python_path(cmd): 277 def fix_python_path(cmd):
176 """Returns the fixed command line to call the right python executable.""" 278 """Returns the fixed command line to call the right python executable."""
177 out = cmd[:] 279 out = cmd[:]
178 if out[0] == 'python': 280 if out[0] == 'python':
179 out[0] = sys.executable 281 out[0] = sys.executable
180 elif out[0].endswith('.py'): 282 elif out[0].endswith('.py'):
181 out.insert(0, sys.executable) 283 out.insert(0, sys.executable)
182 return out 284 return out
183 285
184 286
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 os.makedirs(outfiledir) 488 os.makedirs(outfiledir)
387 if 'sha-1' in properties: 489 if 'sha-1' in properties:
388 # A normal file. 490 # A normal file.
389 infile = properties['sha-1'] 491 infile = properties['sha-1']
390 cache.retrieve(infile) 492 cache.retrieve(infile)
391 link_file(outfile, cache.path(infile), HARDLINK) 493 link_file(outfile, cache.path(infile), HARDLINK)
392 elif 'link' in properties: 494 elif 'link' in properties:
393 # A symlink. 495 # A symlink.
394 os.symlink(properties['link'], outfile) 496 os.symlink(properties['link'], outfile)
395 else: 497 else:
396 raise ValueError('Unexpected entry: %s' % properties) 498 raise ConfigError('Unexpected entry: %s' % properties)
397 if 'mode' in properties: 499 if 'mode' in properties:
398 # It's not set on Windows. 500 # It's not set on Windows.
399 os.chmod(outfile, properties['mode']) 501 os.chmod(outfile, properties['mode'])
400 502
401 cwd = os.path.join(outdir, manifest.get('relative_cwd', '')) 503 cwd = os.path.join(outdir, manifest.get('relative_cwd', ''))
402 if not os.path.isdir(cwd): 504 if not os.path.isdir(cwd):
403 os.makedirs(cwd) 505 os.makedirs(cwd)
404 if manifest.get('read_only'): 506 if manifest.get('read_only'):
405 make_writable(outdir, True) 507 make_writable(outdir, True)
406 cmd = manifest['command'] 508 cmd = manifest['command']
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
475 parser.error('One and only one of --manifest or --hash is required.') 577 parser.error('One and only one of --manifest or --hash is required.')
476 if not options.remote: 578 if not options.remote:
477 parser.error('--remote is required.') 579 parser.error('--remote is required.')
478 if args: 580 if args:
479 parser.error('Unsupported args %s' % ' '.join(args)) 581 parser.error('Unsupported args %s' % ' '.join(args))
480 582
481 if options.hash: 583 if options.hash:
482 # First calculate the reference to it. 584 # First calculate the reference to it.
483 options.manifest = '%s/%s' % (options.remote.rstrip('/'), options.hash) 585 options.manifest = '%s/%s' % (options.remote.rstrip('/'), options.hash)
484 try: 586 try:
485 manifest = json.load(open_remote(options.manifest)) 587 manifest = load_manifest(open_remote(options.manifest).read())
486 except IOError as e: 588 except IOError as e:
487 parser.error( 589 parser.error(
488 'Failed to read manifest %s; remote:%s; hash:%s; %s' % 590 'Failed to read manifest %s; remote:%s; hash:%s; %s' %
489 (options.manifest, options.remote, options.hash, str(e))) 591 (options.manifest, options.remote, options.hash, str(e)))
490 592
491 policies = CachePolicies( 593 policies = CachePolicies(
492 options.max_cache_size, options.min_free_space, options.max_items) 594 options.max_cache_size, options.min_free_space, options.max_items)
493 try: 595 try:
494 return run_tha_test( 596 return run_tha_test(
495 manifest, 597 manifest,
496 os.path.abspath(options.cache), 598 os.path.abspath(options.cache),
497 options.remote, 599 options.remote,
498 policies) 600 policies)
499 except MappingError, e: 601 except (ConfigError, MappingError), e:
500 print >> sys.stderr, str(e) 602 print >> sys.stderr, str(e)
501 return 1 603 return 1
502 604
503 605
504 if __name__ == '__main__': 606 if __name__ == '__main__':
505 sys.exit(main()) 607 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | tools/isolate/run_test_from_archive_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698