OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
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 | |
4 # found in the LICENSE file. | |
5 | |
6 import cStringIO | |
7 import hashlib | |
8 import json | |
9 import logging | |
10 import os | |
11 import re | |
12 import shutil | |
13 import stat | |
14 import subprocess | |
15 import sys | |
16 import tempfile | |
17 import unittest | |
18 | |
19 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
20 sys.path.insert(0, ROOT_DIR) | |
21 | |
22 import isolate | |
23 import run_test_from_archive | |
24 | |
25 VERBOSE = False | |
26 SHA_1_NULL = hashlib.sha1().hexdigest() | |
27 | |
28 | |
29 # Keep the list hard coded. | |
30 EXPECTED_MODES = ( | |
31 'check', | |
32 'hashtable', | |
33 'help', | |
34 'noop', | |
35 'merge', | |
36 'read', | |
37 'remap', | |
38 'run', | |
39 'trace', | |
40 ) | |
41 | |
42 # These are per test case, not per mode. | |
43 RELATIVE_CWD = { | |
44 'fail': '.', | |
45 'missing_trailing_slash': '.', | |
46 'no_run': '.', | |
47 'non_existent': '.', | |
48 'symlink_full': '.', | |
49 'symlink_partial': '.', | |
50 'touch_only': '.', | |
51 'touch_root': os.path.join('tests', 'isolate'), | |
52 'with_flag': '.', | |
53 } | |
54 | |
55 DEPENDENCIES = { | |
56 'fail': ['fail.py'], | |
57 'missing_trailing_slash': [], | |
58 'no_run': [ | |
59 'no_run.isolate', | |
60 os.path.join('files1', 'subdir', '42.txt'), | |
61 os.path.join('files1', 'test_file1.txt'), | |
62 os.path.join('files1', 'test_file2.txt'), | |
63 ], | |
64 'non_existent': [], | |
65 'symlink_full': [ | |
66 os.path.join('files1', 'subdir', '42.txt'), | |
67 os.path.join('files1', 'test_file1.txt'), | |
68 os.path.join('files1', 'test_file2.txt'), | |
69 # files2 is a symlink to files1. | |
70 'files2', | |
71 'symlink_full.py', | |
72 ], | |
73 'symlink_partial': [ | |
74 os.path.join('files1', 'test_file2.txt'), | |
75 # files2 is a symlink to files1. | |
76 'files2', | |
77 'symlink_partial.py', | |
78 ], | |
79 'touch_only': [ | |
80 'touch_only.py', | |
81 os.path.join('files1', 'test_file1.txt'), | |
82 ], | |
83 'touch_root': [ | |
84 os.path.join('tests', 'isolate', 'touch_root.py'), | |
85 'isolate.py', | |
86 ], | |
87 'with_flag': [ | |
88 'with_flag.py', | |
89 os.path.join('files1', 'subdir', '42.txt'), | |
90 os.path.join('files1', 'test_file1.txt'), | |
91 os.path.join('files1', 'test_file2.txt'), | |
92 ], | |
93 } | |
94 | |
95 | |
96 class CalledProcessError(subprocess.CalledProcessError): | |
97 """Makes 2.6 version act like 2.7""" | |
98 def __init__(self, returncode, cmd, output, stderr, cwd): | |
99 super(CalledProcessError, self).__init__(returncode, cmd) | |
100 self.output = output | |
101 self.stderr = stderr | |
102 self.cwd = cwd | |
103 | |
104 def __str__(self): | |
105 return super(CalledProcessError, self).__str__() + ( | |
106 '\n' | |
107 'cwd=%s\n%s\n%s\n%s') % ( | |
108 self.cwd, | |
109 self.output, | |
110 self.stderr, | |
111 ' '.join(self.cmd)) | |
112 | |
113 | |
114 def list_files_tree(directory): | |
115 """Returns the list of all the files in a tree.""" | |
116 actual = [] | |
117 for root, dirnames, filenames in os.walk(directory): | |
118 actual.extend(os.path.join(root, f)[len(directory)+1:] for f in filenames) | |
119 for dirname in dirnames: | |
120 full = os.path.join(root, dirname) | |
121 # Manually include symlinks. | |
122 if os.path.islink(full): | |
123 actual.append(full[len(directory)+1:]) | |
124 return sorted(actual) | |
125 | |
126 | |
127 def calc_sha1(filepath): | |
128 """Calculates the SHA-1 hash for a file.""" | |
129 return hashlib.sha1(open(filepath, 'rb').read()).hexdigest() | |
130 | |
131 | |
132 class IsolateBase(unittest.TestCase): | |
133 # To be defined by the subclass, it defines the amount of meta data saved by | |
134 # isolate.py for each file. Should be one of (NO_INFO, STATS_ONLY, WITH_HASH). | |
135 LEVEL = None | |
136 | |
137 def setUp(self): | |
138 # The tests assume the current directory is the file's directory. | |
139 os.chdir(ROOT_DIR) | |
140 self.tempdir = tempfile.mkdtemp() | |
141 self.result = os.path.join(self.tempdir, 'isolate_smoke_test.results') | |
142 self.outdir = os.path.join(self.tempdir, 'isolated') | |
143 | |
144 def tearDown(self): | |
145 logging.debug(self.tempdir) | |
146 shutil.rmtree(self.tempdir) | |
147 | |
148 @staticmethod | |
149 def _isolate_dict_to_string(values): | |
150 buf = cStringIO.StringIO() | |
151 isolate.pretty_print(values, buf) | |
152 return buf.getvalue() | |
153 | |
154 @classmethod | |
155 def _wrap_in_condition(cls, variables): | |
156 """Wraps a variables dict inside the current OS condition. | |
157 | |
158 Returns the equivalent string. | |
159 """ | |
160 return cls._isolate_dict_to_string( | |
161 { | |
162 'conditions': [ | |
163 ['OS=="%s"' % isolate.get_flavor(), { | |
164 'variables': variables | |
165 }], | |
166 ], | |
167 }) | |
168 | |
169 | |
170 class IsolateModeBase(IsolateBase): | |
171 def _expect_no_tree(self): | |
172 self.assertFalse(os.path.exists(self.outdir)) | |
173 | |
174 def _result_tree(self): | |
175 return list_files_tree(self.outdir) | |
176 | |
177 def _expected_tree(self): | |
178 """Verifies the files written in the temporary directory.""" | |
179 self.assertEquals(sorted(DEPENDENCIES[self.case()]), self._result_tree()) | |
180 | |
181 @staticmethod | |
182 def _fix_file_mode(filename, read_only): | |
183 """4 modes are supported, 0750 (rwx), 0640 (rw), 0550 (rx), 0440 (r).""" | |
184 min_mode = 0440 | |
185 if not read_only: | |
186 min_mode |= 0200 | |
187 return (min_mode | 0110) if filename.endswith('.py') else min_mode | |
188 | |
189 def _gen_files(self, read_only, empty_file): | |
190 """Returns a dict of files like calling isolate.process_input() on each | |
191 file. | |
192 """ | |
193 root_dir = ROOT_DIR | |
194 if RELATIVE_CWD[self.case()] == '.': | |
195 root_dir = os.path.join(root_dir, 'tests', 'isolate') | |
196 | |
197 files = dict((unicode(f), {}) for f in DEPENDENCIES[self.case()]) | |
198 | |
199 for relfile, v in files.iteritems(): | |
200 filepath = os.path.join(root_dir, relfile) | |
201 if self.LEVEL >= isolate.STATS_ONLY: | |
202 filestats = os.lstat(filepath) | |
203 is_link = stat.S_ISLNK(filestats.st_mode) | |
204 if not is_link: | |
205 v[u'size'] = filestats.st_size | |
206 if isolate.get_flavor() != 'win': | |
207 v[u'mode'] = self._fix_file_mode(relfile, read_only) | |
208 else: | |
209 v[u'mode'] = 488 | |
210 # Used the skip recalculating the hash. Use the most recent update | |
211 # time. | |
212 v[u'timestamp'] = int(round(filestats.st_mtime)) | |
213 if is_link: | |
214 v['link'] = os.readlink(filepath) | |
215 | |
216 if self.LEVEL >= isolate.WITH_HASH: | |
217 if not is_link: | |
218 # Upgrade the value to unicode so diffing the structure in case of | |
219 # test failure is easier, since the basestring type must match, | |
220 # str!=unicode. | |
221 v[u'sha-1'] = unicode(calc_sha1(filepath)) | |
222 | |
223 if empty_file: | |
224 item = files[empty_file] | |
225 item['sha-1'] = unicode(SHA_1_NULL) | |
226 if sys.platform != 'win32': | |
227 item['mode'] = 288 | |
228 item['size'] = 0 | |
229 item['touched_only'] = True | |
230 item.pop('timestamp', None) | |
231 return files | |
232 | |
233 def _expected_result(self, args, read_only, empty_file): | |
234 """Verifies self.result contains the expected data.""" | |
235 expected = { | |
236 u'files': self._gen_files(read_only, empty_file), | |
237 u'os': isolate.get_flavor(), | |
238 u'relative_cwd': unicode(RELATIVE_CWD[self.case()]), | |
239 } | |
240 if read_only is not None: | |
241 expected[u'read_only'] = read_only | |
242 if args: | |
243 expected[u'command'] = [u'python'] + [unicode(x) for x in args] | |
244 else: | |
245 expected[u'command'] = [] | |
246 self.assertEquals(expected, json.load(open(self.result, 'r'))) | |
247 | |
248 def _expected_saved_state(self, extra_vars): | |
249 flavor = isolate.get_flavor() | |
250 expected = { | |
251 u'isolate_file': unicode(self.filename()), | |
252 u'variables': { | |
253 u'EXECUTABLE_SUFFIX': '.exe' if flavor == 'win' else '', | |
254 u'OS': unicode(flavor), | |
255 }, | |
256 } | |
257 expected['variables'].update(extra_vars or {}) | |
258 self.assertEquals(expected, json.load(open(self.saved_state(), 'r'))) | |
259 | |
260 def _expect_results(self, args, read_only, extra_vars, empty_file): | |
261 self._expected_result(args, read_only, empty_file) | |
262 self._expected_saved_state(extra_vars) | |
263 # Also verifies run_test_from_archive.py will be able to read it. | |
264 run_test_from_archive.load_manifest(open(self.result, 'r').read()) | |
265 | |
266 def _expect_no_result(self): | |
267 self.assertFalse(os.path.exists(self.result)) | |
268 | |
269 def _execute(self, mode, case, args, need_output): | |
270 """Executes isolate.py.""" | |
271 self.assertEquals( | |
272 case, | |
273 self.case() + '.isolate', | |
274 'Rename the test case to test_%s()' % case) | |
275 cmd = [ | |
276 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), | |
277 mode, | |
278 '--result', self.result, | |
279 '--outdir', self.outdir, | |
280 '--isolate', self.filename(), | |
281 ] | |
282 cmd.extend(args) | |
283 | |
284 env = os.environ.copy() | |
285 if 'ISOLATE_DEBUG' in env: | |
286 del env['ISOLATE_DEBUG'] | |
287 | |
288 if need_output or not VERBOSE: | |
289 stdout = subprocess.PIPE | |
290 stderr = subprocess.PIPE | |
291 else: | |
292 cmd.extend(['-v'] * 3) | |
293 stdout = None | |
294 stderr = None | |
295 | |
296 logging.debug(cmd) | |
297 cwd = ROOT_DIR | |
298 p = subprocess.Popen( | |
299 cmd, | |
300 stdout=stdout, | |
301 stderr=stderr, | |
302 cwd=cwd, | |
303 env=env, | |
304 universal_newlines=True) | |
305 out, err = p.communicate() | |
306 if p.returncode: | |
307 raise CalledProcessError(p.returncode, cmd, out, err, cwd) | |
308 | |
309 # Do not check on Windows since a lot of spew is generated there. | |
310 if sys.platform != 'win32': | |
311 self.assertTrue(err in (None, ''), err) | |
312 return out | |
313 | |
314 def case(self): | |
315 """Returns the filename corresponding to this test case.""" | |
316 test_id = self.id().split('.') | |
317 return re.match('^test_([a-z_]+)$', test_id[2]).group(1) | |
318 | |
319 def filename(self): | |
320 """Returns the filename corresponding to this test case.""" | |
321 filename = os.path.join( | |
322 ROOT_DIR, 'tests', 'isolate', self.case() + '.isolate') | |
323 self.assertTrue(os.path.isfile(filename), filename) | |
324 return filename | |
325 | |
326 def saved_state(self): | |
327 return isolate.result_to_state(self.result) | |
328 | |
329 | |
330 class Isolate(unittest.TestCase): | |
331 # Does not inherit from the other *Base classes. | |
332 def test_help_modes(self): | |
333 # Check coherency in the help and implemented modes. | |
334 p = subprocess.Popen( | |
335 [sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), '--help'], | |
336 stdout=subprocess.PIPE, | |
337 stderr=subprocess.STDOUT, | |
338 cwd=ROOT_DIR) | |
339 out = p.communicate()[0].splitlines() | |
340 self.assertEquals(0, p.returncode) | |
341 out = out[out.index('') + 1:] | |
342 out = out[:out.index('')] | |
343 modes = [re.match(r'^ (\w+) .+', l) for l in out] | |
344 modes = tuple(m.group(1) for m in modes if m) | |
345 # noop doesn't do anything so no point in testing it. | |
346 self.assertEquals(sorted(EXPECTED_MODES), sorted(modes)) | |
347 | |
348 def test_modes(self): | |
349 # This is a bit redundant but make sure all combinations are tested. | |
350 files = sorted( | |
351 i[:-len('.isolate')] | |
352 for i in os.listdir(os.path.join(ROOT_DIR, 'tests', 'isolate')) | |
353 if i.endswith('.isolate') | |
354 ) | |
355 self.assertEquals(sorted(RELATIVE_CWD), files) | |
356 self.assertEquals(sorted(DEPENDENCIES), files) | |
357 | |
358 if sys.platform == 'win32': | |
359 # Symlink stuff is unsupported there, remove them from the list. | |
360 files = [f for f in files if not f.startswith('symlink_')] | |
361 | |
362 # TODO(csharp): touched_only is disabled until crbug.com/150823 is fixed. | |
363 files.remove('touch_only') | |
364 | |
365 # modes read and trace are tested together. | |
366 modes_to_check = list(EXPECTED_MODES) | |
367 modes_to_check.remove('help') | |
368 modes_to_check.remove('merge') | |
369 modes_to_check.remove('noop') | |
370 modes_to_check.remove('read') | |
371 modes_to_check.remove('trace') | |
372 modes_to_check.append('trace_read_merge') | |
373 for mode in modes_to_check: | |
374 expected_cases = set('test_%s' % f for f in files) | |
375 fixture_name = 'Isolate_%s' % mode | |
376 fixture = getattr(sys.modules[__name__], fixture_name) | |
377 actual_cases = set(i for i in dir(fixture) if i.startswith('test_')) | |
378 missing = expected_cases - actual_cases | |
379 self.assertFalse(missing, '%s.%s' % (fixture_name, missing)) | |
380 | |
381 | |
382 class Isolate_check(IsolateModeBase): | |
383 LEVEL = isolate.NO_INFO | |
384 | |
385 def test_fail(self): | |
386 self._execute('check', 'fail.isolate', [], False) | |
387 self._expect_no_tree() | |
388 self._expect_results(['fail.py'], None, None, None) | |
389 | |
390 def test_missing_trailing_slash(self): | |
391 try: | |
392 self._execute('check', 'missing_trailing_slash.isolate', [], False) | |
393 self.fail() | |
394 except subprocess.CalledProcessError: | |
395 pass | |
396 self._expect_no_tree() | |
397 self._expect_no_result() | |
398 | |
399 def test_non_existent(self): | |
400 try: | |
401 self._execute('check', 'non_existent.isolate', [], False) | |
402 self.fail() | |
403 except subprocess.CalledProcessError: | |
404 pass | |
405 self._expect_no_tree() | |
406 self._expect_no_result() | |
407 | |
408 def test_no_run(self): | |
409 self._execute('check', 'no_run.isolate', [], False) | |
410 self._expect_no_tree() | |
411 self._expect_results([], None, None, None) | |
412 | |
413 # TODO(csharp): Disabled until crbug.com/150823 is fixed. | |
414 def do_not_test_touch_only(self): | |
415 self._execute('check', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) | |
416 self._expect_no_tree() | |
417 empty = os.path.join('files1', 'test_file1.txt') | |
418 self._expected_result(['touch_only.py', 'gyp'], None, empty) | |
419 | |
420 def test_touch_root(self): | |
421 self._execute('check', 'touch_root.isolate', [], False) | |
422 self._expect_no_tree() | |
423 self._expect_results(['touch_root.py'], None, None, None) | |
424 | |
425 def test_with_flag(self): | |
426 self._execute('check', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) | |
427 self._expect_no_tree() | |
428 self._expect_results( | |
429 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) | |
430 | |
431 if sys.platform != 'win32': | |
432 def test_symlink_full(self): | |
433 self._execute('check', 'symlink_full.isolate', [], False) | |
434 self._expect_no_tree() | |
435 self._expect_results(['symlink_full.py'], None, None, None) | |
436 | |
437 def test_symlink_partial(self): | |
438 self._execute('check', 'symlink_partial.isolate', [], False) | |
439 self._expect_no_tree() | |
440 self._expect_results(['symlink_partial.py'], None, None, None) | |
441 | |
442 | |
443 class Isolate_hashtable(IsolateModeBase): | |
444 LEVEL = isolate.WITH_HASH | |
445 | |
446 def _gen_expected_tree(self, empty_file): | |
447 expected = [ | |
448 v['sha-1'] for v in self._gen_files(False, empty_file).itervalues() | |
449 ] | |
450 expected.append(calc_sha1(self.result)) | |
451 return expected | |
452 | |
453 def _expected_hash_tree(self, empty_file): | |
454 """Verifies the files written in the temporary directory.""" | |
455 self.assertEquals( | |
456 sorted(self._gen_expected_tree(empty_file)), self._result_tree()) | |
457 | |
458 def test_fail(self): | |
459 self._execute('hashtable', 'fail.isolate', [], False) | |
460 self._expected_hash_tree(None) | |
461 self._expect_results(['fail.py'], None, None, None) | |
462 | |
463 def test_missing_trailing_slash(self): | |
464 try: | |
465 self._execute('hashtable', 'missing_trailing_slash.isolate', [], False) | |
466 self.fail() | |
467 except subprocess.CalledProcessError: | |
468 pass | |
469 self._expect_no_tree() | |
470 self._expect_no_result() | |
471 | |
472 def test_non_existent(self): | |
473 try: | |
474 self._execute('hashtable', 'non_existent.isolate', [], False) | |
475 self.fail() | |
476 except subprocess.CalledProcessError: | |
477 pass | |
478 self._expect_no_tree() | |
479 self._expect_no_result() | |
480 | |
481 def test_no_run(self): | |
482 self._execute('hashtable', 'no_run.isolate', [], False) | |
483 self._expected_hash_tree(None) | |
484 self._expect_results([], None, None, None) | |
485 | |
486 # TODO(csharp): Disabled until crbug.com/150823 is fixed. | |
487 def do_not_test_touch_only(self): | |
488 self._execute( | |
489 'hashtable', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) | |
490 empty = os.path.join('files1', 'test_file1.txt') | |
491 self._expected_hash_tree(empty) | |
492 self._expected_result(['touch_only.py', 'gyp'], None, empty) | |
493 | |
494 def test_touch_root(self): | |
495 self._execute('hashtable', 'touch_root.isolate', [], False) | |
496 self._expected_hash_tree(None) | |
497 self._expect_results(['touch_root.py'], None, None, None) | |
498 | |
499 def test_with_flag(self): | |
500 self._execute( | |
501 'hashtable', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) | |
502 self._expected_hash_tree(None) | |
503 self._expect_results( | |
504 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) | |
505 | |
506 if sys.platform != 'win32': | |
507 def test_symlink_full(self): | |
508 self._execute('hashtable', 'symlink_full.isolate', [], False) | |
509 # Construct our own tree. | |
510 expected = [ | |
511 str(v['sha-1']) | |
512 for v in self._gen_files(False, None).itervalues() if 'sha-1' in v | |
513 ] | |
514 expected.append(calc_sha1(self.result)) | |
515 self.assertEquals(sorted(expected), self._result_tree()) | |
516 self._expect_results(['symlink_full.py'], None, None, None) | |
517 | |
518 def test_symlink_partial(self): | |
519 self._execute('hashtable', 'symlink_partial.isolate', [], False) | |
520 # Construct our own tree. | |
521 expected = [ | |
522 str(v['sha-1']) | |
523 for v in self._gen_files(False, None).itervalues() if 'sha-1' in v | |
524 ] | |
525 expected.append(calc_sha1(self.result)) | |
526 self.assertEquals(sorted(expected), self._result_tree()) | |
527 self._expect_results(['symlink_partial.py'], None, None, None) | |
528 | |
529 | |
530 | |
531 class Isolate_remap(IsolateModeBase): | |
532 LEVEL = isolate.STATS_ONLY | |
533 | |
534 def test_fail(self): | |
535 self._execute('remap', 'fail.isolate', [], False) | |
536 self._expected_tree() | |
537 self._expect_results(['fail.py'], None, None, None) | |
538 | |
539 def test_missing_trailing_slash(self): | |
540 try: | |
541 self._execute('remap', 'missing_trailing_slash.isolate', [], False) | |
542 self.fail() | |
543 except subprocess.CalledProcessError: | |
544 pass | |
545 self._expect_no_tree() | |
546 self._expect_no_result() | |
547 | |
548 def test_non_existent(self): | |
549 try: | |
550 self._execute('remap', 'non_existent.isolate', [], False) | |
551 self.fail() | |
552 except subprocess.CalledProcessError: | |
553 pass | |
554 self._expect_no_tree() | |
555 self._expect_no_result() | |
556 | |
557 def test_no_run(self): | |
558 self._execute('remap', 'no_run.isolate', [], False) | |
559 self._expected_tree() | |
560 self._expect_results([], None, None, None) | |
561 | |
562 # TODO(csharp): Disabled until crbug.com/150823 is fixed. | |
563 def do_not_test_touch_only(self): | |
564 self._execute('remap', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) | |
565 self._expected_tree() | |
566 empty = os.path.join('files1', 'test_file1.txt') | |
567 self._expect_results( | |
568 ['touch_only.py', 'gyp'], None, {u'FLAG': u'gyp'}, empty) | |
569 | |
570 def test_touch_root(self): | |
571 self._execute('remap', 'touch_root.isolate', [], False) | |
572 self._expected_tree() | |
573 self._expect_results(['touch_root.py'], None, None, None) | |
574 | |
575 def test_with_flag(self): | |
576 self._execute('remap', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) | |
577 self._expected_tree() | |
578 self._expect_results( | |
579 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) | |
580 | |
581 if sys.platform != 'win32': | |
582 def test_symlink_full(self): | |
583 self._execute('remap', 'symlink_full.isolate', [], False) | |
584 self._expected_tree() | |
585 self._expect_results(['symlink_full.py'], None, None, None) | |
586 | |
587 def test_symlink_partial(self): | |
588 self._execute('remap', 'symlink_partial.isolate', [], False) | |
589 self._expected_tree() | |
590 self._expect_results(['symlink_partial.py'], None, None, None) | |
591 | |
592 | |
593 class Isolate_run(IsolateModeBase): | |
594 LEVEL = isolate.STATS_ONLY | |
595 | |
596 def _expect_empty_tree(self): | |
597 self.assertEquals([], self._result_tree()) | |
598 | |
599 def test_fail(self): | |
600 try: | |
601 self._execute('run', 'fail.isolate', [], False) | |
602 self.fail() | |
603 except subprocess.CalledProcessError: | |
604 pass | |
605 self._expect_empty_tree() | |
606 self._expect_results(['fail.py'], None, None, None) | |
607 | |
608 def test_missing_trailing_slash(self): | |
609 try: | |
610 self._execute('run', 'missing_trailing_slash.isolate', [], False) | |
611 self.fail() | |
612 except subprocess.CalledProcessError: | |
613 pass | |
614 self._expect_no_tree() | |
615 self._expect_no_result() | |
616 | |
617 def test_non_existent(self): | |
618 try: | |
619 self._execute('run', 'non_existent.isolate', [], False) | |
620 self.fail() | |
621 except subprocess.CalledProcessError: | |
622 pass | |
623 self._expect_no_tree() | |
624 self._expect_no_result() | |
625 | |
626 def test_no_run(self): | |
627 try: | |
628 self._execute('run', 'no_run.isolate', [], False) | |
629 self.fail() | |
630 except subprocess.CalledProcessError: | |
631 pass | |
632 self._expect_empty_tree() | |
633 self._expect_no_result() | |
634 | |
635 # TODO(csharp): Disabled until crbug.com/150823 is fixed. | |
636 def do_not_test_touch_only(self): | |
637 self._execute('run', 'touch_only.isolate', ['-V', 'FLAG', 'run'], False) | |
638 self._expect_empty_tree() | |
639 empty = os.path.join('files1', 'test_file1.txt') | |
640 self._expect_results( | |
641 ['touch_only.py', 'run'], None, {u'FLAG': u'run'}, empty) | |
642 | |
643 def test_touch_root(self): | |
644 self._execute('run', 'touch_root.isolate', [], False) | |
645 self._expect_empty_tree() | |
646 self._expect_results(['touch_root.py'], None, None, None) | |
647 | |
648 def test_with_flag(self): | |
649 self._execute('run', 'with_flag.isolate', ['-V', 'FLAG', 'run'], False) | |
650 # Not sure about the empty tree, should be deleted. | |
651 self._expect_empty_tree() | |
652 self._expect_results( | |
653 ['with_flag.py', 'run'], None, {u'FLAG': u'run'}, None) | |
654 | |
655 if sys.platform != 'win32': | |
656 def test_symlink_full(self): | |
657 self._execute('run', 'symlink_full.isolate', [], False) | |
658 self._expect_empty_tree() | |
659 self._expect_results(['symlink_full.py'], None, None, None) | |
660 | |
661 def test_symlink_partial(self): | |
662 self._execute('run', 'symlink_partial.isolate', [], False) | |
663 self._expect_empty_tree() | |
664 self._expect_results(['symlink_partial.py'], None, None, None) | |
665 | |
666 | |
667 class Isolate_trace_read_merge(IsolateModeBase): | |
668 # Tests both trace, read and merge. | |
669 # Warning: merge updates .isolate files. But they are currently in their | |
670 # canonical format so they shouldn't be changed. | |
671 LEVEL = isolate.STATS_ONLY | |
672 | |
673 def _check_merge(self, filename): | |
674 filepath = isolate.trace_inputs.get_native_path_case( | |
675 os.path.join(ROOT_DIR, 'tests', 'isolate', filename)) | |
676 expected = 'Updating %s\n' % filepath | |
677 with open(filepath, 'rb') as f: | |
678 old_content = f.read() | |
679 out = self._execute('merge', filename, [], True) or '' | |
680 self.assertEquals(expected, out) | |
681 with open(filepath, 'rb') as f: | |
682 new_content = f.read() | |
683 self.assertEquals(old_content, new_content) | |
684 | |
685 def test_fail(self): | |
686 # Even if the process returns non-zero, the trace will still be good. | |
687 try: | |
688 self._execute('trace', 'fail.isolate', ['-v'], True) | |
689 self.fail() | |
690 except subprocess.CalledProcessError, e: | |
691 self.assertEquals('', e.output) | |
692 self._expect_no_tree() | |
693 self._expect_results(['fail.py'], None, None, None) | |
694 expected = self._wrap_in_condition( | |
695 { | |
696 isolate.KEY_TRACKED: [ | |
697 'fail.py', | |
698 ], | |
699 }) | |
700 out = self._execute('read', 'fail.isolate', [], True) or '' | |
701 self.assertEquals(expected.splitlines(), out.splitlines()) | |
702 self._check_merge('fail.isolate') | |
703 | |
704 def test_missing_trailing_slash(self): | |
705 try: | |
706 self._execute('trace', 'missing_trailing_slash.isolate', [], True) | |
707 self.fail() | |
708 except subprocess.CalledProcessError, e: | |
709 self.assertEquals('', e.output) | |
710 out = e.stderr | |
711 self._expect_no_tree() | |
712 self._expect_no_result() | |
713 expected = ( | |
714 '\n' | |
715 'Error: Input directory %s must have a trailing slash\n' % | |
716 os.path.join(ROOT_DIR, 'tests', 'isolate', 'files1') | |
717 ) | |
718 self.assertEquals(expected, out) | |
719 | |
720 def test_non_existent(self): | |
721 try: | |
722 self._execute('trace', 'non_existent.isolate', [], True) | |
723 self.fail() | |
724 except subprocess.CalledProcessError, e: | |
725 self.assertEquals('', e.output) | |
726 out = e.stderr | |
727 self._expect_no_tree() | |
728 self._expect_no_result() | |
729 expected = ( | |
730 '\n' | |
731 'Error: Input file %s doesn\'t exist\n' % | |
732 os.path.join(ROOT_DIR, 'tests', 'isolate', 'A_file_that_do_not_exist') | |
733 ) | |
734 self.assertEquals(expected, out) | |
735 | |
736 def test_no_run(self): | |
737 try: | |
738 self._execute('trace', 'no_run.isolate', [], True) | |
739 self.fail() | |
740 except subprocess.CalledProcessError, e: | |
741 out = e.output | |
742 err = e.stderr | |
743 self._expect_no_tree() | |
744 self._expect_no_result() | |
745 expected = '\nError: No command to run\n' | |
746 self.assertEquals('', out) | |
747 self.assertEquals(expected, err) | |
748 | |
749 # TODO(csharp): Disabled until crbug.com/150823 is fixed. | |
750 def do_not_test_touch_only(self): | |
751 out = self._execute( | |
752 'trace', 'touch_only.isolate', ['-V', 'FLAG', 'trace'], True) | |
753 self.assertEquals('', out) | |
754 self._expect_no_tree() | |
755 empty = os.path.join('files1', 'test_file1.txt') | |
756 self._expect_results( | |
757 ['touch_only.py', 'trace'], None, {u'FLAG': u'trace'}, empty) | |
758 expected = { | |
759 isolate.KEY_TRACKED: [ | |
760 'touch_only.py', | |
761 ], | |
762 isolate.KEY_TOUCHED: [ | |
763 # Note that .isolate format mandates / and not os.path.sep. | |
764 'files1/test_file1.txt', | |
765 ], | |
766 } | |
767 if sys.platform != 'linux2': | |
768 # TODO(maruel): Implement touch-only tracing on non-linux. | |
769 del expected[isolate.KEY_TOUCHED] | |
770 | |
771 out = self._execute('read', 'touch_only.isolate', [], True) | |
772 self.assertEquals(self._wrap_in_condition(expected), out) | |
773 self._check_merge('touch_only.isolate') | |
774 | |
775 def test_touch_root(self): | |
776 out = self._execute('trace', 'touch_root.isolate', [], True) | |
777 self.assertEquals('', out) | |
778 self._expect_no_tree() | |
779 self._expect_results(['touch_root.py'], None, None, None) | |
780 expected = self._wrap_in_condition( | |
781 { | |
782 isolate.KEY_TRACKED: [ | |
783 '../../isolate.py', | |
784 'touch_root.py', | |
785 ], | |
786 }) | |
787 out = self._execute('read', 'touch_root.isolate', [], True) | |
788 self.assertEquals(expected, out) | |
789 self._check_merge('touch_root.isolate') | |
790 | |
791 def test_with_flag(self): | |
792 out = self._execute( | |
793 'trace', 'with_flag.isolate', ['-V', 'FLAG', 'trace'], True) | |
794 self.assertEquals('', out) | |
795 self._expect_no_tree() | |
796 self._expect_results( | |
797 ['with_flag.py', 'trace'], None, {u'FLAG': u'trace'}, None) | |
798 expected = { | |
799 isolate.KEY_TRACKED: [ | |
800 'with_flag.py', | |
801 ], | |
802 isolate.KEY_UNTRACKED: [ | |
803 # Note that .isolate format mandates / and not os.path.sep. | |
804 'files1/', | |
805 ], | |
806 } | |
807 out = self._execute('read', 'with_flag.isolate', [], True) | |
808 self.assertEquals(self._wrap_in_condition(expected), out) | |
809 self._check_merge('with_flag.isolate') | |
810 | |
811 if sys.platform != 'win32': | |
812 def test_symlink_full(self): | |
813 out = self._execute( | |
814 'trace', 'symlink_full.isolate', [], True) | |
815 self.assertEquals('', out) | |
816 self._expect_no_tree() | |
817 self._expect_results(['symlink_full.py'], None, None, None) | |
818 expected = { | |
819 isolate.KEY_TRACKED: [ | |
820 'symlink_full.py', | |
821 ], | |
822 isolate.KEY_UNTRACKED: [ | |
823 # Note that .isolate format mandates / and not os.path.sep. | |
824 'files2/', | |
825 ], | |
826 } | |
827 out = self._execute('read', 'symlink_full.isolate', [], True) | |
828 self.assertEquals(self._wrap_in_condition(expected), out) | |
829 self._check_merge('symlink_full.isolate') | |
830 | |
831 def test_symlink_partial(self): | |
832 out = self._execute( | |
833 'trace', 'symlink_partial.isolate', [], True) | |
834 self.assertEquals('', out) | |
835 self._expect_no_tree() | |
836 self._expect_results(['symlink_partial.py'], None, None, None) | |
837 expected = { | |
838 isolate.KEY_TRACKED: [ | |
839 'symlink_partial.py', | |
840 ], | |
841 isolate.KEY_UNTRACKED: [ | |
842 'files2/test_file2.txt', | |
843 ], | |
844 } | |
845 out = self._execute('read', 'symlink_partial.isolate', [], True) | |
846 self.assertEquals(self._wrap_in_condition(expected), out) | |
847 self._check_merge('symlink_partial.isolate') | |
848 | |
849 | |
850 class IsolateNoOutdir(IsolateBase): | |
851 # Test without the --outdir flag. | |
852 # So all the files are first copied in the tempdir and the test is run from | |
853 # there. | |
854 def setUp(self): | |
855 super(IsolateNoOutdir, self).setUp() | |
856 self.root = os.path.join(self.tempdir, 'root') | |
857 os.makedirs(os.path.join(self.root, 'tests', 'isolate')) | |
858 for i in ('touch_root.isolate', 'touch_root.py'): | |
859 shutil.copy( | |
860 os.path.join(ROOT_DIR, 'tests', 'isolate', i), | |
861 os.path.join(self.root, 'tests', 'isolate', i)) | |
862 shutil.copy( | |
863 os.path.join(ROOT_DIR, 'isolate.py'), | |
864 os.path.join(self.root, 'isolate.py')) | |
865 | |
866 def _execute(self, mode, args, need_output): | |
867 """Executes isolate.py.""" | |
868 cmd = [ | |
869 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), | |
870 mode, | |
871 '--result', self.result, | |
872 ] | |
873 cmd.extend(args) | |
874 | |
875 env = os.environ.copy() | |
876 if 'ISOLATE_DEBUG' in env: | |
877 del env['ISOLATE_DEBUG'] | |
878 | |
879 if need_output or not VERBOSE: | |
880 stdout = subprocess.PIPE | |
881 stderr = subprocess.STDOUT | |
882 else: | |
883 cmd.extend(['-v'] * 3) | |
884 stdout = None | |
885 stderr = None | |
886 | |
887 logging.debug(cmd) | |
888 cwd = self.tempdir | |
889 p = subprocess.Popen( | |
890 cmd, | |
891 stdout=stdout, | |
892 stderr=stderr, | |
893 cwd=cwd, | |
894 env=env, | |
895 universal_newlines=True) | |
896 out, err = p.communicate() | |
897 if p.returncode: | |
898 raise CalledProcessError(p.returncode, cmd, out, err, cwd) | |
899 return out | |
900 | |
901 def mode(self): | |
902 """Returns the execution mode corresponding to this test case.""" | |
903 test_id = self.id().split('.') | |
904 self.assertEquals(3, len(test_id)) | |
905 self.assertEquals('__main__', test_id[0]) | |
906 return re.match('^test_([a-z]+)$', test_id[2]).group(1) | |
907 | |
908 def filename(self): | |
909 """Returns the filename corresponding to this test case.""" | |
910 filename = os.path.join(self.root, 'tests', 'isolate', 'touch_root.isolate') | |
911 self.assertTrue(os.path.isfile(filename), filename) | |
912 return filename | |
913 | |
914 def test_check(self): | |
915 self._execute('check', ['--isolate', self.filename()], False) | |
916 files = sorted([ | |
917 'isolate_smoke_test.results', | |
918 'isolate_smoke_test.state', | |
919 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), | |
920 os.path.join('root', 'tests', 'isolate', 'touch_root.py'), | |
921 os.path.join('root', 'isolate.py'), | |
922 ]) | |
923 self.assertEquals(files, list_files_tree(self.tempdir)) | |
924 | |
925 def test_hashtable(self): | |
926 self._execute('hashtable', ['--isolate', self.filename()], False) | |
927 files = sorted([ | |
928 os.path.join( | |
929 'hashtable', calc_sha1(os.path.join(ROOT_DIR, 'isolate.py'))), | |
930 os.path.join( | |
931 'hashtable', | |
932 calc_sha1( | |
933 os.path.join(ROOT_DIR, 'tests', 'isolate', 'touch_root.py'))), | |
934 os.path.join('hashtable', calc_sha1(os.path.join(self.result))), | |
935 'isolate_smoke_test.results', | |
936 'isolate_smoke_test.state', | |
937 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), | |
938 os.path.join('root', 'tests', 'isolate', 'touch_root.py'), | |
939 os.path.join('root', 'isolate.py'), | |
940 ]) | |
941 self.assertEquals(files, list_files_tree(self.tempdir)) | |
942 | |
943 def test_remap(self): | |
944 self._execute('remap', ['--isolate', self.filename()], False) | |
945 files = sorted([ | |
946 'isolate_smoke_test.results', | |
947 'isolate_smoke_test.state', | |
948 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), | |
949 os.path.join('root', 'tests', 'isolate', 'touch_root.py'), | |
950 os.path.join('root', 'isolate.py'), | |
951 ]) | |
952 self.assertEquals(files, list_files_tree(self.tempdir)) | |
953 | |
954 def test_run(self): | |
955 self._execute('run', ['--isolate', self.filename()], False) | |
956 files = sorted([ | |
957 'isolate_smoke_test.results', | |
958 'isolate_smoke_test.state', | |
959 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), | |
960 os.path.join('root', 'tests', 'isolate', 'touch_root.py'), | |
961 os.path.join('root', 'isolate.py'), | |
962 ]) | |
963 self.assertEquals(files, list_files_tree(self.tempdir)) | |
964 | |
965 def test_trace_read_merge(self): | |
966 self._execute('trace', ['--isolate', self.filename()], False) | |
967 # Read the trace before cleaning up. No need to specify self.filename() | |
968 # because add the needed information is in the .state file. | |
969 output = self._execute('read', [], True) | |
970 expected = { | |
971 isolate.KEY_TRACKED: [ | |
972 '../../isolate.py', | |
973 'touch_root.py', | |
974 ], | |
975 } | |
976 self.assertEquals(self._wrap_in_condition(expected), output) | |
977 | |
978 output = self._execute('merge', [], True) | |
979 expected = 'Updating %s\n' % isolate.trace_inputs.get_native_path_case( | |
980 os.path.join(self.root, 'tests', 'isolate', 'touch_root.isolate')) | |
981 self.assertEquals(expected, output) | |
982 # In theory the file is going to be updated but in practice its content | |
983 # won't change. | |
984 | |
985 # Clean the directory from the logs, which are OS-specific. | |
986 isolate.trace_inputs.get_api().clean_trace( | |
987 os.path.join(self.tempdir, 'isolate_smoke_test.results.log')) | |
988 files = sorted([ | |
989 'isolate_smoke_test.results', | |
990 'isolate_smoke_test.state', | |
991 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), | |
992 os.path.join('root', 'tests', 'isolate', 'touch_root.py'), | |
993 os.path.join('root', 'isolate.py'), | |
994 ]) | |
995 self.assertEquals(files, list_files_tree(self.tempdir)) | |
996 | |
997 | |
998 if __name__ == '__main__': | |
999 VERBOSE = '-v' in sys.argv | |
1000 logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR) | |
1001 unittest.main() | |
OLD | NEW |