Index: tests/fake_repos.py |
diff --git a/tests/fake_repos.py b/tests/fake_repos.py |
index 8674aca56acf1f5a6ea2cbe23bec013a1623ccd8..5f1125603e13f8e944ab7337dcf95a33d0c2bd50 100755 |
--- a/tests/fake_repos.py |
+++ b/tests/fake_repos.py |
@@ -6,14 +6,17 @@ |
"""Generate fake repositories for testing.""" |
import atexit |
+import datetime |
import errno |
import logging |
import os |
import pprint |
import re |
+import socket |
import stat |
import subprocess |
import sys |
+import tempfile |
import time |
import unittest |
@@ -24,23 +27,43 @@ import scm |
## Utility functions |
-def addKill(): |
- """Add kill() method to subprocess.Popen for python <2.6""" |
- if getattr(subprocess.Popen, 'kill', None): |
+def kill_pid(pid): |
+ """Kills a process by its process id.""" |
+ try: |
+ # Unable to import 'module' |
+ # pylint: disable=F0401 |
+ import signal |
+ return os.kill(pid, signal.SIGKILL) |
+ except ImportError: |
+ pass |
+ |
+ |
+def kill_win(process): |
+ """Kills a process with its windows handle. |
+ |
+ Has no effect on other platforms. |
+ """ |
+ try: |
+ # Unable to import 'module' |
+ # pylint: disable=F0401 |
+ import win32process |
+ # Access to a protected member _handle of a client class |
+ # pylint: disable=W0212 |
+ return win32process.TerminateProcess(process._handle, -1) |
+ except ImportError: |
+ pass |
+ |
+ |
+def add_kill(): |
+ """Adds kill() method to subprocess.Popen for python <2.6""" |
+ if hasattr(subprocess.Popen, 'kill'): |
return |
- # Unable to import 'module' |
- # pylint: disable=F0401 |
+ |
if sys.platform == 'win32': |
- def kill_win(process): |
- import win32process |
- # Access to a protected member _handle of a client class |
- # pylint: disable=W0212 |
- return win32process.TerminateProcess(process._handle, -1) |
subprocess.Popen.kill = kill_win |
else: |
def kill_nix(process): |
- import signal |
- return os.kill(process.pid, signal.SIGKILL) |
+ return kill_pid(process.pid) |
Dirk Pranke
2011/03/04 21:15:17
I'd likely just change kill_pid to take process as
M-A Ruel
2011/03/04 21:18:21
I can't because I need to kill 'git-daemon' child
|
subprocess.Popen.kill = kill_nix |
@@ -250,11 +273,14 @@ class FakeReposBase(object): |
self.gitdaemon = None |
self.common_init = False |
self.repos_dir = None |
+ self.git_pid_file = None |
self.git_root = None |
self.svn_checkout = None |
self.svn_repo = None |
self.git_dirty = False |
self.svn_dirty = False |
+ self.svn_port = 3690 |
+ self.git_port = 9418 |
def trial_dir(self): |
if not self.TRIAL_DIR: |
@@ -262,7 +288,7 @@ class FakeReposBase(object): |
os.path.dirname(os.path.abspath(__file__)), '_trial') |
return self.TRIAL_DIR |
- def setUp(self): |
+ def set_up(self): |
"""All late initialization comes here. |
Note that it deletes all trial_dir() and not only repos_dir. |
@@ -274,46 +300,59 @@ class FakeReposBase(object): |
self.git_root = join(self.repos_dir, 'git') |
self.svn_checkout = join(self.repos_dir, 'svn_checkout') |
self.svn_repo = join(self.repos_dir, 'svn') |
- addKill() |
+ add_kill() |
rmtree(self.trial_dir()) |
os.makedirs(self.repos_dir) |
- atexit.register(self.tearDown) |
+ atexit.register(self.tear_down) |
def cleanup_dirt(self): |
"""For each dirty repository, regenerate it.""" |
- if self.svnserve and self.svn_dirty: |
+ if self.svn_dirty: |
+ if not self.tear_down_svn(): |
+ logging.warning('Using both leaking checkout and svn dirty checkout') |
+ if self.git_dirty: |
+ if not self.tear_down_git(): |
+ logging.warning('Using both leaking checkout and git dirty checkout') |
+ |
+ def tear_down(self): |
+ self.tear_down_svn() |
+ self.tear_down_git() |
+ if not self.SHOULD_LEAK: |
+ logging.debug('Removing %s' % self.trial_dir()) |
+ rmtree(self.trial_dir()) |
+ |
+ def tear_down_svn(self): |
+ if self.svnserve: |
logging.debug('Killing svnserve pid %s' % self.svnserve.pid) |
self.svnserve.kill() |
+ self.wait_for_port_to_free(self.svn_port) |
self.svnserve = None |
if not self.SHOULD_LEAK: |
- logging.debug('Removing dirty %s' % self.svn_repo) |
+ logging.debug('Removing %s' % self.svn_repo) |
rmtree(self.svn_repo) |
- logging.debug('Removing dirty %s' % self.svn_checkout) |
+ logging.debug('Removing %s' % self.svn_checkout) |
rmtree(self.svn_checkout) |
else: |
- logging.warning('Using both leaking checkout and dirty checkout') |
- if self.gitdaemon and self.git_dirty: |
+ return False |
+ return True |
+ |
+ def tear_down_git(self): |
+ if self.gitdaemon: |
logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) |
self.gitdaemon.kill() |
self.gitdaemon = None |
+ if self.git_pid_file: |
+ pid = int(self.git_pid_file.read()) |
+ self.git_pid_file.close() |
+ kill_pid(pid) |
+ self.git_pid_file = None |
+ self.wait_for_port_to_free(self.git_port) |
if not self.SHOULD_LEAK: |
- logging.debug('Removing dirty %s' % self.git_root) |
+ logging.debug('Removing %s' % self.git_root) |
rmtree(self.git_root) |
else: |
- logging.warning('Using both leaking checkout and dirty checkout') |
- |
- def tearDown(self): |
- if self.svnserve: |
- logging.debug('Killing svnserve pid %s' % self.svnserve.pid) |
- self.svnserve.kill() |
- self.svnserve = None |
- if self.gitdaemon: |
- logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid) |
- self.gitdaemon.kill() |
- self.gitdaemon = None |
- if not self.SHOULD_LEAK: |
- logging.debug('Removing %s' % self.trial_dir()) |
- rmtree(self.trial_dir()) |
+ return False |
+ return True |
@staticmethod |
def _genTree(root, tree_dict): |
@@ -332,9 +371,9 @@ class FakeReposBase(object): |
else: |
write(join(root, k), v) |
- def setUpSVN(self): |
+ def set_up_svn(self): |
"""Creates subversion repositories and start the servers.""" |
- self.setUp() |
+ self.set_up() |
if self.svnserve: |
return True |
try: |
@@ -354,28 +393,38 @@ class FakeReposBase(object): |
cmd = ['svnserve', '-d', '--foreground', '-r', self.repos_dir] |
if self.HOST == '127.0.0.1': |
cmd.append('--listen-host=127.0.0.1') |
+ self.check_port_is_free(self.svn_port) |
self.svnserve = Popen(cmd, cwd=self.svn_repo) |
+ self.wait_for_port_to_bind(self.svn_port, self.svnserve) |
self.populateSvn() |
self.svn_dirty = False |
return True |
- def setUpGIT(self): |
+ def set_up_git(self): |
"""Creates git repositories and start the servers.""" |
- self.setUp() |
+ self.set_up() |
if self.gitdaemon: |
return True |
if sys.platform == 'win32': |
return False |
+ assert self.git_pid_file == None |
for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]: |
check_call(['git', 'init', '-q', join(self.git_root, repo)]) |
self.git_hashes[repo] = [None] |
+ # Unlike svn, populate git before starting the server. |
self.populateGit() |
# Start the daemon. |
- cmd = ['git', 'daemon', '--export-all', '--base-path=' + self.repos_dir] |
+ self.git_pid_file = tempfile.NamedTemporaryFile() |
+ cmd = ['git', 'daemon', |
+ '--export-all', |
+ '--reuseaddr', |
+ '--base-path=' + self.repos_dir, |
+ '--pid-file=' + self.git_pid_file.name] |
if self.HOST == '127.0.0.1': |
cmd.append('--listen=127.0.0.1') |
- logging.debug(cmd) |
+ self.check_port_is_free(self.git_port) |
self.gitdaemon = Popen(cmd, cwd=self.repos_dir) |
+ self.wait_for_port_to_bind(self.git_port, self.gitdaemon) |
self.git_dirty = False |
return True |
@@ -400,6 +449,52 @@ class FakeReposBase(object): |
new_tree = tree.copy() |
self.git_hashes[repo].append((commit_hash, new_tree)) |
+ def check_port_is_free(self, port): |
+ sock = socket.socket() |
+ try: |
+ sock.connect((self.HOST, port)) |
+ # It worked, throw. |
+ assert False, '%d shouldn\'t be bound' % port |
+ except EnvironmentError: |
+ pass |
+ finally: |
+ sock.close() |
+ |
+ def wait_for_port_to_bind(self, port, process): |
+ sock = socket.socket() |
+ try: |
+ start = datetime.datetime.utcnow() |
Dirk Pranke
2011/03/04 21:15:17
Curious why you're using datetime instead of just
M-A Ruel
2011/03/04 21:18:21
Habit, no real reason.
|
+ maxdelay = datetime.timedelta(seconds=30) |
+ while (datetime.datetime.utcnow() - start) < maxdelay: |
+ try: |
+ sock.connect((self.HOST, port)) |
+ logging.debug('%d is now bound' % port) |
+ return |
+ except EnvironmentError: |
+ pass |
+ logging.debug('%d is still not bound' % port) |
+ finally: |
+ sock.close() |
+ # The process failed to bind. Kill it and dump its ouput. |
+ process.kill() |
+ logging.error('%s' % process.communicate()[0]) |
+ assert False, '%d is still not bound' % port |
+ |
+ def wait_for_port_to_free(self, port): |
+ start = datetime.datetime.utcnow() |
+ maxdelay = datetime.timedelta(seconds=30) |
+ while (datetime.datetime.utcnow() - start) < maxdelay: |
+ try: |
+ sock = socket.socket() |
+ sock.connect((self.HOST, port)) |
+ logging.debug('%d was bound, waiting to free' % port) |
+ except EnvironmentError: |
+ logging.debug('%d now free' % port) |
+ return |
+ finally: |
+ sock.close() |
+ assert False, '%d is still bound' % port |
+ |
def populateSvn(self): |
raise NotImplementedError() |
@@ -635,7 +730,7 @@ class FakeReposTestBase(unittest.TestCase): |
def setUp(self): |
unittest.TestCase.setUp(self) |
- self.FAKE_REPOS.setUp() |
+ self.FAKE_REPOS.set_up() |
# Remove left overs and start fresh. |
if not self.CLASS_ROOT_DIR: |
@@ -720,7 +815,8 @@ def main(argv): |
fake = FakeRepos() |
print 'Using %s' % fake.trial_dir() |
try: |
- fake.setUp() |
+ fake.set_up_svn() |
+ fake.set_up_git() |
print('Fake setup, press enter to quit or Ctrl-C to keep the checkouts.') |
sys.stdin.readline() |
except KeyboardInterrupt: |