| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 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 """Generate fake repositories for testing.""" | 6 """Generate fake repositories for testing.""" |
| 7 | 7 |
| 8 import atexit | 8 import atexit |
| 9 import errno | 9 import errno |
| 10 import logging | 10 import logging |
| 11 import os | 11 import os |
| 12 import pprint | 12 import pprint |
| 13 import re | 13 import re |
| 14 import stat | 14 import stat |
| 15 import subprocess | 15 import subprocess |
| 16 import sys | 16 import sys |
| 17 import time | 17 import time |
| 18 import unittest | 18 import unittest |
| 19 | 19 |
| 20 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 21 |
| 22 import scm |
| 20 | 23 |
| 21 ## Utility functions | 24 ## Utility functions |
| 22 | 25 |
| 23 | 26 |
| 24 def addKill(): | 27 def addKill(): |
| 25 """Add kill() method to subprocess.Popen for python <2.6""" | 28 """Add kill() method to subprocess.Popen for python <2.6""" |
| 26 if getattr(subprocess.Popen, 'kill', None): | 29 if getattr(subprocess.Popen, 'kill', None): |
| 27 return | 30 return |
| 28 if sys.platform == 'win32': | 31 if sys.platform == 'win32': |
| 29 def kill_win(process): | 32 def kill_win(process): |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 if k not in dict2: | 145 if k not in dict2: |
| 143 diff[k] = v | 146 diff[k] = v |
| 144 elif v != dict2[k]: | 147 elif v != dict2[k]: |
| 145 diff[k] = (v, dict2[k]) | 148 diff[k] = (v, dict2[k]) |
| 146 for k, v in dict2.iteritems(): | 149 for k, v in dict2.iteritems(): |
| 147 if k not in dict1: | 150 if k not in dict1: |
| 148 diff[k] = v | 151 diff[k] = v |
| 149 return diff | 152 return diff |
| 150 | 153 |
| 151 | 154 |
| 152 def mangle_svn_tree(*args): | |
| 153 result = {} | |
| 154 for old_root, new_root, tree in args: | |
| 155 for k, v in tree.iteritems(): | |
| 156 if not k.startswith(old_root): | |
| 157 continue | |
| 158 result[join(new_root, k[len(old_root) + 1:]).replace(os.sep, '/')] = v | |
| 159 return result | |
| 160 | |
| 161 | |
| 162 def mangle_git_tree(*args): | |
| 163 result = {} | |
| 164 for new_root, tree in args: | |
| 165 for k, v in tree.iteritems(): | |
| 166 result[join(new_root, k)] = v | |
| 167 return result | |
| 168 | |
| 169 | |
| 170 def commit_svn(repo): | 155 def commit_svn(repo): |
| 171 """Commits the changes and returns the new revision number.""" | 156 """Commits the changes and returns the new revision number.""" |
| 172 # Basic parsing. | |
| 173 to_add = [] | 157 to_add = [] |
| 174 to_remove = [] | 158 to_remove = [] |
| 175 for item in Popen(['svn', 'status'], | 159 for status, filepath in scm.SVN.CaptureStatus(repo): |
| 176 cwd=repo).communicate()[0].splitlines(False): | 160 if status[0] == '?': |
| 177 if item[0] == '?': | 161 to_add.append(filepath) |
| 178 to_add.append(item[7:].strip()) | 162 elif status[0] == '!': |
| 179 elif item[0] == '!': | 163 to_remove.append(filepath) |
| 180 to_remove.append(item[7:].strip()) | |
| 181 if to_add: | 164 if to_add: |
| 182 check_call(['svn', 'add', '--no-auto-props', '-q'] + to_add, cwd=repo) | 165 check_call(['svn', 'add', '--no-auto-props', '-q'] + to_add, cwd=repo) |
| 183 if to_remove: | 166 if to_remove: |
| 184 check_call(['svn', 'remove', '-q'] + to_remove, cwd=repo) | 167 check_call(['svn', 'remove', '-q'] + to_remove, cwd=repo) |
| 185 proc = Popen(['svn', 'commit', repo, '-m', 'foo', '--non-interactive', | 168 proc = Popen(['svn', 'commit', repo, '-m', 'foo', '--non-interactive', |
| 186 '--no-auth-cache', '--username', 'user1', '--password', 'foo'], | 169 '--no-auth-cache', '--username', 'user1', '--password', 'foo'], |
| 187 cwd=repo) | 170 cwd=repo) |
| 188 out, err = proc.communicate() | 171 out, err = proc.communicate() |
| 189 match = re.search(r'revision (\d+).', out) | 172 match = re.search(r'revision (\d+).', out) |
| 190 if not match: | 173 if not match: |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 235 self.SHOULD_LEAK = True | 218 self.SHOULD_LEAK = True |
| 236 sys.argv.remove('-l') | 219 sys.argv.remove('-l') |
| 237 elif leak is not None: | 220 elif leak is not None: |
| 238 self.SHOULD_LEAK = leak | 221 self.SHOULD_LEAK = leak |
| 239 if host: | 222 if host: |
| 240 self.HOST = host | 223 self.HOST = host |
| 241 if trial_dir: | 224 if trial_dir: |
| 242 self.TRIAL_DIR = trial_dir | 225 self.TRIAL_DIR = trial_dir |
| 243 | 226 |
| 244 # Format is [ None, tree, tree, ...] | 227 # Format is [ None, tree, tree, ...] |
| 228 # i.e. revisions are 1-based. |
| 245 self.svn_revs = [None] | 229 self.svn_revs = [None] |
| 246 # Format is { repo: [ (hash, tree), (hash, tree), ... ], ... } | 230 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } |
| 231 # so reference looks like self.git_hashes[repo][rev][0] for hash and |
| 232 # self.git_hashes[repo][rev][1] for it's tree snapshot. |
| 233 # For consistency with self.svn_revs, it is 1-based too. |
| 247 self.git_hashes = {} | 234 self.git_hashes = {} |
| 248 self.svnserve = None | 235 self.svnserve = None |
| 249 self.gitdaemon = None | 236 self.gitdaemon = None |
| 250 self.common_init = False | 237 self.common_init = False |
| 251 | 238 |
| 252 def trial_dir(self): | 239 def trial_dir(self): |
| 253 if not self.TRIAL_DIR: | 240 if not self.TRIAL_DIR: |
| 254 self.TRIAL_DIR = os.path.join( | 241 self.TRIAL_DIR = os.path.join( |
| 255 os.path.dirname(os.path.abspath(__file__)), '_trial') | 242 os.path.dirname(os.path.abspath(__file__)), '_trial') |
| 256 return self.TRIAL_DIR | 243 return self.TRIAL_DIR |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 395 | 382 |
| 396 def setUpGIT(self): | 383 def setUpGIT(self): |
| 397 """Creates git repositories and start the servers.""" | 384 """Creates git repositories and start the servers.""" |
| 398 if self.gitdaemon: | 385 if self.gitdaemon: |
| 399 return True | 386 return True |
| 400 self.setUp() | 387 self.setUp() |
| 401 if sys.platform == 'win32': | 388 if sys.platform == 'win32': |
| 402 return False | 389 return False |
| 403 for repo in ['repo_%d' % r for r in range(1, 5)]: | 390 for repo in ['repo_%d' % r for r in range(1, 5)]: |
| 404 check_call(['git', 'init', '-q', join(self.git_root, repo)]) | 391 check_call(['git', 'init', '-q', join(self.git_root, repo)]) |
| 405 self.git_hashes[repo] = [] | 392 self.git_hashes[repo] = [None] |
| 406 | 393 |
| 407 # Testing: | 394 # Testing: |
| 408 # - dependency disapear | 395 # - dependency disapear |
| 409 # - dependency renamed | 396 # - dependency renamed |
| 410 # - versioned and unversioned reference | 397 # - versioned and unversioned reference |
| 411 # - relative and full reference | 398 # - relative and full reference |
| 412 # - deps_os | 399 # - deps_os |
| 413 # - var | 400 # - var |
| 414 # - hooks | 401 # - hooks |
| 415 # TODO(maruel): | 402 # TODO(maruel): |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 472 'open(\\'src/git_hooked1\\', \\'w\\').write(\\'git_hooked1\\')'], | 459 'open(\\'src/git_hooked1\\', \\'w\\').write(\\'git_hooked1\\')'], |
| 473 }, | 460 }, |
| 474 { | 461 { |
| 475 # Should not be run. | 462 # Should not be run. |
| 476 'pattern': 'nonexistent', | 463 'pattern': 'nonexistent', |
| 477 'action': ['python', '-c', | 464 'action': ['python', '-c', |
| 478 'open(\\'src/git_hooked2\\', \\'w\\').write(\\'git_hooked2\\')'], | 465 'open(\\'src/git_hooked2\\', \\'w\\').write(\\'git_hooked2\\')'], |
| 479 }, | 466 }, |
| 480 ] | 467 ] |
| 481 """ % { | 468 """ % { |
| 469 'host': self.HOST, |
| 470 # See self.__init__() for the format. Grab's the hash of the first |
| 471 # commit in repo_2. Only keep the first 7 character because of: |
| 482 # TODO(maruel): http://crosbug.com/3591 We need to strip the hash.. duh. | 472 # TODO(maruel): http://crosbug.com/3591 We need to strip the hash.. duh. |
| 483 'host': self.HOST, | 473 'hash': self.git_hashes['repo_2'][1][0][:7] |
| 484 'hash': self.git_hashes['repo_2'][0][0][:7] | |
| 485 }, | 474 }, |
| 486 'origin': "git/repo_1@2\n" | 475 'origin': "git/repo_1@2\n" |
| 487 }) | 476 }) |
| 488 | 477 |
| 489 # Start the daemon. | 478 # Start the daemon. |
| 490 cmd = ['git', 'daemon', '--export-all', '--base-path=' + self.repos_dir] | 479 cmd = ['git', 'daemon', '--export-all', '--base-path=' + self.repos_dir] |
| 491 if self.HOST == '127.0.0.1': | 480 if self.HOST == '127.0.0.1': |
| 492 cmd.append('--listen=127.0.0.1') | 481 cmd.append('--listen=127.0.0.1') |
| 493 logging.debug(cmd) | 482 logging.debug(cmd) |
| 494 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) | 483 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) |
| 495 return True | 484 return True |
| 496 | 485 |
| 497 def _commit_svn(self, tree): | 486 def _commit_svn(self, tree): |
| 498 self._genTree(self.svn_root, tree) | 487 self._genTree(self.svn_root, tree) |
| 499 commit_svn(self.svn_root) | 488 commit_svn(self.svn_root) |
| 500 if self.svn_revs and self.svn_revs[-1]: | 489 if self.svn_revs and self.svn_revs[-1]: |
| 501 new_tree = self.svn_revs[-1].copy() | 490 new_tree = self.svn_revs[-1].copy() |
| 502 new_tree.update(tree) | 491 new_tree.update(tree) |
| 503 else: | 492 else: |
| 504 new_tree = tree.copy() | 493 new_tree = tree.copy() |
| 505 self.svn_revs.append(new_tree) | 494 self.svn_revs.append(new_tree) |
| 506 | 495 |
| 507 def _commit_git(self, repo, tree): | 496 def _commit_git(self, repo, tree): |
| 508 repo_root = join(self.git_root, repo) | 497 repo_root = join(self.git_root, repo) |
| 509 self._genTree(repo_root, tree) | 498 self._genTree(repo_root, tree) |
| 510 hash = commit_git(repo_root) | 499 hash = commit_git(repo_root) |
| 511 if self.git_hashes[repo]: | 500 if self.git_hashes[repo][-1]: |
| 512 new_tree = self.git_hashes[repo][-1][1].copy() | 501 new_tree = self.git_hashes[repo][-1][1].copy() |
| 513 new_tree.update(tree) | 502 new_tree.update(tree) |
| 514 else: | 503 else: |
| 515 new_tree = tree.copy() | 504 new_tree = tree.copy() |
| 516 self.git_hashes[repo].append((hash, new_tree)) | 505 self.git_hashes[repo].append((hash, new_tree)) |
| 517 | 506 |
| 518 | 507 |
| 519 class FakeReposTestBase(unittest.TestCase): | 508 class FakeReposTestBase(unittest.TestCase): |
| 520 """This is vaguely inspired by twisted.""" | 509 """This is vaguely inspired by twisted.""" |
| 521 | 510 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 535 self.root_dir = join(self.CLASS_ROOT_DIR, self.id()) | 524 self.root_dir = join(self.CLASS_ROOT_DIR, self.id()) |
| 536 rmtree(self.root_dir) | 525 rmtree(self.root_dir) |
| 537 os.makedirs(self.root_dir) | 526 os.makedirs(self.root_dir) |
| 538 self.svn_base = 'svn://%s/svn/' % self.FAKE_REPOS.HOST | 527 self.svn_base = 'svn://%s/svn/' % self.FAKE_REPOS.HOST |
| 539 self.git_base = 'git://%s/git/' % self.FAKE_REPOS.HOST | 528 self.git_base = 'git://%s/git/' % self.FAKE_REPOS.HOST |
| 540 | 529 |
| 541 def tearDown(self): | 530 def tearDown(self): |
| 542 if not self.FAKE_REPOS.SHOULD_LEAK: | 531 if not self.FAKE_REPOS.SHOULD_LEAK: |
| 543 rmtree(self.root_dir) | 532 rmtree(self.root_dir) |
| 544 | 533 |
| 545 def checkString(self, expected, result): | 534 def checkString(self, expected, result, msg=None): |
| 546 """Prints the diffs to ease debugging.""" | 535 """Prints the diffs to ease debugging.""" |
| 547 if expected != result: | 536 if expected != result: |
| 548 # Strip the begining | 537 # Strip the begining |
| 549 while expected and result and expected[0] == result[0]: | 538 while expected and result and expected[0] == result[0]: |
| 550 expected = expected[1:] | 539 expected = expected[1:] |
| 551 result = result[1:] | 540 result = result[1:] |
| 552 # The exception trace makes it hard to read so dump it too. | 541 # The exception trace makes it hard to read so dump it too. |
| 553 if '\n' in result: | 542 if '\n' in result: |
| 554 print result | 543 print result |
| 555 self.assertEquals(expected, result) | 544 self.assertEquals(expected, result, msg) |
| 556 | 545 |
| 557 def check(self, expected, results): | 546 def check(self, expected, results): |
| 558 """Checks stdout, stderr, retcode.""" | 547 """Checks stdout, stderr, retcode.""" |
| 559 self.checkString(expected[0], results[0]) | 548 self.checkString(expected[0], results[0]) |
| 560 self.checkString(expected[1], results[1]) | 549 self.checkString(expected[1], results[1]) |
| 561 self.assertEquals(expected[2], results[2]) | 550 self.assertEquals(expected[2], results[2]) |
| 562 | 551 |
| 563 def assertTree(self, tree, tree_root=None): | 552 def assertTree(self, tree, tree_root=None): |
| 564 """Diff the checkout tree with a dict.""" | 553 """Diff the checkout tree with a dict.""" |
| 565 if not tree_root: | 554 if not tree_root: |
| 566 tree_root = self.root_dir | 555 tree_root = self.root_dir |
| 567 actual = read_tree(tree_root) | 556 actual = read_tree(tree_root) |
| 568 diff = dict_diff(tree, actual) | 557 diff = dict_diff(tree, actual) |
| 569 if diff: | 558 if diff: |
| 570 logging.debug('Actual %s\n%s' % (tree_root, pprint.pformat(actual))) | 559 logging.debug('Actual %s\n%s' % (tree_root, pprint.pformat(actual))) |
| 571 logging.debug('Expected\n%s' % pprint.pformat(tree)) | 560 logging.debug('Expected\n%s' % pprint.pformat(tree)) |
| 572 logging.debug('Diff\n%s' % pprint.pformat(diff)) | 561 logging.debug('Diff\n%s' % pprint.pformat(diff)) |
| 573 self.assertEquals(diff, []) | 562 self.assertEquals(diff, []) |
| 574 | 563 |
| 564 def mangle_svn_tree(self, *args): |
| 565 """Creates a 'virtual directory snapshot' to compare with the actual result |
| 566 on disk.""" |
| 567 result = {} |
| 568 for item, new_root in args: |
| 569 old_root, rev = item.split('@', 1) |
| 570 tree = self.FAKE_REPOS.svn_revs[int(rev)] |
| 571 for k, v in tree.iteritems(): |
| 572 if not k.startswith(old_root): |
| 573 continue |
| 574 result[join(new_root, k[len(old_root) + 1:]).replace(os.sep, '/')] = v |
| 575 return result |
| 576 |
| 577 def mangle_git_tree(self, *args): |
| 578 """Creates a 'virtual directory snapshot' to compare with the actual result |
| 579 on disk.""" |
| 580 result = {} |
| 581 for item, new_root in args: |
| 582 repo, rev = item.split('@', 1) |
| 583 tree = self.gittree(repo, rev) |
| 584 for k, v in tree.iteritems(): |
| 585 result[join(new_root, k)] = v |
| 586 return result |
| 587 |
| 588 def githash(self, repo, rev): |
| 589 """Sort-hand: Returns the hash for a git 'revision'.""" |
| 590 return self.FAKE_REPOS.git_hashes[repo][int(rev)][0] |
| 591 |
| 592 def gittree(self, repo, rev): |
| 593 """Sort-hand: returns the directory tree for a git 'revision'.""" |
| 594 return self.FAKE_REPOS.git_hashes[repo][int(rev)][1] |
| 595 |
| 575 | 596 |
| 576 def main(argv): | 597 def main(argv): |
| 577 fake = FakeRepos() | 598 fake = FakeRepos() |
| 578 print 'Using %s' % fake.trial_dir() | 599 print 'Using %s' % fake.trial_dir() |
| 579 try: | 600 try: |
| 580 fake.setUp() | 601 fake.setUp() |
| 581 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') | 602 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') |
| 582 sys.stdin.readline() | 603 sys.stdin.readline() |
| 583 except KeyboardInterrupt: | 604 except KeyboardInterrupt: |
| 584 fake.SHOULD_LEAK = True | 605 fake.SHOULD_LEAK = True |
| 585 return 0 | 606 return 0 |
| 586 | 607 |
| 587 | 608 |
| 588 if __name__ == '__main__': | 609 if __name__ == '__main__': |
| 589 sys.exit(main(sys.argv)) | 610 sys.exit(main(sys.argv)) |
| OLD | NEW |