Chromium Code Reviews| 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 datetime | 9 import datetime |
| 10 import errno | 10 import errno |
| 11 import logging | 11 import logging |
| 12 import os | 12 import os |
| 13 import pprint | 13 import pprint |
| 14 import re | 14 import re |
| 15 import socket | 15 import socket |
| 16 import stat | 16 import stat |
| 17 import subprocess | 17 import subprocess |
| 18 import sys | 18 import sys |
| 19 import tempfile | 19 import tempfile |
| 20 import time | 20 import time |
| 21 import unittest | |
| 22 | 21 |
| 23 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 22 # trial_dir must be first for non-system libraries. |
| 24 | 23 from tests import trial_dir |
| 25 import scm | 24 import scm |
| 26 | 25 |
| 27 ## Utility functions | 26 ## Utility functions |
| 28 | 27 |
| 29 | 28 |
| 30 def kill_pid(pid): | 29 def kill_pid(pid): |
| 31 """Kills a process by its process id.""" | 30 """Kills a process by its process id.""" |
| 32 try: | 31 try: |
| 33 # Unable to import 'module' | 32 # Unable to import 'module' |
| 34 # pylint: disable=F0401 | 33 # pylint: disable=F0401 |
| (...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 213 check_call(['git', 'add', '-A', '-f'], cwd=repo) | 212 check_call(['git', 'add', '-A', '-f'], cwd=repo) |
| 214 check_call(['git', 'commit', '-q', '--message', 'foo'], cwd=repo) | 213 check_call(['git', 'commit', '-q', '--message', 'foo'], cwd=repo) |
| 215 rev = Popen(['git', 'show-ref', '--head', 'HEAD'], | 214 rev = Popen(['git', 'show-ref', '--head', 'HEAD'], |
| 216 cwd=repo).communicate()[0].split(' ', 1)[0] | 215 cwd=repo).communicate()[0].split(' ', 1)[0] |
| 217 logging.debug('At revision %s' % rev) | 216 logging.debug('At revision %s' % rev) |
| 218 return rev | 217 return rev |
| 219 | 218 |
| 220 | 219 |
| 221 _FAKE_LOADED = False | 220 _FAKE_LOADED = False |
| 222 | 221 |
| 223 class FakeReposBase(object): | 222 class FakeReposBase(trial_dir.TrialDir): |
| 224 """Generate both svn and git repositories to test gclient functionality. | 223 """Generate both svn and git repositories to test gclient functionality. |
| 225 | 224 |
| 226 Many DEPS functionalities need to be tested: Var, File, From, deps_os, hooks, | 225 Many DEPS functionalities need to be tested: Var, File, From, deps_os, hooks, |
| 227 use_relative_paths. | 226 use_relative_paths. |
| 228 | 227 |
| 229 And types of dependencies: Relative urls, Full urls, both svn and git. | 228 And types of dependencies: Relative urls, Full urls, both svn and git. |
| 230 | 229 |
| 231 populateSvn() and populateGit() need to be implemented by the subclass. | 230 populateSvn() and populateGit() need to be implemented by the subclass. |
| 232 """ | 231 """ |
| 233 | |
| 234 # When SHOULD_LEAK is set to True, temporary directories created while the | |
| 235 # tests are running aren't deleted at the end of the tests. Expect failures | |
| 236 # when running more than one test due to inter-test side-effects. Helps with | |
| 237 # debugging. | |
| 238 SHOULD_LEAK = False | |
| 239 # Override if unhappy. | |
| 240 TRIAL_DIR = None | |
| 241 # Hostname | 232 # Hostname |
| 242 NB_GIT_REPOS = 1 | 233 NB_GIT_REPOS = 1 |
| 243 USERS = [ | 234 USERS = [ |
| 244 ('user1@example.com', 'foo'), | 235 ('user1@example.com', 'foo'), |
| 245 ('user2@example.com', 'bar'), | 236 ('user2@example.com', 'bar'), |
| 246 ] | 237 ] |
| 247 | 238 |
| 248 def __init__(self, trial_dir=None, leak=None, host=None): | 239 def __init__(self, host=None): |
| 249 global _FAKE_LOADED | 240 global _FAKE_LOADED |
| 250 if _FAKE_LOADED: | 241 if _FAKE_LOADED: |
| 251 raise Exception('You can only start one FakeRepos at a time.') | 242 raise Exception('You can only start one FakeRepos at a time.') |
| 252 _FAKE_LOADED = True | 243 _FAKE_LOADED = True |
| 253 # Quick hack. | 244 |
| 254 if '-v' in sys.argv: | 245 super(FakeReposBase, self).__init__('repos') |
| 255 logging.basicConfig(level=logging.DEBUG) | 246 |
| 256 elif leak is not None: | |
| 257 self.SHOULD_LEAK = leak | |
| 258 self.host = host or '127.0.0.1' | 247 self.host = host or '127.0.0.1' |
| 259 if trial_dir: | |
| 260 self.TRIAL_DIR = trial_dir | |
| 261 | |
| 262 # Format is [ None, tree, tree, ...] | 248 # Format is [ None, tree, tree, ...] |
| 263 # i.e. revisions are 1-based. | 249 # i.e. revisions are 1-based. |
| 264 self.svn_revs = [None] | 250 self.svn_revs = [None] |
| 265 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } | 251 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } |
| 266 # so reference looks like self.git_hashes[repo][rev][0] for hash and | 252 # so reference looks like self.git_hashes[repo][rev][0] for hash and |
| 267 # self.git_hashes[repo][rev][1] for it's tree snapshot. | 253 # self.git_hashes[repo][rev][1] for it's tree snapshot. |
| 268 # For consistency with self.svn_revs, it is 1-based too. | 254 # For consistency with self.svn_revs, it is 1-based too. |
| 269 self.git_hashes = {} | 255 self.git_hashes = {} |
| 270 self.svnserve = None | 256 self.svnserve = None |
| 271 self.gitdaemon = None | 257 self.gitdaemon = None |
| 272 self.common_init = False | |
| 273 self.repos_dir = None | |
| 274 self.git_pid_file = None | 258 self.git_pid_file = None |
| 275 self.git_root = None | 259 self.git_root = None |
| 276 self.svn_checkout = None | 260 self.svn_checkout = None |
| 277 self.svn_repo = None | 261 self.svn_repo = None |
| 278 self.git_dirty = False | 262 self.git_dirty = False |
| 279 self.svn_dirty = False | 263 self.svn_dirty = False |
| 280 self.svn_base = 'svn://%s/svn/' % self.host | 264 self.svn_base = 'svn://%s/svn/' % self.host |
| 281 self.git_base = 'git://%s/git/' % self.host | 265 self.git_base = 'git://%s/git/' % self.host |
| 282 self.svn_port = 3690 | 266 self.svn_port = 3690 |
| 283 self.git_port = 9418 | 267 self.git_port = 9418 |
| 284 | 268 |
| 285 def trial_dir(self): | |
| 286 if not self.TRIAL_DIR: | |
| 287 self.TRIAL_DIR = os.path.join( | |
| 288 os.path.dirname(os.path.abspath(__file__)), '_trial') | |
| 289 return self.TRIAL_DIR | |
| 290 | |
| 291 def set_up(self): | 269 def set_up(self): |
| 292 """All late initialization comes here. | 270 """All late initialization comes here.""" |
| 293 | |
| 294 Note that it deletes all trial_dir() and not only repos_dir. | |
| 295 """ | |
| 296 self.cleanup_dirt() | 271 self.cleanup_dirt() |
| 297 if not self.common_init: | 272 if not self.root_dir: |
| 298 self.common_init = True | 273 try: |
| 299 self.repos_dir = os.path.join(self.trial_dir(), 'repos') | 274 add_kill() |
| 300 self.git_root = join(self.repos_dir, 'git') | 275 # self.root_dir is not set before this call. |
| 301 self.svn_checkout = join(self.repos_dir, 'svn_checkout') | 276 super(FakeReposBase, self).set_up() |
|
Evan Martin
2011/03/04 19:46:11
I feel like this mechanism, where you conditionall
| |
| 302 self.svn_repo = join(self.repos_dir, 'svn') | 277 self.git_root = join(self.root_dir, 'git') |
| 303 add_kill() | 278 self.svn_checkout = join(self.root_dir, 'svn_checkout') |
| 304 rmtree(self.trial_dir()) | 279 self.svn_repo = join(self.root_dir, 'svn') |
| 305 os.makedirs(self.repos_dir) | 280 finally: |
| 306 atexit.register(self.tear_down) | 281 # Registers cleanup. |
| 282 atexit.register(self.tear_down) | |
| 307 | 283 |
| 308 def cleanup_dirt(self): | 284 def cleanup_dirt(self): |
| 309 """For each dirty repository, destroy it.""" | 285 """For each dirty repository, destroy it.""" |
| 310 if self.svn_dirty: | 286 if self.svn_dirty: |
| 311 if not self.tear_down_svn(): | 287 if not self.tear_down_svn(): |
| 312 logging.error('Using both leaking checkout and svn dirty checkout') | 288 logging.error('Using both leaking checkout and svn dirty checkout') |
| 313 if self.git_dirty: | 289 if self.git_dirty: |
| 314 if not self.tear_down_git(): | 290 if not self.tear_down_git(): |
| 315 logging.error('Using both leaking checkout and git dirty checkout') | 291 logging.error('Using both leaking checkout and git dirty checkout') |
| 316 | 292 |
| 317 def tear_down(self): | 293 def tear_down(self): |
| 318 """Kills the servers and delete the directories.""" | 294 """Kills the servers and delete the directories.""" |
| 319 self.tear_down_svn() | 295 self.tear_down_svn() |
| 320 self.tear_down_git() | 296 self.tear_down_git() |
| 321 if not self.SHOULD_LEAK: | 297 # This deletes the directories. |
| 322 logging.debug('Removing %s' % self.trial_dir()) | 298 super(FakeReposBase, self).tear_down() |
| 323 rmtree(self.trial_dir()) | |
| 324 | 299 |
| 325 def tear_down_svn(self): | 300 def tear_down_svn(self): |
| 326 if self.svnserve: | 301 if self.svnserve: |
| 327 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) | 302 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) |
| 328 self.svnserve.kill() | 303 self.svnserve.kill() |
| 329 self.wait_for_port_to_free(self.svn_port) | 304 self.wait_for_port_to_free(self.svn_port) |
| 330 self.svnserve = None | 305 self.svnserve = None |
| 331 if not self.SHOULD_LEAK: | 306 if not self.SHOULD_LEAK: |
| 332 logging.debug('Removing %s' % self.svn_repo) | 307 logging.debug('Removing %s' % self.svn_repo) |
| 333 rmtree(self.svn_repo) | 308 rmtree(self.svn_repo) |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 write(join(self.svn_repo, 'conf', 'svnserve.conf'), | 360 write(join(self.svn_repo, 'conf', 'svnserve.conf'), |
| 386 '[general]\n' | 361 '[general]\n' |
| 387 'anon-access = read\n' | 362 'anon-access = read\n' |
| 388 'auth-access = write\n' | 363 'auth-access = write\n' |
| 389 'password-db = passwd\n') | 364 'password-db = passwd\n') |
| 390 text = '[users]\n' | 365 text = '[users]\n' |
| 391 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) | 366 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) |
| 392 write(join(self.svn_repo, 'conf', 'passwd'), text) | 367 write(join(self.svn_repo, 'conf', 'passwd'), text) |
| 393 | 368 |
| 394 # Start the daemon. | 369 # Start the daemon. |
| 395 cmd = ['svnserve', '-d', '--foreground', '-r', self.repos_dir] | 370 cmd = ['svnserve', '-d', '--foreground', '-r', self.root_dir] |
| 396 if self.host == '127.0.0.1': | 371 if self.host == '127.0.0.1': |
| 397 cmd.append('--listen-host=' + self.host) | 372 cmd.append('--listen-host=' + self.host) |
| 398 self.check_port_is_free(self.svn_port) | 373 self.check_port_is_free(self.svn_port) |
| 399 self.svnserve = Popen(cmd, cwd=self.svn_repo) | 374 self.svnserve = Popen(cmd, cwd=self.svn_repo) |
| 400 self.wait_for_port_to_bind(self.svn_port, self.svnserve) | 375 self.wait_for_port_to_bind(self.svn_port, self.svnserve) |
| 401 self.populateSvn() | 376 self.populateSvn() |
| 402 self.svn_dirty = False | 377 self.svn_dirty = False |
| 403 return True | 378 return True |
| 404 | 379 |
| 405 def set_up_git(self): | 380 def set_up_git(self): |
| 406 """Creates git repositories and start the servers.""" | 381 """Creates git repositories and start the servers.""" |
| 407 self.set_up() | 382 self.set_up() |
| 408 if self.gitdaemon: | 383 if self.gitdaemon: |
| 409 return True | 384 return True |
| 410 if sys.platform == 'win32': | 385 if sys.platform == 'win32': |
| 411 return False | 386 return False |
| 412 assert self.git_pid_file == None | 387 assert self.git_pid_file == None |
| 413 for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: | 388 for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: |
| 414 check_call(['git', 'init', '-q', join(self.git_root, repo)]) | 389 check_call(['git', 'init', '-q', join(self.git_root, repo)]) |
| 415 self.git_hashes[repo] = [None] | 390 self.git_hashes[repo] = [None] |
| 416 # Unlike svn, populate git before starting the server. | 391 # Unlike svn, populate git before starting the server. |
| 417 self.populateGit() | 392 self.populateGit() |
| 418 # Start the daemon. | 393 # Start the daemon. |
| 419 self.git_pid_file = tempfile.NamedTemporaryFile() | 394 self.git_pid_file = tempfile.NamedTemporaryFile() |
| 420 cmd = ['git', 'daemon', | 395 cmd = ['git', 'daemon', |
| 421 '--export-all', | 396 '--export-all', |
| 422 '--reuseaddr', | 397 '--reuseaddr', |
| 423 '--base-path=' + self.repos_dir, | 398 '--base-path=' + self.root_dir, |
| 424 '--pid-file=' + self.git_pid_file.name] | 399 '--pid-file=' + self.git_pid_file.name] |
| 425 if self.host == '127.0.0.1': | 400 if self.host == '127.0.0.1': |
| 426 cmd.append('--listen=' + self.host) | 401 cmd.append('--listen=' + self.host) |
| 427 self.check_port_is_free(self.git_port) | 402 self.check_port_is_free(self.git_port) |
| 428 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) | 403 self.gitdaemon = Popen(cmd, cwd=self.root_dir) |
| 429 self.wait_for_port_to_bind(self.git_port, self.gitdaemon) | 404 self.wait_for_port_to_bind(self.git_port, self.gitdaemon) |
| 430 self.git_dirty = False | 405 self.git_dirty = False |
| 431 return True | 406 return True |
| 432 | 407 |
| 433 def _commit_svn(self, tree): | 408 def _commit_svn(self, tree): |
| 434 self._genTree(self.svn_checkout, tree) | 409 self._genTree(self.svn_checkout, tree) |
| 435 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) | 410 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) |
| 436 if self.svn_revs and self.svn_revs[-1]: | 411 if self.svn_revs and self.svn_revs[-1]: |
| 437 new_tree = self.svn_revs[-1].copy() | 412 new_tree = self.svn_revs[-1].copy() |
| 438 new_tree.update(tree) | 413 new_tree.update(tree) |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 707 'git_base': self.git_base, | 682 'git_base': self.git_base, |
| 708 # See self.__init__() for the format. Grab's the hash of the first | 683 # See self.__init__() for the format. Grab's the hash of the first |
| 709 # commit in repo_2. Only keep the first 7 character because of: | 684 # commit in repo_2. Only keep the first 7 character because of: |
| 710 # TODO(maruel): http://crosbug.com/3591 We need to strip the hash.. duh. | 685 # TODO(maruel): http://crosbug.com/3591 We need to strip the hash.. duh. |
| 711 'hash': self.git_hashes['repo_2'][1][0][:7] | 686 'hash': self.git_hashes['repo_2'][1][0][:7] |
| 712 }, | 687 }, |
| 713 'origin': 'git/repo_1@2\n', | 688 'origin': 'git/repo_1@2\n', |
| 714 }) | 689 }) |
| 715 | 690 |
| 716 | 691 |
| 717 class FakeReposTestBase(unittest.TestCase): | 692 class FakeReposTestBase(trial_dir.TestCase): |
| 718 """This is vaguely inspired by twisted.""" | 693 """This is vaguely inspired by twisted.""" |
| 719 | |
| 720 # Replace this in your subclass. | |
| 721 CLASS_ROOT_DIR = None | |
| 722 | |
| 723 # static FakeRepos instance. Lazy loaded. | 694 # static FakeRepos instance. Lazy loaded. |
| 724 FAKE_REPOS = None | 695 FAKE_REPOS = None |
| 725 # Override if necessary. | 696 # Override if necessary. |
| 726 FAKE_REPOS_CLASS = FakeRepos | 697 FAKE_REPOS_CLASS = FakeRepos |
| 727 | 698 |
| 728 def __init__(self, *args, **kwargs): | 699 def setUp(self): |
| 729 unittest.TestCase.__init__(self, *args, **kwargs) | 700 super(FakeReposTestBase, self).setUp() |
| 730 if not FakeReposTestBase.FAKE_REPOS: | 701 if not FakeReposTestBase.FAKE_REPOS: |
| 731 # Lazy create the global instance. | 702 # Lazy create the global instance. |
| 732 FakeReposTestBase.FAKE_REPOS = self.FAKE_REPOS_CLASS() | 703 FakeReposTestBase.FAKE_REPOS = self.FAKE_REPOS_CLASS() |
| 733 | 704 # No need to call self.FAKE_REPOS.setUp(), it will be called by the child |
| 734 def setUp(self): | 705 # class. |
| 735 unittest.TestCase.setUp(self) | 706 # Do not define tearDown(), since super's version does the right thing and |
| 736 self.FAKE_REPOS.set_up() | 707 # FAKE_REPOS is kept across tests. |
| 737 | |
| 738 # Remove left overs and start fresh. | |
| 739 if not self.CLASS_ROOT_DIR: | |
| 740 self.CLASS_ROOT_DIR = join(self.FAKE_REPOS.trial_dir(), 'smoke') | |
| 741 self.root_dir = join(self.CLASS_ROOT_DIR, self.id()) | |
| 742 rmtree(self.root_dir) | |
| 743 os.makedirs(self.root_dir) | |
| 744 | |
| 745 def tearDown(self): | |
| 746 if not self.FAKE_REPOS.SHOULD_LEAK: | |
| 747 rmtree(self.root_dir) | |
| 748 | 708 |
| 749 @property | 709 @property |
| 750 def svn_base(self): | 710 def svn_base(self): |
| 751 """Shortcut.""" | 711 """Shortcut.""" |
| 752 return self.FAKE_REPOS.svn_base | 712 return self.FAKE_REPOS.svn_base |
| 753 | 713 |
| 754 @property | 714 @property |
| 755 def git_base(self): | 715 def git_base(self): |
| 756 """Shortcut.""" | 716 """Shortcut.""" |
| 757 return self.FAKE_REPOS.git_base | 717 return self.FAKE_REPOS.git_base |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 817 """Sort-hand: Returns the hash for a git 'revision'.""" | 777 """Sort-hand: Returns the hash for a git 'revision'.""" |
| 818 return self.FAKE_REPOS.git_hashes[repo][int(rev)][0] | 778 return self.FAKE_REPOS.git_hashes[repo][int(rev)][0] |
| 819 | 779 |
| 820 def gittree(self, repo, rev): | 780 def gittree(self, repo, rev): |
| 821 """Sort-hand: returns the directory tree for a git 'revision'.""" | 781 """Sort-hand: returns the directory tree for a git 'revision'.""" |
| 822 return self.FAKE_REPOS.git_hashes[repo][int(rev)][1] | 782 return self.FAKE_REPOS.git_hashes[repo][int(rev)][1] |
| 823 | 783 |
| 824 | 784 |
| 825 def main(argv): | 785 def main(argv): |
| 826 fake = FakeRepos() | 786 fake = FakeRepos() |
| 827 print 'Using %s' % fake.trial_dir() | 787 print 'Using %s' % fake.root_dir |
| 828 try: | 788 try: |
| 829 fake.set_up_svn() | 789 fake.set_up_svn() |
| 830 fake.set_up_git() | 790 fake.set_up_git() |
| 831 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') | 791 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') |
| 832 sys.stdin.readline() | 792 sys.stdin.readline() |
| 833 except KeyboardInterrupt: | 793 except KeyboardInterrupt: |
| 834 fake.SHOULD_LEAK = True | 794 fake.leak = True |
| 835 return 0 | 795 return 0 |
| 836 | 796 |
| 837 | 797 |
| 838 if '-l' in sys.argv: | |
| 839 # See SHOULD_LEAK definition in FakeReposBase for its purpose. | |
| 840 FakeReposBase.SHOULD_LEAK = True | |
| 841 print 'Leaking!' | |
| 842 sys.argv.remove('-l') | |
| 843 | |
| 844 | |
| 845 if __name__ == '__main__': | 798 if __name__ == '__main__': |
| 846 sys.exit(main(sys.argv)) | 799 sys.exit(main(sys.argv)) |
| OLD | NEW |