| 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 |