Merge lp:~gnuoy/charm-helpers/add-vcsdirs-module into lp:charm-helpers

Proposed by Liam Young
Status: Needs review
Proposed branch: lp:~gnuoy/charm-helpers/add-vcsdirs-module
Merge into: lp:charm-helpers
Diff against target: 510 lines (+498/-0)
2 files modified
charmhelpers/contrib/vcsdir/__init__.py (+208/-0)
tests/contrib/vcsdir/test_vcsdir.py (+290/-0)
To merge this branch: bzr merge lp:~gnuoy/charm-helpers/add-vcsdirs-module
Reviewer Review Type Date Requested Status
Liam Young (community) Needs Resubmitting
Matthew Wedgwood (community) Needs Information
Review via email: mp+177894@code.launchpad.net

Description of the change

Code to manage a payload from a version control system by creating
a directory for each revision and using a symlink to point at the
desired directory which an application server can then reference
e.g.
Point apache/gunicorn etc at /srv/code/my-project and deploy
payload from charm:

deploy_base = VcsDirs('/srv/code', 'my-project')
deploy_base.deploy_dir('/var/lib/juju/charm/new-code')

If r8 was in /var/lib/juju/charm/new-code then this would result in:
$ ls -l /srv/code
total 4
lrwxrwxrwx 1 liam liam 23 Jul 31 16:51 my-project -> /srv/code/my-project-8
drwxrwxr-x 3 liam liam 4096 Jul 31 16:41 my-project-8

Old directories can be removed with prune_dirs e.g.
deploy_base.prune_dirs(keep_dirs=2, order_by_revno=True)

To post a comment you must log in.
Revision history for this message
Matthew Wedgwood (mew) wrote :

This looks awesome, Liam. Thanks! As this is a contrib feature, I'm game for merging it as-is, but I see a few things that are worth discussing first.

In _get_vcs_identifier, I think it would be best to catch exceptions a bit more specifically. If the user doesn't have the git module, for example, they might get back a generic "Exception" with no description of what went wrong. It would be helpful to provide a description when you throw the exception (e.g. Exception("Something bad happened")). Also, if the srcdir is neither a git nor bzr repo, the function simply returns None. That looks problematic in the places it's used.

In VcsDirs.deploy_dir() (line 174), I see:

   if check_newer and current_branchid >= int(candidate_branchid):

I know git isn't your focus, but I'm pretty sure that the branchids won't be comparable as ints. If fixing that isn't trivial, I certainly wouldn't begrudge you to remove git support for this merge. Also (trivial), I believe the "check_newer" boolean in this line is redundant.

As for the body of this conditional, did you intend to do anything besides report? I would have expected it to do the deploy only if the new code is newer.

