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 |