OLD | NEW |
---|---|
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 Loading... | |
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 as root_dir.""" | 175 """Returns a temporary directory on the same file system as 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 load_manifest(content): | |
183 """Verifies the manifest is valid and loads this object with the json data. | |
184 """ | |
185 data = json.loads(content) | |
186 if not isinstance(data, dict): | |
187 raise ConfigError('Expected dict, got %r' % data) | |
188 | |
189 for key, value in data.iteritems(): | |
190 if key == 'command': | |
191 if not isinstance(value, list): | |
192 raise ConfigError('Expected list, got %r' % value) | |
193 for subvalue in value: | |
194 if not isinstance(subvalue, basestring): | |
195 raise ConfigError('Expected string, got %r' % subvalue) | |
196 | |
197 elif key == 'files': | |
198 if not isinstance(value, dict): | |
199 raise ConfigError('Expected dict, got %r' % value) | |
200 for subkey, subvalue in value.iteritems(): | |
201 if not isinstance(subkey, basestring): | |
202 raise ConfigError('Expected string, got %r' % subkey) | |
203 if not isinstance(subvalue, dict): | |
204 raise ConfigError('Expected dict, got %r' % subvalue) | |
205 for subsubkey, subsubvalue in subvalue.iteritems(): | |
M-A Ruel
2012/08/28 20:51:02
The main issue is that it's much easier to make a
| |
206 if subsubkey == 'link': | |
207 if not isinstance(subsubvalue, basestring): | |
208 raise ConfigError('Expected string, got %r' % subsubvalue) | |
209 elif subsubkey == 'mode': | |
210 if not isinstance(subsubvalue, int): | |
211 raise ConfigError('Expected int, got %r' % subsubvalue) | |
212 elif subsubkey == 'sha-1': | |
213 if not RE_IS_SHA1.match(subsubvalue): | |
214 raise ConfigError('Expected sha-1, got %r' % subsubvalue) | |
215 elif subsubkey == 'timestamp': | |
216 if not isinstance(subsubvalue, int): | |
217 raise ConfigError('Expected int, got %r' % subsubvalue) | |
218 else: | |
219 raise ConfigError('Unknown key %s' % subsubkey) | |
220 if bool('sha-1' in subvalue) and bool('link' in subvalue): | |
221 raise ConfigError( | |
222 'Did not expect both \'sha-1\' and \'link\', got: %r' % subvalue) | |
223 | |
224 elif key == 'read_only': | |
225 if not isinstance(value, bool): | |
226 raise ConfigError('Expected bool, got %r' % value) | |
227 | |
228 elif key == 'relative_cwd': | |
229 if not isinstance(value, basestring): | |
230 raise ConfigError('Expected string, got %r' % value) | |
231 | |
232 else: | |
233 raise ConfigError('Unknown key %s' % subkey) | |
234 | |
235 return data | |
236 | |
237 | |
175 def fix_python_path(cmd): | 238 def fix_python_path(cmd): |
176 """Returns the fixed command line to call the right python executable.""" | 239 """Returns the fixed command line to call the right python executable.""" |
177 out = cmd[:] | 240 out = cmd[:] |
178 if out[0] == 'python': | 241 if out[0] == 'python': |
179 out[0] = sys.executable | 242 out[0] = sys.executable |
180 elif out[0].endswith('.py'): | 243 elif out[0].endswith('.py'): |
181 out.insert(0, sys.executable) | 244 out.insert(0, sys.executable) |
182 return out | 245 return out |
183 | 246 |
184 | 247 |
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
387 os.makedirs(outfiledir) | 450 os.makedirs(outfiledir) |
388 if 'sha-1' in properties: | 451 if 'sha-1' in properties: |
389 # A normal file. | 452 # A normal file. |
390 infile = properties['sha-1'] | 453 infile = properties['sha-1'] |
391 cache.retrieve(infile) | 454 cache.retrieve(infile) |
392 link_file(outfile, cache.path(infile), HARDLINK) | 455 link_file(outfile, cache.path(infile), HARDLINK) |
393 elif 'link' in properties: | 456 elif 'link' in properties: |
394 # A symlink. | 457 # A symlink. |
395 os.symlink(properties['link'], outfile) | 458 os.symlink(properties['link'], outfile) |
396 else: | 459 else: |
397 raise ValueError('Unexpected entry: %s' % properties) | 460 raise ConfigError('Unexpected entry: %s' % properties) |
398 if 'mode' in properties: | 461 if 'mode' in properties: |
399 # It's not set on Windows. | 462 # It's not set on Windows. |
400 os.chmod(outfile, properties['mode']) | 463 os.chmod(outfile, properties['mode']) |
401 | 464 |
402 cwd = os.path.join(outdir, manifest.get('relative_cwd', '')) | 465 cwd = os.path.join(outdir, manifest.get('relative_cwd', '')) |
403 if not os.path.isdir(cwd): | 466 if not os.path.isdir(cwd): |
404 os.makedirs(cwd) | 467 os.makedirs(cwd) |
405 if manifest.get('read_only'): | 468 if manifest.get('read_only'): |
406 make_writable(outdir, True) | 469 make_writable(outdir, True) |
407 cmd = manifest['command'] | 470 cmd = manifest['command'] |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
476 parser.error('One and only one of --manifest or --hash is required.') | 539 parser.error('One and only one of --manifest or --hash is required.') |
477 if not options.remote: | 540 if not options.remote: |
478 parser.error('--remote is required.') | 541 parser.error('--remote is required.') |
479 if args: | 542 if args: |
480 parser.error('Unsupported args %s' % ' '.join(args)) | 543 parser.error('Unsupported args %s' % ' '.join(args)) |
481 | 544 |
482 if options.hash: | 545 if options.hash: |
483 # First calculate the reference to it. | 546 # First calculate the reference to it. |
484 options.manifest = '%s/%s' % (options.remote.rstrip('/'), options.hash) | 547 options.manifest = '%s/%s' % (options.remote.rstrip('/'), options.hash) |
485 try: | 548 try: |
486 manifest = json.load(open_remote(options.manifest)) | 549 manifest = load_manifest(open_remote(options.manifest).read()) |
487 except IOError as e: | 550 except IOError as e: |
488 parser.error( | 551 parser.error( |
489 'Failed to read manifest %s; remote:%s; hash:%s; %s' % | 552 'Failed to read manifest %s; remote:%s; hash:%s; %s' % |
490 (options.manifest, options.remote, options.hash, str(e))) | 553 (options.manifest, options.remote, options.hash, str(e))) |
491 | 554 |
492 policies = CachePolicies( | 555 policies = CachePolicies( |
493 options.max_cache_size, options.min_free_space, options.max_items) | 556 options.max_cache_size, options.min_free_space, options.max_items) |
494 try: | 557 try: |
495 return run_tha_test( | 558 return run_tha_test( |
496 manifest, | 559 manifest, |
497 os.path.abspath(options.cache), | 560 os.path.abspath(options.cache), |
498 options.remote, | 561 options.remote, |
499 policies) | 562 policies) |
500 except MappingError, e: | 563 except (ConfigError, MappingError), e: |
501 print >> sys.stderr, str(e) | 564 print >> sys.stderr, str(e) |
502 return 1 | 565 return 1 |
503 | 566 |
504 | 567 |
505 if __name__ == '__main__': | 568 if __name__ == '__main__': |
506 sys.exit(main()) | 569 sys.exit(main()) |
OLD | NEW |