Finally, I see that you move the code from its original place rather than copying (or preferably rsync'ing) it. I wonder if that might be somewhat surprising to users. I think it bears mentioning in the docs at least.

review: Needs Information
Revision history for this message
Liam Young (gnuoy) wrote :

> This looks awesome, Liam. Thanks! As this is a contrib feature, I'm game for
> merging it as-is, but I see a few things that are worth discussing first.
>
> In _get_vcs_identifier, I think it would be best to catch exceptions a bit
> more specifically. If the user doesn't have the git module, for example, they
> might get back a generic "Exception" with no description of what went wrong.
> It would be helpful to provide a description when you throw the exception
> (e.g. Exception("Something bad happened")). Also, if the srcdir is neither a
> git nor bzr repo, the function simply returns None. That looks problematic in
> the places it's used.
>

This was intentional as _get_vcs_identifier tries to autodetect which vcs system is in use. If it gets an exception because a particular vcs module isn't available or because the directory is not under the control of that vcs system then it moves on and tries the next one, hence not raising an exception back to the user. Do you think this approach is flawed ? or would it be ok to just make sure that if no vcs system is detected an exception, with useful message, is raised ?

> In VcsDirs.deploy_dir() (line 174), I see:
>
> if check_newer and current_branchid >= int(candidate_branchid):
>
> I know git isn't your focus, but I'm pretty sure that the branchids won't be
> comparable as ints. If fixing that isn't trivial, I certainly wouldn't
> begrudge you to remove git support for this merge. Also (trivial), I believe
> the "check_newer" boolean in this line is redundant.

I left it up to the user to decide whether to compare the branchids based on the vcs system they're using. If the caller sets check_newer to be true when using git it will fail ( and I agree current_branchid >= int(candidate_branchid) does not look massively useful). Perhaps I need to take the decision about whether to compare ids away from the user and have it be dictated by the vcs system (bzr => yes, git => no).

>
> As for the body of this conditional, did you intend to do anything besides
> report? I would have expected it to do the deploy only if the new code is
> newer.

If using bzr I'd expect check_newer to be set to true and so only a newer revno of the code is deployed. As for git, I decided not to worry and just deploy what is given

>
> Finally, I see that you move the code from its original place rather than
> copying (or preferably rsync'ing) it. I wonder if that might be somewhat
> surprising to users. I think it bears mentioning in the docs at least.

That makes sense, I'll take a look at rsync'ing it.

Revision history for this message
Matthew Wedgwood (mew) wrote :

> > In _get_vcs_identifier, I think it would be best to catch exceptions a bit
> > more specifically. If the user doesn't have the git module, for example, they
> > might get back a generic "Exception" with no description of what went wrong.
> > It would be helpful to provide a description when you throw the exception
> > (e.g. Exception("Something bad happened")). Also, if the srcdir is neither a
> > git nor bzr repo, the function simply returns None. That looks problematic in
> > the places it's used.
>
> This was intentional as _get_vcs_identifier tries to autodetect which vcs
> system is in use. If it gets an exception because a particular vcs module
> isn't available or because the directory is not under the control of that vcs
> system then it moves on and tries the next one, hence not raising an exception
> back to the user. Do you think this approach is flawed ? or would it be ok to
> just make sure that if no vcs system is detected an exception, with useful
> message, is raised ?

I guess my main concern is that there will be no way to distinguish between those errors. They'll simply come out as a traceback ending in "raise Exception" necessitating a look at the code to figure out what went wrong. What I would expect is a message along the lines of "<dir> is not a VCS repository. Tried <repo types>."

> > In VcsDirs.deploy_dir() (line 174), I see:
> >
> > if check_newer and current_branchid >= int(candidate_branchid):
> >
> > I know git isn't your focus, but I'm pretty sure that the branchids won't be
> > comparable as ints. If fixing that isn't trivial, I certainly wouldn't
> > begrudge you to remove git support for this merge. Also (trivial), I believe
> > the "check_newer" boolean in this line is redundant.
>
> I left it up to the user to decide whether to compare the branchids based on
> the vcs system they're using. If the caller sets check_newer to be true when
> using git it will fail ( and I agree current_branchid >=
> int(candidate_branchid) does not look massively useful). Perhaps I need to
> take the decision about whether to compare ids away from the user and have it
> be dictated by the vcs system (bzr => yes, git => no).

I think it's useful to provide the option, as it seems quite helpful to have the library refuse to activate an older version unless it's explicitly asked to. It's still possible to compare branch IDs in git, but you have to use 'git rev-list' and work it out.

66. By Liam Young

Added support for comparing Git directories by the committed date of the head commit

67. By Liam Young

Switched to using rsync rather than move for copying src directories

Revision history for this message
Liam Young (gnuoy) wrote :

I've added the following:
1) A message with the exception if _get_vcs_identifier fails to find the vcs system for the directory
2) Added support for comparing Git directories by the committed date of the head commit
3) Switched to using rsync for populating directories rather than move.

review: Needs Resubmitting

Unmerged revisions

67. By Liam Young

Switched to using rsync rather than move for copying src directories

66. By Liam Young

Added support for comparing Git directories by the committed date of the head commit

65. By Liam Young

