| 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 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 class FakeReposBase(object): | 222 class FakeReposBase(object): |
| 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 self.trial = trial_dir.TrialDir('repos') |
| 255 logging.basicConfig(level=logging.DEBUG) | |
| 256 elif leak is not None: | |
| 257 self.SHOULD_LEAK = leak | |
| 258 self.host = host or '127.0.0.1' | 246 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, ...] | 247 # Format is [ None, tree, tree, ...] |
| 263 # i.e. revisions are 1-based. | 248 # i.e. revisions are 1-based. |
| 264 self.svn_revs = [None] | 249 self.svn_revs = [None] |
| 265 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } | 250 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } |
| 266 # so reference looks like self.git_hashes[repo][rev][0] for hash and | 251 # 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. | 252 # self.git_hashes[repo][rev][1] for it's tree snapshot. |
| 268 # For consistency with self.svn_revs, it is 1-based too. | 253 # For consistency with self.svn_revs, it is 1-based too. |
| 269 self.git_hashes = {} | 254 self.git_hashes = {} |
| 270 self.svnserve = None | 255 self.svnserve = None |
| 271 self.gitdaemon = None | 256 self.gitdaemon = None |
| 272 self.common_init = False | |
| 273 self.repos_dir = None | |
| 274 self.git_pid_file = None | 257 self.git_pid_file = None |
| 275 self.git_root = None | 258 self.git_root = None |
| 276 self.svn_checkout = None | 259 self.svn_checkout = None |
| 277 self.svn_repo = None | 260 self.svn_repo = None |
| 278 self.git_dirty = False | 261 self.git_dirty = False |
| 279 self.svn_dirty = False | 262 self.svn_dirty = False |
| 280 self.svn_base = 'svn://%s/svn/' % self.host | 263 self.svn_base = 'svn://%s/svn/' % self.host |
| 281 self.git_base = 'git://%s/git/' % self.host | 264 self.git_base = 'git://%s/git/' % self.host |
| 282 self.svn_port = 3690 | 265 self.svn_port = 3690 |
| 283 self.git_port = 9418 | 266 self.git_port = 9418 |
| 284 | 267 |
| 285 def trial_dir(self): | 268 @property |
| 286 if not self.TRIAL_DIR: | 269 def root_dir(self): |
| 287 self.TRIAL_DIR = os.path.join( | 270 return self.trial.root_dir |
| 288 os.path.dirname(os.path.abspath(__file__)), '_trial') | |
| 289 return self.TRIAL_DIR | |
| 290 | 271 |
| 291 def set_up(self): | 272 def set_up(self): |
| 292 """All late initialization comes here. | 273 """All late initialization comes here.""" |
| 293 | |
| 294 Note that it deletes all trial_dir() and not only repos_dir. | |
| 295 """ | |
| 296 self.cleanup_dirt() | 274 self.cleanup_dirt() |
| 297 if not self.common_init: | 275 if not self.root_dir: |
| 298 self.common_init = True | 276 try: |
| 299 self.repos_dir = os.path.join(self.trial_dir(), 'repos') | 277 add_kill() |
| 300 self.git_root = join(self.repos_dir, 'git') | 278 # self.root_dir is not set before this call. |
| 301 self.svn_checkout = join(self.repos_dir, 'svn_checkout') | 279 self.trial.set_up() |
| 302 self.svn_repo = join(self.repos_dir, 'svn') | 280 self.git_root = join(self.root_dir, 'git') |
| 303 add_kill() | 281 self.svn_checkout = join(self.root_dir, 'svn_checkout') |
| 304 rmtree(self.trial_dir()) | 282 self.svn_repo = join(self.root_dir, 'svn') |
| 305 os.makedirs(self.repos_dir) | 283 finally: |
| 306 atexit.register(self.tear_down) | 284 # Registers cleanup. |
| 285 atexit.register(self.tear_down) |
| 307 | 286 |
| 308 def cleanup_dirt(self): | 287 def cleanup_dirt(self): |
| 309 """For each dirty repository, destroy it.""" | 288 """For each dirty repository, destroy it.""" |
| 310 if self.svn_dirty: | 289 if self.svn_dirty: |
| 311 if not self.tear_down_svn(): | 290 if not self.tear_down_svn(): |
| 312 logging.error('Using both leaking checkout and svn dirty checkout') | 291 logging.error('Using both leaking checkout and svn dirty checkout') |
| 313 if self.git_dirty: | 292 if self.git_dirty: |
| 314 if not self.tear_down_git(): | 293 if not self.tear_down_git(): |
| 315 logging.error('Using both leaking checkout and git dirty checkout') | 294 logging.error('Using both leaking checkout and git dirty checkout') |
| 316 | 295 |
| 317 def tear_down(self): | 296 def tear_down(self): |
| 318 """Kills the servers and delete the directories.""" | 297 """Kills the servers and delete the directories.""" |
| 319 self.tear_down_svn() | 298 self.tear_down_svn() |
| 320 self.tear_down_git() | 299 self.tear_down_git() |
| 321 if not self.SHOULD_LEAK: | 300 # This deletes the directories. |
| 322 logging.debug('Removing %s' % self.trial_dir()) | 301 self.trial.tear_down() |
| 323 rmtree(self.trial_dir()) | 302 self.trial = None |
| 324 | 303 |
| 325 def tear_down_svn(self): | 304 def tear_down_svn(self): |
| 326 if self.svnserve: | 305 if self.svnserve: |
| 327 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) | 306 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) |
| 328 self.svnserve.kill() | 307 self.svnserve.kill() |
| 329 self.wait_for_port_to_free(self.svn_port) | 308 self.wait_for_port_to_free(self.svn_port) |
| 330 self.svnserve = None | 309 self.svnserve = None |
| 331 if not self.SHOULD_LEAK: | 310 if not self.trial.SHOULD_LEAK: |
| 332 logging.debug('Removing %s' % self.svn_repo) | 311 logging.debug('Removing %s' % self.svn_repo) |
| 333 rmtree(self.svn_repo) | 312 rmtree(self.svn_repo) |
| 334 logging.debug('Removing %s' % self.svn_checkout) | 313 logging.debug('Removing %s' % self.svn_checkout) |
| 335 rmtree(self.svn_checkout) | 314 rmtree(self.svn_checkout) |
| 336 else: | 315 else: |
| 337 return False | 316 return False |
| 338 return True | 317 return True |
| 339 | 318 |
| 340 def tear_down_git(self): | 319 def tear_down_git(self): |
| 341 if self.gitdaemon: | 320 if self.gitdaemon: |
| 342 logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) | 321 logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) |
| 343 self.gitdaemon.kill() | 322 self.gitdaemon.kill() |
| 344 self.gitdaemon = None | 323 self.gitdaemon = None |
| 345 if self.git_pid_file: | 324 if self.git_pid_file: |
| 346 pid = int(self.git_pid_file.read()) | 325 pid = int(self.git_pid_file.read()) |
| 347 self.git_pid_file.close() | 326 self.git_pid_file.close() |
| 348 logging.debug('Killing git daemon pid %s' % pid) | 327 logging.debug('Killing git daemon pid %s' % pid) |
| 349 kill_pid(pid) | 328 kill_pid(pid) |
| 350 self.git_pid_file = None | 329 self.git_pid_file = None |
| 351 self.wait_for_port_to_free(self.git_port) | 330 self.wait_for_port_to_free(self.git_port) |
| 352 if not self.SHOULD_LEAK: | 331 if not self.trial.SHOULD_LEAK: |
| 353 logging.debug('Removing %s' % self.git_root) | 332 logging.debug('Removing %s' % self.git_root) |
| 354 rmtree(self.git_root) | 333 rmtree(self.git_root) |
| 355 else: | 334 else: |
| 356 return False | 335 return False |
| 357 return True | 336 return True |
| 358 | 337 |
| 359 @staticmethod | 338 @staticmethod |
| 360 def _genTree(root, tree_dict): | 339 def _genTree(root, tree_dict): |
| 361 """For a dictionary of file contents, generate a filesystem.""" | 340 """For a dictionary of file contents, generate a filesystem.""" |
| 362 if not os.path.isdir(root): | 341 if not os.path.isdir(root): |
| (...skipping 22 matching lines...) Expand all Loading... |
| 385 write(join(self.svn_repo, 'conf', 'svnserve.conf'), | 364 write(join(self.svn_repo, 'conf', 'svnserve.conf'), |
| 386 '[general]\n' | 365 '[general]\n' |
| 387 'anon-access = read\n' | 366 'anon-access = read\n' |
| 388 'auth-access = write\n' | 367 'auth-access = write\n' |
| 389 'password-db = passwd\n') | 368 'password-db = passwd\n') |
| 390 text = '[users]\n' | 369 text = '[users]\n' |
| 391 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) | 370 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) |
| 392 write(join(self.svn_repo, 'conf', 'passwd'), text) | 371 write(join(self.svn_repo, 'conf', 'passwd'), text) |
| 393 | 372 |
| 394 # Start the daemon. | 373 # Start the daemon. |
| 395 cmd = ['svnserve', '-d', '--foreground', '-r', self.repos_dir] | 374 cmd = ['svnserve', '-d', '--foreground', '-r', self.root_dir] |
| 396 if self.host == '127.0.0.1': | 375 if self.host == '127.0.0.1': |
| 397 cmd.append('--listen-host=' + self.host) | 376 cmd.append('--listen-host=' + self.host) |
| 398 self.check_port_is_free(self.svn_port) | 377 self.check_port_is_free(self.svn_port) |
| 399 self.svnserve = Popen(cmd, cwd=self.svn_repo) | 378 self.svnserve = Popen(cmd, cwd=self.svn_repo) |
| 400 self.wait_for_port_to_bind(self.svn_port, self.svnserve) | 379 self.wait_for_port_to_bind(self.svn_port, self.svnserve) |
| 401 self.populateSvn() | 380 self.populateSvn() |
| 402 self.svn_dirty = False | 381 self.svn_dirty = False |
| 403 return True | 382 return True |
| 404 | 383 |
| 405 def set_up_git(self): | 384 def set_up_git(self): |
| 406 """Creates git repositories and start the servers.""" | 385 """Creates git repositories and start the servers.""" |
| 407 self.set_up() | 386 self.set_up() |
| 408 if self.gitdaemon: | 387 if self.gitdaemon: |
| 409 return True | 388 return True |
| 410 if sys.platform == 'win32': | 389 if sys.platform == 'win32': |
| 411 return False | 390 return False |
| 412 assert self.git_pid_file == None | 391 assert self.git_pid_file == None |
| 413 for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: | 392 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)]) | 393 check_call(['git', 'init', '-q', join(self.git_root, repo)]) |
| 415 self.git_hashes[repo] = [None] | 394 self.git_hashes[repo] = [None] |
| 416 # Unlike svn, populate git before starting the server. | 395 # Unlike svn, populate git before starting the server. |
| 417 self.populateGit() | 396 self.populateGit() |
| 418 # Start the daemon. | 397 # Start the daemon. |
| 419 self.git_pid_file = tempfile.NamedTemporaryFile() | 398 self.git_pid_file = tempfile.NamedTemporaryFile() |
| 420 cmd = ['git', 'daemon', | 399 cmd = ['git', 'daemon', |
| 421 '--export-all', | 400 '--export-all', |
| 422 '--reuseaddr', | 401 '--reuseaddr', |
| 423 '--base-path=' + self.repos_dir, | 402 '--base-path=' + self.root_dir, |
| 424 '--pid-file=' + self.git_pid_file.name] | 403 '--pid-file=' + self.git_pid_file.name] |
| 425 if self.host == '127.0.0.1': | 404 if self.host == '127.0.0.1': |
| 426 cmd.append('--listen=' + self.host) | 405 cmd.append('--listen=' + self.host) |
| 427 self.check_port_is_free(self.git_port) | 406 self.check_port_is_free(self.git_port) |
| 428 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) | 407 self.gitdaemon = Popen(cmd, cwd=self.root_dir) |
| 429 self.wait_for_port_to_bind(self.git_port, self.gitdaemon) | 408 self.wait_for_port_to_bind(self.git_port, self.gitdaemon) |
| 430 self.git_dirty = False | 409 self.git_dirty = False |
| 431 return True | 410 return True |
| 432 | 411 |
| 433 def _commit_svn(self, tree): | 412 def _commit_svn(self, tree): |
| 434 self._genTree(self.svn_checkout, tree) | 413 self._genTree(self.svn_checkout, tree) |
| 435 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) | 414 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) |
| 436 if self.svn_revs and self.svn_revs[-1]: | 415 if self.svn_revs and self.svn_revs[-1]: |
| 437 new_tree = self.svn_revs[-1].copy() | 416 new_tree = self.svn_revs[-1].copy() |
| 438 new_tree.update(tree) | 417 new_tree.update(tree) |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 707 'git_base': self.git_base, | 686 'git_base': self.git_base, |
| 708 # See self.__init__() for the format. Grab's the hash of the first | 687 # 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: | 688 # 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. | 689 # TODO(maruel): http://crosbug.com/3591 We need to strip the hash.. duh. |
| 711 'hash': self.git_hashes['repo_2'][1][0][:7] | 690 'hash': self.git_hashes['repo_2'][1][0][:7] |
| 712 }, | 691 }, |
| 713 'origin': 'git/repo_1@2\n', | 692 'origin': 'git/repo_1@2\n', |
| 714 }) | 693 }) |
| 715 | 694 |
| 716 | 695 |
| 717 class FakeReposTestBase(unittest.TestCase): | 696 class FakeReposTestBase(trial_dir.TestCase): |
| 718 """This is vaguely inspired by twisted.""" | 697 """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. | 698 # static FakeRepos instance. Lazy loaded. |
| 724 FAKE_REPOS = None | 699 FAKE_REPOS = None |
| 725 # Override if necessary. | 700 # Override if necessary. |
| 726 FAKE_REPOS_CLASS = FakeRepos | 701 FAKE_REPOS_CLASS = FakeRepos |
| 727 | 702 |
| 728 def __init__(self, *args, **kwargs): | 703 def setUp(self): |
| 729 unittest.TestCase.__init__(self, *args, **kwargs) | 704 super(FakeReposTestBase, self).setUp() |
| 730 if not FakeReposTestBase.FAKE_REPOS: | 705 if not FakeReposTestBase.FAKE_REPOS: |
| 731 # Lazy create the global instance. | 706 # Lazy create the global instance. |
| 732 FakeReposTestBase.FAKE_REPOS = self.FAKE_REPOS_CLASS() | 707 FakeReposTestBase.FAKE_REPOS = self.FAKE_REPOS_CLASS() |
| 733 | 708 # No need to call self.FAKE_REPOS.setUp(), it will be called by the child |
| 734 def setUp(self): | 709 # class. |
| 735 unittest.TestCase.setUp(self) | 710 # Do not define tearDown(), since super's version does the right thing and |
| 736 self.FAKE_REPOS.set_up() | 711 # 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 | 712 |
| 749 @property | 713 @property |
| 750 def svn_base(self): | 714 def svn_base(self): |
| 751 """Shortcut.""" | 715 """Shortcut.""" |
| 752 return self.FAKE_REPOS.svn_base | 716 return self.FAKE_REPOS.svn_base |
| 753 | 717 |
| 754 @property | 718 @property |
| 755 def git_base(self): | 719 def git_base(self): |
| 756 """Shortcut.""" | 720 """Shortcut.""" |
| 757 return self.FAKE_REPOS.git_base | 721 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'.""" | 781 """Sort-hand: Returns the hash for a git 'revision'.""" |
| 818 return self.FAKE_REPOS.git_hashes[repo][int(rev)][0] | 782 return self.FAKE_REPOS.git_hashes[repo][int(rev)][0] |
| 819 | 783 |
| 820 def gittree(self, repo, rev): | 784 def gittree(self, repo, rev): |
| 821 """Sort-hand: returns the directory tree for a git 'revision'.""" | 785 """Sort-hand: returns the directory tree for a git 'revision'.""" |
| 822 return self.FAKE_REPOS.git_hashes[repo][int(rev)][1] | 786 return self.FAKE_REPOS.git_hashes[repo][int(rev)][1] |
| 823 | 787 |
| 824 | 788 |
| 825 def main(argv): | 789 def main(argv): |
| 826 fake = FakeRepos() | 790 fake = FakeRepos() |
| 827 print 'Using %s' % fake.trial_dir() | 791 print 'Using %s' % fake.root_dir |
| 828 try: | 792 try: |
| 829 fake.set_up_svn() | 793 fake.set_up_svn() |
| 830 fake.set_up_git() | 794 fake.set_up_git() |
| 831 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') | 795 print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') |
| 832 sys.stdin.readline() | 796 sys.stdin.readline() |
| 833 except KeyboardInterrupt: | 797 except KeyboardInterrupt: |
| 834 fake.SHOULD_LEAK = True | 798 trial_dir.TrialDir.SHOULD_LEAK.leak = True |
| 835 return 0 | 799 return 0 |
| 836 | 800 |
| 837 | 801 |
| 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__': | 802 if __name__ == '__main__': |
| 846 sys.exit(main(sys.argv)) | 803 sys.exit(main(sys.argv)) |
| OLD | NEW |