1#! /usr/bin/python
2import logging, mox, os, shutil, tempfile, unittest, utils
3
4# This makes autotest_lib imports available.
5import common
6from autotest_lib.client.common_lib import revision_control
7
8
9class GitRepoManager(object):
10    """
11    A wrapper for GitRepo.
12    """
13    commit_hash = None
14    commit_msg = None
15    repodir = None
16    git_repo_manager = None
17
18
19    def __init__(self, master_repo=None):
20        """
21        Setup self.git_repo_manager.
22
23        If a master_repo is present clone it.
24        Otherwise create a directory in /tmp and init it.
25
26        @param master_repo: GitRepo representing master.
27        """
28        if master_repo is None:
29            self.repodir = tempfile.mktemp(suffix='master')
30            self._create_git_repo(self.repodir)
31            self.git_repo_manager = revision_control.GitRepo(
32                                        self.repodir,
33                                        self.repodir,
34                                        abs_work_tree=self.repodir)
35            self._setup_git_environment()
36            # Create an initial commit. We really care about the common case
37            # where there exists a commit in the upstream repo.
38            self._edit('initial_commit_file', 'is_non_empty')
39            self.add()
40            self.commit('initial_commit')
41        else:
42            self.repodir = tempfile.mktemp(suffix='dependent')
43            self.git_repo_manager = revision_control.GitRepo(
44                                      self.repodir,
45                                      master_repo.repodir,
46                                      abs_work_tree=self.repodir)
47            self.git_repo_manager.clone()
48            self._setup_git_environment()
49
50
51    def _setup_git_environment(self):
52        """
53        Mock out basic git environment to keep tests deterministic.
54        """
55        # Set user and email for the test git checkout.
56        self.git_repo_manager.gitcmd('config user.name Unittests')
57        self.git_repo_manager.gitcmd('config user.email utests@chromium.org')
58
59
60    def _edit(self, filename='foo', msg='bar'):
61        """
62        Write msg into a file in the repodir.
63
64        @param filename: Name of the file in current repo.
65                If none exists one will be created.
66        @param msg: A message to write into the file.
67        """
68        local_file_name = os.path.join(self.git_repo_manager.repodir,
69                                       filename)
70        with open(local_file_name, 'w') as f:
71            f.write(msg)
72
73
74    def _create_git_repo(self, repodir):
75        """
76        Init a new git repository.
77
78        @param repodir: directory for repo.
79        """
80        logging.info('initializing git repo in: %s', repodir)
81        gitcmd = 'git init %s' % repodir
82        rv = utils.run(gitcmd)
83        if rv.exit_status != 0:
84            logging.error(rv.stderr)
85            raise revision_control.revision_control.GitError(gitcmd + 'failed')
86
87
88    def add(self):
89        """
90        Add all unadded files in repodir to repo.
91        """
92        rv = self.git_repo_manager.gitcmd('add .')
93        if rv.exit_status != 0:
94            logging.error(rv.stderr)
95            raise revision_control.GitError('Unable to add files to repo', rv)
96
97
98    def commit(self, msg='default'):
99        """
100        Commit changes to repo with the supplied commit msg.
101        Also updates commit_hash with the hash for this commit.
102
103        @param msg: A message that goes with the commit.
104        """
105        self.git_repo_manager.commit(msg)
106        self.commit_hash = self.git_repo_manager.get_latest_commit_hash()
107
108
109    def get_master_tot(self):
110        """
111        Get everything from masters TOT squashing local changes.
112        If the dependent repo is empty pull from master.
113        """
114        self.git_repo_manager.reinit_repo_at('master')
115        self.commit_hash = self.git_repo_manager.get_latest_commit_hash()
116
117
118class RevisionControlUnittest(mox.MoxTestBase):
119    """
120    A unittest to exercise build_externals.py's usage
121    of revision_control.py's Git wrappers.
122    """
123    master_repo=None
124    dependent_repo=None
125
126    def setUp(self):
127        """
128        Create a master repo and clone it into a dependent repo.
129        """
130        super(RevisionControlUnittest, self).setUp()
131        self.master_repo = GitRepoManager()
132        self.dependent_repo = GitRepoManager(self.master_repo)
133
134
135    def tearDown(self):
136        """
137        Delete temporary directories.
138        """
139        shutil.rmtree(self.master_repo.repodir)
140        shutil.rmtree(self.dependent_repo.repodir)
141        super(RevisionControlUnittest, self).tearDown()
142
143
144    def testCommit(self):
145        """
146        Test add, commit, pull, clone.
147        """
148        self.master_repo._edit()
149        self.master_repo.add()
150        self.master_repo.commit()
151        self.dependent_repo.get_master_tot()
152        self.assertEquals(self.dependent_repo.commit_hash,
153            self.master_repo.commit_hash,
154            msg=(("hashes don't match after clone, master and dependent repo"
155                  "out of sync: %r != %r") %
156                  (self.dependent_repo.commit_hash,
157                   self.master_repo.commit_hash)))
158
159        self.master_repo._edit(msg='foobar')
160        self.master_repo.commit()
161        self.dependent_repo.get_master_tot()
162        self.assertEquals(self.dependent_repo.commit_hash,
163            self.master_repo.commit_hash,
164            msg=(("hashes don't match after pull, master and dependent repo"
165                  "out of sync: %r != %r") %
166                  (self.dependent_repo.commit_hash,
167                   self.master_repo.commit_hash)))
168
169
170    def testGitUrlClone(self):
171        """
172        Test that git clone raises a ValueError if giturl is unset.
173        """
174        self.dependent_repo.git_repo_manager._giturl = None
175        self.assertRaises(ValueError,
176                          self.dependent_repo.git_repo_manager.clone)
177
178
179    def testGitUrlPull(self):
180        """
181        Test that git pull raises a ValueError if giturl is unset.
182        """
183        self.dependent_repo.git_repo_manager._giturl = None
184        self.assertRaises(ValueError,
185                          self.dependent_repo.git_repo_manager.pull)
186
187
188    def testGitUrlFetch(self):
189        """
190        Test that git fetch raises a ValueError if giturl is unset.
191        """
192        self.dependent_repo.git_repo_manager._giturl = None
193        self.assertRaises(ValueError,
194                          self.dependent_repo.git_repo_manager.fetch_remote)
195
196
197if __name__ == '__main__':
198  unittest.main()
199