Add VcsDirs module for managing a payload from a version control system by creating a directory for each revision

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'charmhelpers/contrib/vcsdir'
2=== added file 'charmhelpers/contrib/vcsdir/__init__.py'
3--- charmhelpers/contrib/vcsdir/__init__.py 1970-01-01 00:00:00 +0000
4+++ charmhelpers/contrib/vcsdir/__init__.py 2013-08-28 15:57:15 +0000
5@@ -0,0 +1,208 @@
6+#
7+# Code to manage a payload from a version control system by creating
8+# a directory for each revision and using a symlink to point at the
9+# desired directory which an application server can then reference
10+# e.g.
11+# Point apache/gunicorn etc at /srv/code/my-project and deploy
12+# payload from charm:
13+#
14+# deploy_base = VcsDirs('/srv/code', 'my-project')
15+# deploy_base.deploy_dir('/var/lib/juju/charm/new-code')
16+#
17+# If r8 was in /var/lib/juju/charm/new-code then this would result in:
18+# $ ls -l /srv/code
19+# total 4
20+# lrwxrwxrwx 1 liam liam 23 Jul 31 16:51 my-project -> /srv/code/my-project-8
21+# drwxrwxr-x 3 liam liam 4096 Jul 31 16:41 my-project-8
22+#
23+# Old directories can be removed with prune_dirs e.g.
24+# deploy_base.prune_dirs(keep_dirs=2, order_by_revno=True)
25+
26+
27+import glob
28+import os
29+import shutil
30+from charmhelpers.core import host
31+
32+
33+def _get_vcs_identifier(source):
34+ """Return identifier for branch and a value which can be
35+ used to inspect chronological order of branches"""
36+ try:
37+ from git import Repo
38+ repo = Repo(source)
39+ # Return sha for the branch id and use commited date for ordering with
40+ # other branches
41+ return (repo.head.commit.hexsha, repo.head.commit.committed_date)
42+ except:
43+ pass
44+ try:
45+ from bzrlib.branch import Branch
46+ current_branch = Branch.open(source)
47+ # Return branch revno for both the branch id and also use it for
48+ # ordering with other branches
49+ return (current_branch.revno(), current_branch.revno())
50+ except:
51+ raise Exception(source + " is not a VCS controlled repository. Tried Git and Bzr")
52+
53+
54+class VcsDir():
55+ """A VcsDir is a directory which is managed by a vcs system and has a name
56+ in format codename-branchid. Where branchid is the id for the
57+ branch e.g. if using bzr it is the bzr revno."""
58+ def __init__(self, dir):
59+ self.fqdir = dir
60+ self.basename = os.path.basename(self.fqdir)
61+ self.dirname = os.path.dirname(self.fqdir)
62+ if os.path.exists(self.fqdir) and not os.path.isdir(self.fqdir):
63+ raise Exception
64+
65+ def get_fqdir(self):
66+ """Get the full path to the directory"""
67+ return self.fqdir
68+
69+ def get_basename(self):
70+ """Return directory without path"""
71+ return self.basename
72+
73+ def get_codename(self):
74+ """Return the project codename"""
75+ return self.basename.rpartition('-')[0]
76+
77+ def get_branchid(self):
78+ """Return the vcs identifier for the branch"""
79+ return self.basename.rsplit('-')[-1]
80+
81+ def get_comparator(self):
82+ """Return the vcs comparator for the branch"""
83+ (_, vcs_comparator) = _get_vcs_identifier(self.fqdir)
84+ return vcs_comparator
85+
86+ def validate_name(self):
87+ """Check the branchid in the dir name matches the code branchid"""
88+ (vcs_identifier, _) = _get_vcs_identifier(self.fqdir)
89+ if str(self.get_branchid()) == str(vcs_identifier):
90+ return True
91+ else:
92+ return False
93+
94+
95+class VcsDirs():
96+ """A collection of VcsDir directories with a symlink pointing at the
97+ desired directory which an application (apache, gunicorn etc) can
98+ use"""
99+ def __init__(self, dir, code_name):
100+ self.fqdir = dir
101+ self.code_name = code_name
102+ self.dir_glob = os.path.join(self.fqdir, code_name + '-*')
103+
104+ def add_code_symlink(self, branchid):
105+ """Create a symlink pointing at the requestred vcsdir"""
106+ symlink = self.get_currentcode_symlink()
107+ source_dir = self.get_directory_by_branchid(branchid)
108+ if os.path.islink(symlink):
109+ os.remove(symlink)
110+ os.symlink(source_dir, symlink)
111+
112+ def get_currentcode_symlink(self):
113+ """Return full path to currentcode symlink"""
114+ return os.path.join(self.fqdir, self.code_name)
115+
116+ def get_currentcode_symlink_target(self):
117+ """Return the target of the currentcode symlink"""
118+ return os.path.realpath(self.get_currentcode_symlink())
119+
120+ def get_currentcode_branchid(self):
121+ """Return the branchid of the vcsdir that the currentcode symlinki is
122+ currently pointing at"""
123+ new_vcsdir = VcsDir(self.get_currentcode_symlink_target())
124+ branchid = new_vcsdir.get_branchid()
125+ return branchid
126+
127+ def get_branchids(self):
128+ """Get a list of all the branchids available"""
129+ branchids = set()
130+ for direc in glob.glob(self.dir_glob):
131+ if os.path.isdir(direc):
132+ branchids.add(VcsDir(direc).get_branchid())
133+ return branchids
134+
135+ def get_directories_key_branchid(self):
136+ """Get a hash of all the vcs directories with branchid as the key"""
137+ branch_ids = {}
138+ for direc in glob.glob(self.dir_glob):
139+ if os.path.isdir(direc):
140+ new_vcsdir = VcsDir(direc)
141+ new_vcsdir.validate_name()
142+ branchid = new_vcsdir.get_branchid()
143+ if branchid in branch_ids:
144+ continue
145+ else:
146+ branch_ids[branchid] = direc
147+ return branch_ids
148+
149+ def get_directories_key_comparator(self):
150+ """Get the comparator of all the vcs directories with branchid as
151+ the key"""
152+ branch_comps = {}
153+ for direc in glob.glob(self.dir_glob):
154+ if os.path.isdir(direc):
155+ new_vcsdir = VcsDir(direc)
156+ branch_comp = new_vcsdir.get_comparator()
157+ if branch_comp in branch_comps:
158+ continue
159+ else:
160+ branch_comps[branch_comp] = direc
161+ return branch_comps
162+
163+ def get_directory_by_branchid(self, branchid):
164+ """Return the directory for a given branchid"""
165+ branch_ids = self.get_directories_key_branchid()
166+ if branchid in branch_ids:
167+ return branch_ids[branchid]
168+ return None
169+
170+ def is_branchid_present(self, branchid):
171+ """Is a directory containing a particular branchid present """
172+ if branchid in self.get_branchids():
173+ return True
174+ return False
175+
176+ def prune_dirs(self, keep_dirs=3, order_by_revno=False):
177+ """Delete the oldest 'keep_dirs' number of directories. Delete the
178+ oldest by directory mtime unless order_by_revno is True"""
179+ delete_count = 0
180+ direc = self.get_directories_key_comparator()
181+ number_to_delete = len(direc) - keep_dirs
182+ if order_by_revno:
183+ sorted_ids = sorted(direc.iterkeys())
184+ else:
185+ branchid_dates = [(branchid, os.stat(direc[branchid]).st_mtime)
186+ for branchid in direc.keys()]
187+ sorted_ids = [r[0]
188+ for r in sorted(branchid_dates, key=lambda r: r[1])]
189+ for branchid in sorted_ids:
190+ if delete_count < number_to_delete:
191+ if direc[branchid] != self.get_currentcode_symlink_target():
192+ shutil.rmtree(direc[branchid])
193+ delete_count += 1
194+
195+ def deploy_dir(self, srcdir, check_newer=False):
196+ """Move a new directory in and update the currentcode symlink to point
197+ at it. check_newer assumes vcs type supports rA > rb"""
198+ (candidate_branchid, candidate_comparator) = _get_vcs_identifier(srcdir)
199+ branchids = self.get_branchids()
200+ if candidate_branchid in branchids:
201+ print "Directory containing branchid already exists"
202+ return
203+ if check_newer and os.path.exists(self.get_currentcode_symlink()):
204+ (_, current_comparator) = _get_vcs_identifier(self.get_currentcode_symlink_target())
205+ if check_newer and current_comparator >= int(candidate_comparator):
206+ print "ERROR. Not deploying branch. Live branch (%s) >= candidate branch(%s)" % \
207+ (current_comparator, candidate_comparator)
208+ return
209+ # Move new directiry in and point currenctcode symlink at it
210+ host.rsync(srcdir + os.sep, os.path.join(self.fqdir,
211+ self.code_name +
212+ "-" + str(candidate_branchid)))
213+ self.add_code_symlink(branchid=str(candidate_branchid))
214
215=== added directory 'tests/contrib/vcsdir'
216=== added file 'tests/contrib/vcsdir/__init__.py'
217=== added file 'tests/contrib/vcsdir/test_vcsdir.py'
218--- tests/contrib/vcsdir/test_vcsdir.py 1970-01-01 00:00:00 +0000
219+++ tests/contrib/vcsdir/test_vcsdir.py 2013-08-28 15:57:15 +0000
220@@ -0,0 +1,290 @@
221+from testtools import TestCase
222+import glob
223+from mock import patch, call, Mock
224+import charmhelpers.contrib.vcsdir as vcsdir
225+from charmhelpers.core import host
226+import os
227+import shutil
228+
229+DISTINCT_BRANCHIDS = ['aa', 'bb', 'cc']
230+PROJECT_NAME = 'my-super-whizzy-project'
231+BASE_DIR = '/srv'
232+
233+tmpbranchids = DISTINCT_BRANCHIDS * 2
234+FAKE_VCSDIRS = []
235+
236+for branchid in tmpbranchids:
237+ directory = os.path.join(BASE_DIR,
238+ PROJECT_NAME + '-' + branchid)
239+ FAKE_VCSDIRS.append([directory, branchid])
240+TESTDIR = {}
241+TESTDIR['direc'] = FAKE_VCSDIRS[0][0]
242+TESTDIR['branchid'] = FAKE_VCSDIRS[0][1]
243+NEWDIR = {}
244+NEWDIR['direc'] = os.path.join(BASE_DIR, 'new-dir')
245+NEWDIR['branchid'] = 'zz'
246+
247+
248+class mock_git():
249+ class Repo():
250+ def __init__(self, source):
251+ self.head.commit.hexsha = "gittest"
252+ self.head.commit.committed_date = 1374676968
253+
254+ class head():
255+ class commit():
256+ pass
257+
258+
259+class BzrBranch():
260+ def revno(self):
261+ return 10
262+
263+
264+class mock_bzrlib_branch():
265+ class Branch():
266+ @staticmethod
267+ def open(source):
268+ return BzrBranch()
269+
270+
271+class vcsidentifierTest(TestCase):
272+ def test_get_vcs_identifier_git_exception(self):
273+ self.assertRaises(Exception, lambda: vcsdir._get_vcs_identifier('tmp'))
274+
275+ @patch.dict('sys.modules', {'git': mock_git})
276+ def test_get_vcs_identifier_git(self):
277+ self.assertEqual(vcsdir._get_vcs_identifier('tmp'), ("gittest", 1374676968))
278+
279+ @patch.dict('sys.modules', {'bzrlib.branch': mock_bzrlib_branch})
280+ def test_get_vcs_identifier_bzr(self):
281+ self.assertEqual(vcsdir._get_vcs_identifier('tmp'), (10, 10))
282+
283+
284+class VcsDirTest(TestCase):
285+ @patch.object(os.path, 'isdir')
286+ @patch.object(os.path, 'exists')
287+ def test_fail_nodir(self, mock_pathexists, mock_pathisdir):
288+ mock_pathexists.return_value = True
289+ mock_pathisdir.return_value = False
290+ self.assertRaises(Exception, lambda: vcsdir.VcsDir('/tmp'))
291+
292+ def test_get_fqdir(self):
293+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
294+ self.assertEqual(TESTDIR['direc'], vcstestdir.get_fqdir())
295+
296+ def test_get_basename(self):
297+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
298+ self.assertEqual(TESTDIR['direc'].replace(BASE_DIR + os.sep, ''),
299+ vcstestdir.get_basename())
300+
301+ def test_get_codename(self):
302+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
303+ self.assertEqual(PROJECT_NAME, vcstestdir.get_codename())
304+
305+ def test_get_branchid(self):
306+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
307+ self.assertEqual('aa', vcstestdir.get_branchid())
308+
309+ @patch.object(vcsdir, '_get_vcs_identifier')
310+ def test_get_comparator(self, mock_get_vcs_identifier):
311+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
312+ mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[0], DISTINCT_BRANCHIDS[0])
313+ self.assertEqual('aa', vcstestdir.get_comparator())
314+
315+ @patch.object(vcsdir, '_get_vcs_identifier')
316+ def test_validate_name(self, mock_get_vcs_identifier):
317+ vcstestdir = vcsdir.VcsDir(TESTDIR['direc'])
318+ mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[0], DISTINCT_BRANCHIDS[0])
319+ self.assertEqual(True, vcstestdir.validate_name())
320+ mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[1], DISTINCT_BRANCHIDS[1])
321+ self.assertEqual(False, vcstestdir.validate_name())
322+
323+
324+class VcsDirsTest(TestCase):
325+ @patch.object(os, 'remove')
326+ @patch.object(os.path, 'islink')
327+ @patch.object(os, 'symlink')
328+ @patch.object(vcsdir.VcsDirs, 'get_directory_by_branchid')
329+ def test_add_code_symlink(self, mock_get_directory_by_branchid,
330+ mock_symlink, mock_pathislink, mock_remove):
331+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
332+ code_symlink = os.path.join(BASE_DIR, PROJECT_NAME)
333+ mock_pathislink.return_value = True
334+ mock_get_directory_by_branchid.return_value = TESTDIR['direc']
335+ testdir.add_code_symlink(branchid=TESTDIR['branchid'])
336+ mock_symlink.assert_called_with(TESTDIR['direc'], code_symlink)
337+
338+ @patch.object(vcsdir.VcsDir, 'get_branchid')
339+ @patch.object(vcsdir.VcsDirs, 'get_currentcode_symlink')
340+ def test_get_currentcode_branchid(self, mock_get_currentcode_symlink,
341+ mock_get_branchid):
342+ mock_get_currentcode_symlink.return_value = os.path.join(BASE_DIR,
343+ PROJECT_NAME)
344+ mock_get_branchid.return_value = TESTDIR['branchid']
345+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
346+ self.assertEqual(testdir.get_currentcode_branchid(),
347+ TESTDIR['branchid'])
348+
349+ @patch.object(os.path, 'isdir')
350+ @patch.object(vcsdir.VcsDir, 'get_branchid')
351+ @patch.object(glob, 'glob')
352+ def test_get_branchids(self, mock_glob, mock_get_branchid,
353+ mock_ospathisdir):
354+ branchids = [x[1] for x in FAKE_VCSDIRS]
355+ directories = [x[0] for x in FAKE_VCSDIRS]
356+
357+ def branchid_side_effect():
358+ return branchids.pop()
359+ mock_get_branchid.side_effect = branchid_side_effect
360+ mock_glob.return_value = directories
361+ mock_ospathisdir.return_value = True
362+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
363+ branchids = testdir.get_branchids()
364+ # Check we have all the distinct branchids
365+ self.assertEqual(branchids, set([x[1] for x in FAKE_VCSDIRS]))
366+
367+ @patch.object(os.path, 'isdir')
368+ @patch.object(vcsdir.VcsDir, 'validate_name')
369+ @patch.object(vcsdir.VcsDir, 'get_branchid')
370+ @patch.object(glob, 'glob')
371+ def test_get_directories_key_branchid(self, mock_glob,
372+ mock_get_branchid,
373+ mock_validate_name,
374+ mock_ospathisdir):
375+ vcsdirs = [x[0] for x in FAKE_VCSDIRS]
376+ branchids = [x[1] for x in FAKE_VCSDIRS]
377+ mock_glob.return_value = vcsdirs
378+ mock_validate_name.return_value = True
379+ mock_ospathisdir.return_value = True
380+
381+ def branchid_side_effect():
382+ return branchids.pop(0)
383+ mock_get_branchid.side_effect = branchid_side_effect
384+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
385+ branchids = testdir.get_directories_key_branchid()
386+ self.assertEqual(branchids[TESTDIR['branchid']], TESTDIR['direc'])
387+
388+ @patch.object(os.path, 'isdir')
389+ @patch.object(vcsdir.VcsDir, 'get_comparator')
390+ @patch.object(glob, 'glob')
391+ def test_get_directories_key_comparator(self, mock_glob,
392+ mock_get_comparator,
393+ mock_ospathisdir):
394+ vcsdirs = [x[0] for x in FAKE_VCSDIRS]
395+ branchids = [x[1] for x in FAKE_VCSDIRS]
396+ mock_glob.return_value = vcsdirs
397+ mock_ospathisdir.return_value = True
398+
399+ def branchid_side_effect():
400+ return branchids.pop(0)
401+ mock_get_comparator.side_effect = branchid_side_effect
402+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
403+ branchids = testdir.get_directories_key_comparator()
404+ self.assertEqual(branchids[TESTDIR['branchid']], TESTDIR['direc'])
405+
406+ @patch.object(vcsdir.VcsDirs, 'get_branchids')
407+ def test_is_branchid_present(self, mock_get_branchids):
408+ branchids = [x[1] for x in FAKE_VCSDIRS]
409+ mock_get_branchids.return_value = branchids
410+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
411+ self.assertEqual(True, testdir.is_branchid_present(branchids[0]))
412+ self.assertEqual(False,
413+ testdir.is_branchid_present('nonexistantbranchid'))
414+
415+ @patch.object(vcsdir.VcsDirs, 'get_directories_key_branchid')
416+ def test_get_directory_by_branchid(self,
417+ mock_get_directories_key_branchid):
418+ branchids = {}
419+ for branch in FAKE_VCSDIRS:
420+ branchids[branch[1]] = branch[0]
421+ mock_get_directories_key_branchid.return_value = branchids
422+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
423+ self.assertEqual(FAKE_VCSDIRS[0][0],
424+ testdir.get_directory_by_branchid(
425+ FAKE_VCSDIRS[0][1]))
426+ self.assertEqual(None,
427+ testdir.get_directory_by_branchid('nonexistantid'))
428+
429+ @patch.object(vcsdir.VcsDirs, 'get_directories_key_comparator')
430+ @patch.object(shutil, 'rmtree')
431+ @patch.object(os, 'stat')
432+ def test_prune_dirs_by_ctime(self, mock_osstat, mock_rmtree,
433+ mock_get_directories_key_comparator):
434+ branchids = {}
435+ dirctimes = {}
436+ base_time = 1200000000
437+ for branch in FAKE_VCSDIRS:
438+ branchids[branch[1]] = branch[0]
439+ base_time = base_time + 1
440+ dirctimes[branch[0]] = str(base_time) + ".00000"
441+ mock_get_directories_key_comparator.return_value = branchids
442+
443+ def osstat_side_effect(bob):
444+ mtime = dirctimes[bob]
445+ return Mock(st_mtime=mtime)
446+ mock_osstat.side_effect = osstat_side_effect
447+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
448+ testdir.prune_dirs(keep_dirs=1)
449+ calls = [call(FAKE_VCSDIRS[0][0]), call(FAKE_VCSDIRS[1][0])]
450+ mock_rmtree.assert_has_calls(calls)
451+
452+ @patch.object(vcsdir.VcsDirs, 'get_directories_key_comparator')
453+ @patch.object(shutil, 'rmtree')
454+ def test_prune_dirs_by_revno(self, mock_rmtree,
455+ mock_get_directories_key_comparator):
456+ branch_comps = {}
457+ for branch in FAKE_VCSDIRS:
458+ branch_comps[branch[1]] = branch[0]
459+
460+ mock_get_directories_key_comparator.return_value = branch_comps
461+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
462+ testdir.prune_dirs(keep_dirs=1, order_by_revno=True)
463+ calls = [call(FAKE_VCSDIRS[0][0]), call(FAKE_VCSDIRS[1][0])]
464+ mock_rmtree.assert_has_calls(calls)
465+
466+ @patch.object(vcsdir.VcsDirs, 'add_code_symlink')
467+ @patch.object(vcsdir.VcsDirs, 'get_branchids')
468+ @patch.object(vcsdir, '_get_vcs_identifier')
469+ @patch.object(host, 'rsync')
470+ def test_deploy_dir_newdir(self, mock_rsync, mock_get_vcs_identifier,
471+ mock_get_branchids, mock_add_code_symlink):
472+ branchids = [x[1] for x in FAKE_VCSDIRS]
473+ mock_get_branchids.return_value = branchids
474+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
475+ mock_get_vcs_identifier.return_value = (NEWDIR['branchid'], NEWDIR['branchid'])
476+ testdir.deploy_dir(NEWDIR['direc'])
477+ new_vcsdir = os.path.join(BASE_DIR,
478+ PROJECT_NAME + '-' + NEWDIR['branchid'])
479+ mock_rsync.assert_called_with(NEWDIR['direc'] + os.sep, new_vcsdir)
480+
481+ @patch.object(vcsdir.VcsDirs, 'add_code_symlink')
482+ @patch.object(vcsdir.VcsDirs, 'get_branchids')
483+ @patch.object(vcsdir, '_get_vcs_identifier')
484+ @patch.object(host, 'rsync')
485+ def test_deploy_dir_same_branchid(self, mock_rsync,
486+ mock_get_vcs_identifier,
487+ mock_get_branchids,
488+ mock_add_code_symlink):
489+ mock_get_vcs_identifier.return_value = (FAKE_VCSDIRS[0][1], FAKE_VCSDIRS[0][1])
490+ branchids = [x[1] for x in FAKE_VCSDIRS]
491+ mock_get_branchids.return_value = branchids
492+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
493+ testdir.deploy_dir(NEWDIR['direc'])
494+ assert not mock_rsync.called
495+ assert not mock_add_code_symlink.called
496+
497+ @patch.object(vcsdir.VcsDirs, 'get_currentcode_branchid')
498+ @patch.object(os.path, 'exists')
499+ @patch.object(vcsdir, '_get_vcs_identifier')
500+ @patch.object(host, 'rsync')
501+ def test_deploy_dir_lower_branchid(self, mock_rsync,
502+ mock_get_vcs_identifier,
503+ mock_ospathexists,
504+ mock_get_currentcode_branchid):
505+ mock_ospathexists.return_value = True
506+ mock_get_vcs_identifier.return_value = ('1', '1')
507+ mock_get_currentcode_branchid.return_value = '2'
508+ testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME)
509+ testdir.deploy_dir(NEWDIR['direc'], check_newer=True)
510+ assert not mock_rsync.called

Subscribers

People subscribed via source and target branches