diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2009-11-04 19:53:42 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2009-11-04 19:53:42 +0100 |
commit | d884adc80c80300b4cc05321494713904ef1df2d (patch) | |
tree | 3878d5e0282531596d42505d8725482dde002c20 /test/testlib/helper.py | |
parent | 05d2687afcc78cd192714ee3d71fdf36a37d110f (diff) | |
parent | ace1fed6321bb8dd6d38b2f58d7cf815fa16db7a (diff) | |
download | gitpython-d884adc80c80300b4cc05321494713904ef1df2d.tar.gz |
Merge branch 'improvements'
Diffstat (limited to 'test/testlib/helper.py')
-rw-r--r-- | test/testlib/helper.py | 196 |
1 files changed, 192 insertions, 4 deletions
diff --git a/test/testlib/helper.py b/test/testlib/helper.py index b66d3eaa..081299be 100644 --- a/test/testlib/helper.py +++ b/test/testlib/helper.py @@ -5,6 +5,10 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os +from git import Repo, Remote +from unittest import TestCase +import tempfile +import shutil GIT_REPO = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -23,8 +27,192 @@ class ListProcessAdapter(object): """Allows to use lists as Process object as returned by SubProcess.Popen. Its tailored to work with the test system only""" + class Stream(object): + """Simple stream emulater meant to work only with tests""" + def __init__(self, data): + self.data = data + self.cur_iter = None + + def __iter__(self): + dat = self.data + if isinstance(dat, basestring): + dat = dat.splitlines() + if self.cur_iter is None: + self.cur_iter = iter(dat) + return self.cur_iter + + def read(self): + dat = self.data + if isinstance(dat, (tuple,list)): + dat = "\n".join(dat) + return dat + + def next(self): + if self.cur_iter is None: + self.cur_iter = iter(self) + return self.cur_iter.next() + + # END stream + def __init__(self, input_list_or_string): - l = input_list_or_string - if isinstance(l,basestring): - l = l.splitlines() - self.stdout = iter(l) + self.stdout = self.Stream(input_list_or_string) + self.stderr = self.Stream('') + + def wait(self): + return 0 + + poll = wait + + +def with_bare_rw_repo(func): + """ + Decorator providing a specially made read-write repository to the test case + decorated with it. The test case requires the following signature:: + def case(self, rw_repo) + + The rwrepo will be a bare clone or the types rorepo. Once the method finishes, + it will be removed completely. + + Use this if you want to make purely index based adjustments, change refs, create + heads, generally operations that do not need a working tree. + """ + def bare_repo_creator(self): + repo_dir = tempfile.mktemp("bare_repo") + rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=True) + try: + return func(self, rw_repo) + finally: + shutil.rmtree(repo_dir) + # END cleanup + # END bare repo creator + bare_repo_creator.__name__ = func.__name__ + return bare_repo_creator + +def with_rw_repo(working_tree_ref): + """ + Same as with_bare_repo, but clones the rorepo as non-bare repository, checking + out the working tree at the given working_tree_ref. + + This repository type is more costly due to the working copy checkout. + """ + assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout" + def argument_passer(func): + def repo_creator(self): + repo_dir = tempfile.mktemp("non_bare_repo") + rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=False, n=True) + rw_repo.git.checkout("-b", "master", working_tree_ref) + try: + return func(self, rw_repo) + finally: + shutil.rmtree(repo_dir) + # END cleanup + # END rw repo creator + repo_creator.__name__ = func.__name__ + return repo_creator + # END argument passer + return argument_passer + +def with_rw_and_rw_remote_repo(working_tree_ref): + """ + Same as with_rw_repo, but also provides a writable remote repository from which the + rw_repo has been forked as well as a handle for a git-daemon that may be started to + run the remote_repo. + The remote repository was cloned as bare repository from the rorepo, wheras + the rw repo has a working tree and was cloned from the remote repository. + + remote_repo has two remotes: origin and daemon_origin. One uses a local url, + the other uses a server url. The daemon setup must be done on system level + and should be an inetd service that serves tempdir.gettempdir() and all + directories in it. + + The following scetch demonstrates this:: + rorepo ---<bare clone>---> rw_remote_repo ---<clone>---> rw_repo + + The test case needs to support the following signature:: + def case(self, rw_repo, rw_remote_repo) + + This setup allows you to test push and pull scenarios and hooks nicely. + """ + assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout" + def argument_passer(func): + def remote_repo_creator(self): + remote_repo_dir = tempfile.mktemp("remote_repo") + repo_dir = tempfile.mktemp("remote_clone_non_bare_repo") + + rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True) + rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True) # recursive alternates info ? + rw_repo.git.checkout("-b", "master", working_tree_ref) + + # prepare for git-daemon + rw_remote_repo.daemon_export = True + + # this thing is just annoying ! + crw = rw_remote_repo.config_writer() + section = "daemon" + try: + crw.add_section(section) + except Exception: + pass + crw.set(section, "receivepack", True) + # release lock + del(crw) + + # initialize the remote - first do it as local remote and pull, then + # we change the url to point to the daemon. The daemon should be started + # by the user, not by us + d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir) + d_remote.fetch() + d_remote.config_writer.set('url', "git://localhost%s" % remote_repo_dir) + + try: + return func(self, rw_repo, rw_remote_repo) + finally: + shutil.rmtree(repo_dir) + shutil.rmtree(remote_repo_dir) + # END cleanup + # END bare repo creator + remote_repo_creator.__name__ = func.__name__ + return remote_repo_creator + # END remote repo creator + # END argument parsser + + return argument_passer + + +class TestBase(TestCase): + """ + Base Class providing default functionality to all tests such as: + + - Utility functions provided by the TestCase base of the unittest method such as:: + self.fail("todo") + self.failUnlessRaises(...) + + - Class level repository which is considered read-only as it is shared among + all test cases in your type. + Access it using:: + self.rorepo # 'ro' stands for read-only + + The rorepo is in fact your current project's git repo. If you refer to specific + shas for your objects, be sure you choose some that are part of the immutable portion + of the project history ( to assure tests don't fail for others ). + """ + + @classmethod + def setUpAll(cls): + """ + Dynamically add a read-only repository to our actual type. This way + each test type has its own repository + """ + cls.rorepo = Repo(GIT_REPO) + + def _make_file(self, rela_path, data, repo=None): + """ + Create a file at the given path relative to our repository, filled + with the given data. Returns absolute path to created file. + """ + repo = repo or self.rorepo + abs_path = os.path.join(repo.git.git_dir, rela_path) + fp = open(abs_path, "w") + fp.write(data) + fp.close() + return abs_path |