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 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 socket | |
| 14 import stat | 15 import stat |
| 15 import subprocess | 16 import subprocess |
| 16 import sys | 17 import sys |
| 18 import tempfile | |
| 17 import time | 19 import time |
| 18 import unittest | 20 import unittest |
| 19 | 21 |
| 20 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 22 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 21 | 23 |
| 22 import scm | 24 import scm |
| 23 | 25 |
| 24 ## Utility functions | 26 ## Utility functions |
| 25 | 27 |
| 26 | 28 |
| 29 def kill_pid(pid): | |
| 30 """Kills a process by its process id.""" | |
| 31 try: | |
| 32 import signal | |
| 33 return os.kill(pid, signal.SIGKILL) | |
| 34 except ImportError: | |
| 35 pass | |
| 36 | |
| 37 | |
| 27 def addKill(): | 38 def addKill(): |
|
Evan Martin
2011/03/04 18:13:13
The capitalization style of this doesn't match the
M-A Ruel
2011/03/04 18:53:30
I want to convert to PEP8 but I do that incrementa
| |
| 28 """Add kill() method to subprocess.Popen for python <2.6""" | 39 """Adds kill() method to subprocess.Popen for python <2.6""" |
| 29 if getattr(subprocess.Popen, 'kill', None): | 40 if hasattr(subprocess.Popen, 'kill'): |
| 30 return | 41 return |
| 42 | |
| 31 # Unable to import 'module' | 43 # Unable to import 'module' |
| 32 # pylint: disable=F0401 | 44 # pylint: disable=F0401 |
| 33 if sys.platform == 'win32': | 45 if sys.platform == 'win32': |
| 34 def kill_win(process): | 46 def kill_win(process): |
| 35 import win32process | 47 try: |
| 36 # Access to a protected member _handle of a client class | 48 import win32process |
| 37 # pylint: disable=W0212 | 49 # Access to a protected member _handle of a client class |
| 38 return win32process.TerminateProcess(process._handle, -1) | 50 # pylint: disable=W0212 |
| 51 return win32process.TerminateProcess(process._handle, -1) | |
| 52 except ImportError: | |
| 53 pass | |
|
Evan Martin
2011/03/04 18:13:13
Maybe split this out into a function like kill_pid
M-A Ruel
2011/03/04 18:53:30
done
| |
| 39 subprocess.Popen.kill = kill_win | 54 subprocess.Popen.kill = kill_win |
| 40 else: | 55 else: |
| 41 def kill_nix(process): | 56 def kill_nix(process): |
| 42 import signal | 57 return kill_pid(process.pid) |
| 43 return os.kill(process.pid, signal.SIGKILL) | |
| 44 subprocess.Popen.kill = kill_nix | 58 subprocess.Popen.kill = kill_nix |
| 45 | 59 |
| 46 | 60 |
| 47 def rmtree(*path): | 61 def rmtree(*path): |
| 48 """Recursively removes a directory, even if it's marked read-only. | 62 """Recursively removes a directory, even if it's marked read-only. |
| 49 | 63 |
| 50 Remove the directory located at *path, if it exists. | 64 Remove the directory located at *path, if it exists. |
| 51 | 65 |
| 52 shutil.rmtree() doesn't work on Windows if any of the files or directories | 66 shutil.rmtree() doesn't work on Windows if any of the files or directories |
| 53 are read-only, which svn repositories and some .svn files are. We need to | 67 are read-only, which svn repositories and some .svn files are. We need to |
| (...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 243 self.svn_revs = [None] | 257 self.svn_revs = [None] |
| 244 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } | 258 # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... } |
| 245 # so reference looks like self.git_hashes[repo][rev][0] for hash and | 259 # so reference looks like self.git_hashes[repo][rev][0] for hash and |
| 246 # self.git_hashes[repo][rev][1] for it's tree snapshot. | 260 # self.git_hashes[repo][rev][1] for it's tree snapshot. |
| 247 # For consistency with self.svn_revs, it is 1-based too. | 261 # For consistency with self.svn_revs, it is 1-based too. |
| 248 self.git_hashes = {} | 262 self.git_hashes = {} |
| 249 self.svnserve = None | 263 self.svnserve = None |
| 250 self.gitdaemon = None | 264 self.gitdaemon = None |
| 251 self.common_init = False | 265 self.common_init = False |
| 252 self.repos_dir = None | 266 self.repos_dir = None |
| 267 self.gitpidfile = None | |
|
Evan Martin
2011/03/04 18:13:13
git_pid_file
| |
| 253 self.git_root = None | 268 self.git_root = None |
| 254 self.svn_checkout = None | 269 self.svn_checkout = None |
| 255 self.svn_repo = None | 270 self.svn_repo = None |
| 256 self.git_dirty = False | 271 self.git_dirty = False |
| 257 self.svn_dirty = False | 272 self.svn_dirty = False |
| 273 self.svn_port = 3690 | |
| 274 self.git_port = 9418 | |
| 258 | 275 |
| 259 def trial_dir(self): | 276 def trial_dir(self): |
| 260 if not self.TRIAL_DIR: | 277 if not self.TRIAL_DIR: |
| 261 self.TRIAL_DIR = os.path.join( | 278 self.TRIAL_DIR = os.path.join( |
| 262 os.path.dirname(os.path.abspath(__file__)), '_trial') | 279 os.path.dirname(os.path.abspath(__file__)), '_trial') |
| 263 return self.TRIAL_DIR | 280 return self.TRIAL_DIR |
| 264 | 281 |
| 265 def setUp(self): | 282 def setUp(self): |
| 266 """All late initialization comes here. | 283 """All late initialization comes here. |
| 267 | 284 |
| 268 Note that it deletes all trial_dir() and not only repos_dir. | 285 Note that it deletes all trial_dir() and not only repos_dir. |
| 269 """ | 286 """ |
| 270 self.cleanup_dirt() | 287 self.cleanup_dirt() |
| 271 if not self.common_init: | 288 if not self.common_init: |
| 272 self.common_init = True | 289 self.common_init = True |
| 273 self.repos_dir = os.path.join(self.trial_dir(), 'repos') | 290 self.repos_dir = os.path.join(self.trial_dir(), 'repos') |
| 274 self.git_root = join(self.repos_dir, 'git') | 291 self.git_root = join(self.repos_dir, 'git') |
| 275 self.svn_checkout = join(self.repos_dir, 'svn_checkout') | 292 self.svn_checkout = join(self.repos_dir, 'svn_checkout') |
| 276 self.svn_repo = join(self.repos_dir, 'svn') | 293 self.svn_repo = join(self.repos_dir, 'svn') |
| 277 addKill() | 294 addKill() |
| 278 rmtree(self.trial_dir()) | 295 rmtree(self.trial_dir()) |
| 279 os.makedirs(self.repos_dir) | 296 os.makedirs(self.repos_dir) |
| 280 atexit.register(self.tearDown) | 297 atexit.register(self.tearDown) |
| 281 | 298 |
| 282 def cleanup_dirt(self): | 299 def cleanup_dirt(self): |
| 283 """For each dirty repository, regenerate it.""" | 300 """For each dirty repository, regenerate it.""" |
| 284 if self.svnserve and self.svn_dirty: | 301 if self.svn_dirty: |
| 285 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) | 302 if not self.tearDownSVN(): |
| 286 self.svnserve.kill() | 303 logging.warning('Using both leaking checkout and svn dirty checkout') |
| 287 self.svnserve = None | 304 if self.git_dirty: |
| 288 if not self.SHOULD_LEAK: | 305 if not self.tearDownGIT(): |
| 289 logging.debug('Removing dirty %s' % self.svn_repo) | 306 logging.warning('Using both leaking checkout and git dirty checkout') |
| 290 rmtree(self.svn_repo) | |
| 291 logging.debug('Removing dirty %s' % self.svn_checkout) | |
| 292 rmtree(self.svn_checkout) | |
| 293 else: | |
| 294 logging.warning('Using both leaking checkout and dirty checkout') | |
| 295 if self.gitdaemon and self.git_dirty: | |
| 296 logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) | |
| 297 self.gitdaemon.kill() | |
| 298 self.gitdaemon = None | |
| 299 if not self.SHOULD_LEAK: | |
| 300 logging.debug('Removing dirty %s' % self.git_root) | |
| 301 rmtree(self.git_root) | |
| 302 else: | |
| 303 logging.warning('Using both leaking checkout and dirty checkout') | |
| 304 | 307 |
| 305 def tearDown(self): | 308 def tearDown(self): |
| 309 self.tearDownSVN() | |
| 310 self.tearDownGIT() | |
| 311 if not self.SHOULD_LEAK: | |
| 312 logging.debug('Removing %s' % self.trial_dir()) | |
| 313 rmtree(self.trial_dir()) | |
| 314 | |
| 315 def tearDownSVN(self): | |
|
Evan Martin
2011/03/04 18:13:13
Also weird capitalization here.
| |
| 306 if self.svnserve: | 316 if self.svnserve: |
| 307 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) | 317 logging.debug('Killing svnserve pid %s' % self.svnserve.pid) |
| 308 self.svnserve.kill() | 318 self.svnserve.kill() |
| 319 self.wait_for_port_to_close(self.svn_port) | |
| 309 self.svnserve = None | 320 self.svnserve = None |
| 321 if not self.SHOULD_LEAK: | |
| 322 logging.debug('Removing %s' % self.svn_repo) | |
| 323 rmtree(self.svn_repo) | |
| 324 logging.debug('Removing %s' % self.svn_checkout) | |
| 325 rmtree(self.svn_checkout) | |
| 326 else: | |
| 327 return False | |
| 328 return True | |
| 329 | |
| 330 def tearDownGIT(self): | |
|
Evan Martin
2011/03/04 18:13:13
"Git" or "git", but never "GIT"
| |
| 310 if self.gitdaemon: | 331 if self.gitdaemon: |
| 311 logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) | 332 logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) |
| 312 self.gitdaemon.kill() | 333 self.gitdaemon.kill() |
| 313 self.gitdaemon = None | 334 self.gitdaemon = None |
| 314 if not self.SHOULD_LEAK: | 335 if self.gitpidfile: |
| 315 logging.debug('Removing %s' % self.trial_dir()) | 336 pid = int(self.gitpidfile.read()) |
| 316 rmtree(self.trial_dir()) | 337 self.gitpidfile.close() |
| 338 kill_pid(pid) | |
| 339 self.gitpidfile = None | |
| 340 self.wait_for_port_to_close(self.git_port) | |
| 341 if not self.SHOULD_LEAK: | |
| 342 logging.debug('Removing %s' % self.git_root) | |
| 343 rmtree(self.git_root) | |
| 344 else: | |
| 345 return False | |
| 346 return True | |
| 317 | 347 |
| 318 @staticmethod | 348 @staticmethod |
| 319 def _genTree(root, tree_dict): | 349 def _genTree(root, tree_dict): |
| 320 """For a dictionary of file contents, generate a filesystem.""" | 350 """For a dictionary of file contents, generate a filesystem.""" |
| 321 if not os.path.isdir(root): | 351 if not os.path.isdir(root): |
| 322 os.makedirs(root) | 352 os.makedirs(root) |
| 323 for (k, v) in tree_dict.iteritems(): | 353 for (k, v) in tree_dict.iteritems(): |
| 324 k_os = k.replace('/', os.sep) | 354 k_os = k.replace('/', os.sep) |
| 325 k_arr = k_os.split(os.sep) | 355 k_arr = k_os.split(os.sep) |
| 326 if len(k_arr) > 1: | 356 if len(k_arr) > 1: |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 347 'auth-access = write\n' | 377 'auth-access = write\n' |
| 348 'password-db = passwd\n') | 378 'password-db = passwd\n') |
| 349 text = '[users]\n' | 379 text = '[users]\n' |
| 350 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) | 380 text += ''.join('%s = %s\n' % (usr, pwd) for usr, pwd in self.USERS) |
| 351 write(join(self.svn_repo, 'conf', 'passwd'), text) | 381 write(join(self.svn_repo, 'conf', 'passwd'), text) |
| 352 | 382 |
| 353 # Start the daemon. | 383 # Start the daemon. |
| 354 cmd = ['svnserve', '-d', '--foreground', '-r', self.repos_dir] | 384 cmd = ['svnserve', '-d', '--foreground', '-r', self.repos_dir] |
| 355 if self.HOST == '127.0.0.1': | 385 if self.HOST == '127.0.0.1': |
| 356 cmd.append('--listen-host=127.0.0.1') | 386 cmd.append('--listen-host=127.0.0.1') |
| 387 self.check_port_is_free(self.svn_port) | |
| 357 self.svnserve = Popen(cmd, cwd=self.svn_repo) | 388 self.svnserve = Popen(cmd, cwd=self.svn_repo) |
| 389 self.wait_for_port_to_open(self.svn_port) | |
| 358 self.populateSvn() | 390 self.populateSvn() |
| 359 self.svn_dirty = False | 391 self.svn_dirty = False |
| 360 return True | 392 return True |
| 361 | 393 |
| 362 def setUpGIT(self): | 394 def setUpGIT(self): |
| 363 """Creates git repositories and start the servers.""" | 395 """Creates git repositories and start the servers.""" |
| 364 self.setUp() | 396 self.setUp() |
| 365 if self.gitdaemon: | 397 if self.gitdaemon: |
| 366 return True | 398 return True |
| 367 if sys.platform == 'win32': | 399 if sys.platform == 'win32': |
| 368 return False | 400 return False |
| 401 assert self.gitpidfile == None | |
| 369 for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: | 402 for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: |
| 370 check_call(['git', 'init', '-q', join(self.git_root, repo)]) | 403 check_call(['git', 'init', '-q', join(self.git_root, repo)]) |
| 371 self.git_hashes[repo] = [None] | 404 self.git_hashes[repo] = [None] |
| 405 # Unlike svn, populate git before starting the server. | |
| 372 self.populateGit() | 406 self.populateGit() |
| 373 # Start the daemon. | 407 # Start the daemon. |
| 374 cmd = ['git', 'daemon', '--export-all', '--base-path=' + self.repos_dir] | 408 self.gitpidfile = tempfile.NamedTemporaryFile() |
| 409 cmd = ['git', 'daemon', | |
| 410 '--export-all', | |
| 411 '--base-path=' + self.repos_dir, | |
| 412 '--pid-file=' + self.gitpidfile.name] | |
| 375 if self.HOST == '127.0.0.1': | 413 if self.HOST == '127.0.0.1': |
| 376 cmd.append('--listen=127.0.0.1') | 414 cmd.append('--listen=127.0.0.1') |
| 377 logging.debug(cmd) | 415 self.check_port_is_free(self.git_port) |
| 378 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) | 416 self.gitdaemon = Popen(cmd, cwd=self.repos_dir) |
| 417 self.wait_for_port_to_open(self.git_port) | |
| 379 self.git_dirty = False | 418 self.git_dirty = False |
| 380 return True | 419 return True |
| 381 | 420 |
| 382 def _commit_svn(self, tree): | 421 def _commit_svn(self, tree): |
| 383 self._genTree(self.svn_checkout, tree) | 422 self._genTree(self.svn_checkout, tree) |
| 384 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) | 423 commit_svn(self.svn_checkout, self.USERS[0][0], self.USERS[0][1]) |
| 385 if self.svn_revs and self.svn_revs[-1]: | 424 if self.svn_revs and self.svn_revs[-1]: |
| 386 new_tree = self.svn_revs[-1].copy() | 425 new_tree = self.svn_revs[-1].copy() |
| 387 new_tree.update(tree) | 426 new_tree.update(tree) |
| 388 else: | 427 else: |
| 389 new_tree = tree.copy() | 428 new_tree = tree.copy() |
| 390 self.svn_revs.append(new_tree) | 429 self.svn_revs.append(new_tree) |
| 391 | 430 |
| 392 def _commit_git(self, repo, tree): | 431 def _commit_git(self, repo, tree): |
| 393 repo_root = join(self.git_root, repo) | 432 repo_root = join(self.git_root, repo) |
| 394 self._genTree(repo_root, tree) | 433 self._genTree(repo_root, tree) |
| 395 commit_hash = commit_git(repo_root) | 434 commit_hash = commit_git(repo_root) |
| 396 if self.git_hashes[repo][-1]: | 435 if self.git_hashes[repo][-1]: |
| 397 new_tree = self.git_hashes[repo][-1][1].copy() | 436 new_tree = self.git_hashes[repo][-1][1].copy() |
| 398 new_tree.update(tree) | 437 new_tree.update(tree) |
| 399 else: | 438 else: |
| 400 new_tree = tree.copy() | 439 new_tree = tree.copy() |
| 401 self.git_hashes[repo].append((commit_hash, new_tree)) | 440 self.git_hashes[repo].append((commit_hash, new_tree)) |
| 402 | 441 |
| 442 def check_port_is_free(self, port): | |
| 443 sock = socket.socket() | |
| 444 try: | |
| 445 sock.connect((self.HOST, port)) | |
| 446 # It worked, throw. | |
| 447 logging.fatal('%d shouldn\'t be open' % port) | |
|
Evan Martin
2011/03/04 18:13:13
"open" is a confusing word here -- it means not-fr
M-A Ruel
2011/03/04 18:53:30
Renamed.
| |
| 448 assert False | |
| 449 except EnvironmentError: | |
| 450 pass | |
| 451 finally: | |
| 452 sock.close() | |
| 453 | |
| 454 def wait_for_port_to_open(self, port): | |
| 455 sock = socket.socket() | |
| 456 try: | |
| 457 while True: | |
| 458 try: | |
| 459 sock.connect((self.HOST, port)) | |
| 460 logging.debug('%d is now open' % port) | |
| 461 break | |
| 462 except EnvironmentError: | |
| 463 pass | |
| 464 logging.debug('%d not open yet' % port) | |
| 465 finally: | |
| 466 sock.close() | |
|
Evan Martin
2011/03/04 18:13:13
Should this have a timeout int it?
M-A Ruel
2011/03/04 18:53:30
Added a clamp at 30 seconds.
| |
| 467 | |
| 468 def wait_for_port_to_close(self, port): | |
| 469 while True: | |
| 470 try: | |
| 471 sock = socket.socket() | |
| 472 sock.connect((self.HOST, port)) | |
| 473 logging.debug('%d was open, waiting to close' % port) | |
|
Evan Martin
2011/03/04 18:13:13
Is the wait done by the sock.close()?
M-A Ruel
2011/03/04 18:53:30
There's no wait done, it loops as fast as it can,
| |
| 474 except EnvironmentError: | |
| 475 logging.debug('%d now closed' % port) | |
| 476 break | |
| 477 finally: | |
| 478 sock.close() | |
| 479 | |
| 403 def populateSvn(self): | 480 def populateSvn(self): |
| 404 raise NotImplementedError() | 481 raise NotImplementedError() |
| 405 | 482 |
| 406 def populateGit(self): | 483 def populateGit(self): |
| 407 raise NotImplementedError() | 484 raise NotImplementedError() |
| 408 | 485 |
| 409 | 486 |
| 410 class FakeRepos(FakeReposBase): | 487 class FakeRepos(FakeReposBase): |
| 411 """Implements populateSvn() and populateGit().""" | 488 """Implements populateSvn() and populateGit().""" |
| 412 NB_GIT_REPOS = 4 | 489 NB_GIT_REPOS = 4 |
| (...skipping 317 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 730 | 807 |
| 731 if '-l' in sys.argv: | 808 if '-l' in sys.argv: |
| 732 # See SHOULD_LEAK definition in FakeReposBase for its purpose. | 809 # See SHOULD_LEAK definition in FakeReposBase for its purpose. |
| 733 FakeReposBase.SHOULD_LEAK = True | 810 FakeReposBase.SHOULD_LEAK = True |
| 734 print 'Leaking!' | 811 print 'Leaking!' |
| 735 sys.argv.remove('-l') | 812 sys.argv.remove('-l') |
| 736 | 813 |
| 737 | 814 |
| 738 if __name__ == '__main__': | 815 if __name__ == '__main__': |
| 739 sys.exit(main(sys.argv)) | 816 sys.exit(main(sys.argv)) |
| OLD | NEW |