Merge lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout into lp:lpsetup

Proposed by Francesco Banconi on 2012-07-16
Status: Merged
Approved by: Francesco Banconi on 2012-07-17
Approved revision: 80
Merged at revision: 55
Proposed branch: lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout
Merge into: lp:lpsetup
Diff against target: 652 lines (+385/-38)
7 files modified
lpsetup/handlers.py (+41/-2)
lpsetup/settings.py (+5/-5)
lpsetup/subcommands/initrepo.py (+50/-19)
lpsetup/tests/subcommands/test_initrepo.py (+135/-4)
lpsetup/tests/test_handlers.py (+80/-4)
lpsetup/tests/utils.py (+69/-0)
setup.cfg (+5/-4)
To merge this branch: bzr merge lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout
Reviewer Review Type Date Requested Status
Benji York (community) code 2012-07-16 Approve on 2012-07-16
Review via email: mp+115108@code.launchpad.net

Commit Message

Add --no-checkout option to init-repo and other fixes.

Description of the Change

== Changes ==

*handle_directories* now handles relative paths. E.g. it is possible to use a relative path for the target repository *init-repo* command line argument. Also updated the relevant TestCase.

Added the *handle_branch_and_checkout* handler: it validates branch and checkout names.

s/CHECKOUT_DIR/LP_REPOSITORY_DIR everywhere.

Added `--no-checkout` option to *init-repo*: it creates a "normal" non-lightweight checkout.

The runtime errors that can be generated by `bzr` calls in *init-repo* are now handled and raise an *ExecutionError*. To avoid race conditions this error handling is done by the step itself and not by the handlers.
The *fetch()* step now tries a lot: it contains 3 similar try/except blocks that could be abstracted, for instance, with a code like this::

    def bzrcall(args, error=''):
        """Execute bzr passing *args*.

        If the bzr command fails, raise an ExecutionError containing the given
        *error* + the original bzr failure message.
        """
        cmd = ['bzr'] + list(args)
        try:
            call(*cmd)
        except subprocess.CalledProcessError as err:
            raise exceptions.ExecutionError(error + err.output)

I decided to leave the explicit calls in *fetch()*: please feel free to suggest another approach.

Fixed *setup_bzr_locations*: now the step correctly handles a custom branch name that can be passed as an argument.

Implemented an integration TestCase for *init-repo*. The tests create a fake test branch and use it as source bzr repository. The template directory used to create the fake branch is placed in `lpsetup/tests/test-branch/`: right now it contains only a test file, but in the future it can be used as a "mocked" Launchpad branch. 3 tests are currently implemented: init-repo with tree, without tree, and bazaar location checks.

Created a *BackupFile* context manager, useful to backup and restore a single file: in this branch it is used to preserve bazaar's `locations.conf`.

Added tests for the *capture* context manager.

Added the helper function *create_test_branch()*: create a temporary fake test branch.

`setup.cfg` excludes *create_test_branch* and includes *utils*.

To post a comment you must log in.
Benji York (benji) wrote :

This branch looks good. There is one thing that needs to be fixed (see
below) and one thing for future consideration (farther below).

The (pre-existing) code that replaces "~" with namespace.home_dir should
use os.path.expanduser() instead. The reason is that just replacing all
tilde characters with the current user's home directory can generate
incorrect results. For example, these strings would not be handled
correctly:

    "~user/foo" --> "/home/useruser/foo"
    "~/foo~bar" --> "/home/user/foouserbar"

Using the users actual bzr config (locations.conf) for tests feels a bit
brittle. Perhaps in the future we can override $HOME instead and
provide our own (fully controlled and non-precious) file hierarchy for
the tests to run against.

review: Approve (code)
Francesco Banconi (frankban) wrote :

Thanks Benji, I updated the branch following your suggestions.

Benji York (benji) wrote :

Looks good.

review: Approve (code)
Launchpad QA Bot (lpqabot) wrote :
Download full text (4.3 KiB)

The attempt to merge lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout into lp:lpsetup failed. Below is the output from the failed tests.

nose.plugins.cover: ERROR: Coverage not available: unable to import coverage module
.....................................EEE..........EE.....................................................................
======================================================================
ERROR: disabled_test_bazaar_locations (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmp53p8Un', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_no_trees (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpFnoZP3', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_with_trees (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpXemNXl', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_revno (lpsetup.tests.utils.CreateTestBranchTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 87, in setUp
    self.branch_path = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py"...

Read more...

Launchpad QA Bot (lpqabot) wrote :
Download full text (4.3 KiB)

The attempt to merge lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout into lp:lpsetup failed. Below is the output from the failed tests.

nose.plugins.cover: ERROR: Coverage not available: unable to import coverage module
.....................................EEE..........EE.....................................................................
======================================================================
ERROR: test_bazaar_locations (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpGk18KX', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_no_trees (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpfSq6XO', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_with_trees (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 73, in setUp
    self.source = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmp4yLDfS', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_revno (lpsetup.tests.utils.CreateTestBranchTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 87, in setUp
    self.branch_path = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79...

Read more...

Launchpad QA Bot (lpqabot) wrote :
Download full text (3.2 KiB)

The attempt to merge lp:~frankban/lpsetup/bug-1023895-init-repo-no-checkout into lp:lpsetup failed. Below is the output from the failed tests.

nose.plugins.cover: ERROR: Coverage not available: unable to import coverage module
.....................................F............EE.....................................................................
======================================================================
ERROR: test_revno (lpsetup.tests.utils.CreateTestBranchTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 87, in setUp
    self.branch_path = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpIGoZpV', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
ERROR: test_working_tree (lpsetup.tests.utils.CreateTestBranchTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 87, in setUp
    self.branch_path = create_test_branch()
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/utils.py", line 79, in create_test_branch
    call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
  File "/usr/lib/python2.7/dist-packages/shelltoolbox/__init__.py", line 452, in run
    raise exception
CalledProcessError: Command '['bzr', 'commit', '/mnt/tarmac-tmp/tmpdoiaQC', '-m', 'Initial commit.']' returned non-zero exit status 3

======================================================================
FAIL: test_bazaar_locations (lpsetup.tests.subcommands.test_initrepo.IntegrationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tarmac/repos/lpsetup/trunk/lpsetup/tests/subcommands/test_initrepo.py", line 169, in test_bazaar_locations
    self.assertItemsEqual(options.items(), parser.items(section))
AssertionError: Element counts were not equal:
First has 1, Second has 0: ('push_location', 'lp:~lpqabot/launchpad')
First has 1, Second has 0: ('public_branch', 'bzr+ssh://bazaar.launchpad.net/~lpqabot/launchpad')
First has 0, Second has 1: ('push_location', 'lp:~tarmac/launchpad')
First has 0, Second has 1: ('public_branch', 'bzr+ssh://bazaar.launchpad.net/~tarmac/launchpad')
    """Fail immediately, with the given message."""
>> raise self.failureException("Element counts were not equal:\nFirst has 1, Second has 0: ('push_location', 'lp:~lpqabot/launchpad')\nFirst has 1, Second has 0: ('public_branch', 'bzr+ssh://bazaar.launchpad.net/~lpqabot/launchpad')\nFirst has 0, Second has 1: ('push_location', 'lp:~tarmac/launchpad')\nFirst has 0, Second has 1: ('public_branch', 'bzr+ssh://bazaar.launchpad.net/~tarmac/launchpad')")

-------...

Read more...

77. By Francesco Banconi on 2012-07-17

Fixed CreateTestBranchTest: set the bzr user id.

78. By Francesco Banconi on 2012-07-17

Fixed IntegrationTest.test_bazaar_locations: use the real lpuser.

79. By Francesco Banconi on 2012-07-17

Fixed handle_directories relative path test to actually change the current dir.

80. By Francesco Banconi on 2012-07-17

Skipping tests if lpuser is not set up.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/handlers.py'
2--- lpsetup/handlers.py 2012-07-13 16:01:13 +0000
3+++ lpsetup/handlers.py 2012-07-17 10:49:22 +0000
4@@ -21,6 +21,7 @@
5
6 from shelltoolbox import (
7 bzr_whois,
8+ environ,
9 get_user_home,
10 run,
11 user_exists,
12@@ -234,8 +235,16 @@
13 err = ("argument directory cannot contain "
14 "spaces: '{0}'".format(directory))
15 raise ValidationError(err)
16-
17- directory = directory.replace('~', namespace.home_dir)
18+ # To expand the "~" construction, *os.path.expanduser* tries
19+ # to use the "HOME" environment variable.
20+ # Using *shelltoolbox.environ*, here we replace the original env var
21+ # with the home dir already present in the namespace, for 2 reasons:
22+ # - it is possible that we don't need the current user's home
23+ # (e.g. lpsetup is run as root)
24+ # - it is possible that we need the home of a non-existent user
25+ # (e.g. lpsetup needs to create the user specified using `--user`)
26+ with environ(HOME=namespace.home_dir):
27+ directory = os.path.expanduser(directory)
28 directory = os.path.abspath(directory)
29 if not directory.startswith(namespace.home_dir + os.path.sep):
30 raise ValidationError(
31@@ -279,3 +288,33 @@
32 def handle_working_dir(namespace):
33 """Handle path to the working directory."""
34 namespace.working_dir = normalize_path(namespace.working_dir)
35+
36+
37+def handle_branch_and_checkout(namespace):
38+ """Handle branch and checkout names.
39+
40+ Raise *ValidationError* if::
41+
42+ - branch/checkout name is '.' or '..'
43+ - branch/checkout name contains '/'
44+ - lightweight checkout is used and branch and checkout have the same name
45+
46+ The namespace must contain the following names::
47+
48+ - branch_name
49+ - checkout_name
50+
51+ This handler does not modify the namespace.
52+ """
53+ branch_name = namespace.branch_name
54+ checkout_name = namespace.checkout_name
55+ for name in (branch_name, checkout_name):
56+ if name in ('.', '..') or '/' in name:
57+ raise ValidationError(
58+ 'invalid name "{0}": branch or checkout names can not '
59+ 'be "." or ".." and can not contain "/"'.format(name))
60+ is_lightweight = not getattr(namespace, 'no_checkout', False)
61+ if is_lightweight and (checkout_name == branch_name):
62+ raise ValidationError(
63+ 'branch and checkout: can not use the same name ({0}).'.format(
64+ checkout_name))
65
66=== modified file 'lpsetup/settings.py'
67--- lpsetup/settings.py 2012-07-11 15:47:34 +0000
68+++ lpsetup/settings.py 2012-07-17 10:49:22 +0000
69@@ -15,7 +15,6 @@
70 )
71 # a2enmod requires apache2.2-common
72 BASE_PACKAGES = ['ssh', 'bzr', 'apache2.2-common']
73-CHECKOUT_DIR = '~/launchpad/lp-branches'
74 DEPENDENCIES_DIR = '~/launchpad/lp-sourcedeps'
75 HOSTS_CONTENT = (
76 ('127.0.0.88',
77@@ -39,22 +38,23 @@
78 )
79 LP_BZR_LOCATIONS = {
80 '{repository}': {
81- 'submit_branch': '{checkout_dir}',
82+ 'submit_branch': '{branch_dir}',
83 'public_branch': 'bzr+ssh://bazaar.launchpad.net/~{lpuser}/launchpad',
84 'public_branch:policy': 'appendpath',
85 'push_location': 'lp:~{lpuser}/launchpad',
86 'push_location:policy': 'appendpath',
87- 'merge_target': '{checkout_dir}',
88+ 'merge_target': '{branch_dir}',
89 'submit_to': 'merge@code.launchpad.net',
90 },
91- '{checkout_dir}': {
92+ '{branch_dir}': {
93 'public_branch':
94 'bzr+ssh://bazaar.launchpad.net/~launchpad-pqm/launchpad/devel',
95 }
96 }
97 LP_BRANCH_NAME = 'devel'
98 LP_CHECKOUT_NAME = 'sandbox'
99-LP_CODE_DIR = os.path.join(CHECKOUT_DIR, LP_CHECKOUT_NAME)
100+LP_REPOSITORY_DIR = '~/launchpad/lp-branches'
101+LP_CODE_DIR = os.path.join(LP_REPOSITORY_DIR, LP_CHECKOUT_NAME)
102 LP_PACKAGES = [
103 # "launchpad-database-dependencies-9.1" can be removed once 8.x is
104 # no longer an option in "launchpad-developer-dependencies.
105
106=== modified file 'lpsetup/subcommands/initrepo.py'
107--- lpsetup/subcommands/initrepo.py 2012-07-12 18:23:37 +0000
108+++ lpsetup/subcommands/initrepo.py 2012-07-17 10:49:22 +0000
109@@ -19,16 +19,20 @@
110 ]
111
112 import os
113+import subprocess
114
115 from shelltoolbox import mkdirs
116
117-from lpsetup import argparser
118-from lpsetup import handlers
119+from lpsetup import (
120+ argparser,
121+ exceptions,
122+ handlers,
123+ )
124 from lpsetup.settings import (
125- CHECKOUT_DIR,
126 LP_BRANCH_NAME,
127 LP_BZR_LOCATIONS,
128 LP_CHECKOUT_NAME,
129+ LP_REPOSITORY_DIR,
130 )
131 from lpsetup.utils import (
132 call,
133@@ -37,23 +41,42 @@
134 )
135
136
137-def fetch(source, repository, branch_name, checkout_name):
138+def fetch(source, repository, branch_name, checkout_name, no_checkout):
139 """Create a repo for the Launchpad code and retrieve it."""
140 # XXX should we warn if root?
141 # Set up the repository.
142 mkdirs(repository)
143- call('bzr', 'init-repo', repository, '--no-trees')
144+ is_lightweight = not no_checkout
145+ no_trees = '--no-trees' if is_lightweight else None
146+ # Initialize the repository.
147+ try:
148+ call('bzr', 'init-repo', '--quiet', repository, no_trees)
149+ except subprocess.CalledProcessError as err:
150+ msg = 'Error: unable to initialize the repository: '
151+ raise exceptions.ExecutionError(msg + err.output)
152 # Set up the codebase.
153 branch_dir = os.path.join(repository, branch_name)
154- checkout_dir = os.path.join(repository, checkout_name)
155- call('bzr', 'branch', source, branch_dir)
156- call('bzr', 'co', '--lightweight', branch_dir, checkout_dir)
157-
158-
159-def setup_bzr_locations(lpuser, repository, template=LP_BZR_LOCATIONS):
160+ # Retrieve the branch.
161+ try:
162+ call('bzr', 'branch', source, branch_dir)
163+ except subprocess.CalledProcessError as err:
164+ msg = 'Error: unable to branch source: '
165+ raise exceptions.ExecutionError(msg + err.output)
166+ if is_lightweight:
167+ checkout_dir = os.path.join(repository, checkout_name)
168+ # Create a lightweight checkout.
169+ try:
170+ call('bzr', 'co', '--lightweight', branch_dir, checkout_dir)
171+ except subprocess.CalledProcessError as err:
172+ msg = 'Error: unable to create the lightweight checkout: '
173+ raise exceptions.ExecutionError(msg + err.output)
174+
175+
176+def setup_bzr_locations(
177+ lpuser, repository, branch_name, template=LP_BZR_LOCATIONS):
178 """Set up bazaar locations."""
179 context = {
180- 'checkout_dir': os.path.join(repository, LP_BRANCH_NAME),
181+ 'branch_dir': os.path.join(repository, branch_name),
182 'repository': repository,
183 'lpuser': lpuser,
184 }
185@@ -78,8 +101,10 @@
186 """Get the Launchpad code and dependent source code."""
187
188 steps = (
189- (fetch, 'source', 'repository', 'branch_name', 'checkout_name'),
190- (setup_bzr_locations, 'lpuser', 'repository'),
191+ (fetch,
192+ 'source', 'repository', 'branch_name', 'checkout_name',
193+ 'no_checkout'),
194+ (setup_bzr_locations, 'lpuser', 'repository', 'branch_name'),
195 )
196
197 help = __doc__
198@@ -87,6 +112,7 @@
199 handlers.handle_user,
200 handlers.handle_lpuser_from_lplogin,
201 handlers.handle_directories,
202+ handlers.handle_branch_and_checkout,
203 handlers.handle_source,
204 )
205
206@@ -107,14 +133,19 @@
207 'Defaults to {0}.'.format(LP_BRANCH_NAME))
208 parser.add_argument(
209 '--checkout-name', default=LP_CHECKOUT_NAME,
210- help='Create a checkout with the given name. Defaults to {0}.'
211- .format(LP_CHECKOUT_NAME))
212+ help='Create a checkout with the given name. '
213+ 'Ignored if --no-checkout is specified. '
214+ 'Defaults to {0}.'.format(LP_CHECKOUT_NAME))
215 parser.add_argument(
216- '-r', '--repository', default=CHECKOUT_DIR,
217- help='The directory of the Launchpad repository to be updated. '
218+ '-r', '--repository', default=LP_REPOSITORY_DIR,
219+ help='The directory of the Launchpad repository to be created. '
220 'The directory must reside under the home directory of the '
221 'given user (see -u argument). '
222- '[DEFAULT={0}]'.format(CHECKOUT_DIR))
223+ '[DEFAULT={0}]'.format(LP_REPOSITORY_DIR))
224+ parser.add_argument(
225+ '--no-checkout', action='store_true', dest='no_checkout',
226+ default=False, help='Initialize a bzr repository with trees and '
227+ 'do not create a lightweight checkout.')
228
229 def add_arguments(self, parser):
230 super(SubCommand, self).add_arguments(parser)
231
232=== modified file 'lpsetup/tests/subcommands/test_initrepo.py'
233--- lpsetup/tests/subcommands/test_initrepo.py 2012-07-05 18:38:51 +0000
234+++ lpsetup/tests/subcommands/test_initrepo.py 2012-07-17 10:49:22 +0000
235@@ -4,39 +4,67 @@
236
237 """Tests for the initrepo subcommand."""
238
239+import os
240+import shutil
241+import subprocess
242+import tempfile
243 import unittest
244
245-from lpsetup import handlers
246+from shelltoolbox import (
247+ environ,
248+ run,
249+ )
250+
251+from lpsetup import (
252+ cli,
253+ handlers,
254+ )
255 from lpsetup.subcommands import initrepo
256 from lpsetup.tests.utils import (
257+ create_test_branch,
258 get_random_string,
259 StepsBasedSubCommandTestMixin,
260 )
261+from lpsetup.utils import ConfigParser
262+
263+
264+try:
265+ lpuser = run('bzr', 'launchpad-login').strip()
266+except subprocess.CalledProcessError:
267+ lpuser = None
268+# Create a decorator to skip tests if lp login is not set.
269+skip_if_no_lpuser = unittest.skipIf(
270+ lpuser is None,
271+ 'You need to set up a Launchpad login is needed to run this test.')
272
273
274 fetch_step = (initrepo.fetch,
275- ['source', 'repository', 'branch_name', 'checkout_name'])
276+ ['source', 'repository', 'branch_name', 'checkout_name', 'no_checkout'])
277 setup_bzr_locations_step = (initrepo.setup_bzr_locations,
278- ['lpuser', 'repository'])
279+ ['lpuser', 'repository', 'branch_name'])
280
281
282 def get_arguments():
283 return (
284 '--source', get_random_string(),
285+ '--repository', get_random_string(),
286 '--branch-name', get_random_string(),
287 '--checkout-name', get_random_string(),
288+ '--no-checkout',
289 )
290
291
292+@skip_if_no_lpuser
293 class InitrepoTest(StepsBasedSubCommandTestMixin, unittest.TestCase):
294
295- sub_command_name = 'initrepo'
296+ sub_command_name = 'init-repo'
297 sub_command_class = initrepo.SubCommand
298 expected_arguments = get_arguments()
299 expected_handlers = (
300 handlers.handle_user,
301 handlers.handle_lpuser_from_lplogin,
302 handlers.handle_directories,
303+ handlers.handle_branch_and_checkout,
304 handlers.handle_source,
305 )
306 expected_steps = (
307@@ -44,3 +72,106 @@
308 setup_bzr_locations_step,
309 )
310 needs_root = False
311+
312+
313+@skip_if_no_lpuser
314+class IntegrationTest(unittest.TestCase):
315+
316+ def setUp(self):
317+ """Create a fake source bzr branch under `/tmp/`."""
318+ # The command init-repo updates `~/.bazaar/locations.conf`:
319+ # the tests need to preserve that file using a temporary home dir.
320+ self.temp_home = tempfile.mkdtemp()
321+ with environ(HOME=self.temp_home):
322+ # A Bazaar user id is needed to commit changes.
323+ run('bzr', 'whoami', 'Example User <user@example.com>')
324+ # A Launchpad login is needed to run init-repo.
325+ run('bzr', 'launchpad-login', lpuser)
326+ # Create the source branch. This is done in the cm block because
327+ # we want bzr to use info stored in the temp home.
328+ self.source = create_test_branch()
329+ # Schedule removal of created directories.
330+ self.addCleanup(shutil.rmtree, self.temp_home)
331+ self.addCleanup(shutil.rmtree, self.source)
332+ # The target repository (containing the branch and the checkout)
333+ # must be placed inside the user home, otherwise init-repo exits
334+ # with a ValidationError.
335+ self.repository = tempfile.mktemp(dir=os.path.expanduser('~'))
336+ branch_name = 'branch-' + get_random_string()
337+ checkout_name = 'checkout-' + get_random_string()
338+ self.cmd = (
339+ 'init-repo',
340+ '--source', self.source,
341+ '--repository', self.repository,
342+ '--branch-name', branch_name,
343+ '--checkout-name', checkout_name,
344+ )
345+ self.branch = os.path.join(self.repository, branch_name)
346+ self.checkout = os.path.join(self.repository, checkout_name)
347+
348+ def tearDown(self):
349+ """Remove the target repository."""
350+ if os.path.isdir(self.repository):
351+ shutil.rmtree(self.repository)
352+
353+ def assertIsBranch(self, path, no_tree=False):
354+ """Assert the given *path* is a valid bzr branch.
355+
356+ If *no_tree* is True, also check the *path* does not contain
357+ a working tree.
358+ If *no_tree* is False, also check the *path* contains the test file.
359+ """
360+ if not os.path.isdir(path):
361+ self.fail(path + ' is not a directory')
362+ contents = os.listdir(path)
363+ if '.bzr' not in contents:
364+ self.fail(path + ' is not a bzr repository')
365+ if no_tree:
366+ if len(contents) > 1:
367+ self.fail(path + ' contains a working tree')
368+ elif 'test-file' not in contents:
369+ self.fail(path + ' does not contain a working tree')
370+
371+ def test_no_trees(self):
372+ # Ensure a lightweight checkout is correctly created by init-repo.
373+ with environ(HOME=self.temp_home):
374+ retcode = cli.main(self.cmd)
375+ self.assertIsNone(retcode)
376+ # The branch is created without tree.
377+ self.assertIsBranch(self.branch, no_tree=True)
378+ # The checkout is created with tree.
379+ self.assertIsBranch(self.checkout, no_tree=False)
380+
381+ def test_with_trees(self):
382+ # Ensure a "normal" branch is correctly created by init-repo.
383+ with environ(HOME=self.temp_home):
384+ retcode = cli.main(self.cmd + ('--no-checkout',))
385+ self.assertIsNone(retcode)
386+ # The branch is created with tree.
387+ self.assertIsBranch(self.branch, no_tree=False)
388+
389+ def test_bazaar_locations(self):
390+ # Ensure the file `~/.bazaar/locations.conf` is correctly updated.
391+ expected_locations = {
392+ self.repository: {
393+ 'merge_target': self.branch,
394+ 'public_branch': 'bzr+ssh://bazaar.launchpad.net/~{}/'
395+ 'launchpad'.format(lpuser),
396+ 'public_branch:policy': 'appendpath',
397+ 'push_location': 'lp:~{}/launchpad'.format(lpuser),
398+ 'push_location:policy': 'appendpath',
399+ 'submit_branch': self.branch,
400+ 'submit_to': 'merge@code.launchpad.net',
401+ },
402+ self.branch: {
403+ 'public_branch': 'bzr+ssh://bazaar.launchpad.net/'
404+ '~launchpad-pqm/launchpad/devel',
405+ },
406+ }
407+ with environ(HOME=self.temp_home):
408+ cli.main(self.cmd)
409+ path = os.path.join(self.temp_home, '.bazaar', 'locations.conf')
410+ parser = ConfigParser()
411+ parser.read(path)
412+ for section, options in expected_locations.items():
413+ self.assertItemsEqual(options.items(), parser.items(section))
414
415=== added directory 'lpsetup/tests/test-branch'
416=== added file 'lpsetup/tests/test-branch/test-file'
417=== modified file 'lpsetup/tests/test_handlers.py'
418--- lpsetup/tests/test_handlers.py 2012-07-09 21:18:21 +0000
419+++ lpsetup/tests/test_handlers.py 2012-07-17 10:49:22 +0000
420@@ -7,11 +7,15 @@
421 import argparse
422 from contextlib import contextmanager
423 import getpass
424+import os
425 import pwd
426 import unittest
427
428+from shelltoolbox import cd
429+
430 from lpsetup.exceptions import ValidationError
431 from lpsetup.handlers import (
432+ handle_branch_and_checkout,
433 handle_directories,
434 handle_lpuser_as_username,
435 handle_ssh_keys,
436@@ -61,9 +65,48 @@
437 raise TypeError
438
439
440+class HandleBranchAndCheckoutTest(HandlersTestMixin, unittest.TestCase):
441+
442+ def test_valid_arguments(self):
443+ # The validation succeed if there are no conflicts in arguments.
444+ namespace = argparse.Namespace(
445+ branch_name='branch',
446+ checkout_name='checkout')
447+ handle_branch_and_checkout(namespace)
448+
449+ def test_checkout_equals_branch(self):
450+ # The validation fails if checkout and branch have the same name.
451+ namespace = argparse.Namespace(
452+ branch_name='branch',
453+ checkout_name='branch')
454+ with self.assertNotValid('checkout'):
455+ handle_branch_and_checkout(namespace)
456+
457+ def test_checkout_equals_branch_without_trees(self):
458+ # The validation succeed if checkout and branch have the same name
459+ # but `--no-checkout` is specified.
460+ namespace = argparse.Namespace(
461+ branch_name='branch',
462+ checkout_name='branch',
463+ no_checkout=True)
464+ handle_branch_and_checkout(namespace)
465+
466+ def test_invalid_names(self):
467+ # The validation fails if the branch/checkout name is '.' or '..',
468+ # or if it contains '/'.
469+ for kwargs in (
470+ {'branch_name': '.', 'checkout_name': 'checkout'},
471+ {'branch_name': 'branch', 'checkout_name': '..'},
472+ {'branch_name': '../', 'checkout_name': 'checkout'},
473+ {'branch_name': 'branch', 'checkout_name': 'check/out'},
474+ ):
475+ with self.assertNotValid('invalid name'):
476+ handle_branch_and_checkout(argparse.Namespace(**kwargs))
477+
478+
479 class HandleDirectoriesTest(HandlersTestMixin, unittest.TestCase):
480
481- home_dir = '/home/foo'
482+ home_dir = os.path.expanduser('~')
483 dependencies_dir = '~/launchpad/deps'
484
485 def test_home_is_expanded(self):
486@@ -72,9 +115,20 @@
487 repository='~/launchpad', home_dir=self.home_dir,
488 dependencies_dir=self.dependencies_dir)
489 handle_directories(namespace)
490- self.assertEqual('/home/foo/launchpad', namespace.repository)
491- self.assertEqual(
492- '/home/foo/launchpad/deps', namespace.dependencies_dir)
493+ self.assertEqual(
494+ os.path.join(self.home_dir, 'launchpad'),
495+ namespace.repository)
496+ self.assertEqual(
497+ os.path.join(self.home_dir, 'launchpad', 'deps'),
498+ namespace.dependencies_dir)
499+
500+ def test_tilde_expanded_only_when_needed(self):
501+ # Ensure the "~" construction is expanded only when it represents
502+ # the home directory of the user.
503+ repository = '~repository/~'
504+ namespace = argparse.Namespace(
505+ repository=repository, home_dir=self.home_dir)
506+ self.assertEqual(repository, namespace.repository)
507
508 def test_directory_not_in_home(self):
509 # The validation fails for directories not residing inside the home.
510@@ -90,6 +144,28 @@
511 with self.assertNotValid('directory'):
512 handle_directories(namespace)
513
514+ def test_relative_path(self):
515+ # Ensure relative paths are correctly expanded.
516+ namespace = argparse.Namespace(
517+ repository='launchpad', home_dir=self.home_dir,
518+ dependencies_dir='launchpad/../deps')
519+ with cd(self.home_dir):
520+ handle_directories(namespace)
521+ cwd = os.getcwd()
522+ self.assertEqual(
523+ os.path.join(cwd, 'launchpad'),
524+ namespace.repository)
525+ self.assertEqual(
526+ os.path.join(cwd, 'deps'),
527+ namespace.dependencies_dir)
528+
529+ def test_invalid_relative_path(self):
530+ # The validation fails if a relative path points outside the home.
531+ namespace = argparse.Namespace(
532+ repository='~/../', home_dir=self.home_dir)
533+ with self.assertNotValid('repository'):
534+ handle_directories(namespace)
535+
536
537 class HandleLPUserTest(unittest.TestCase):
538
539
540=== modified file 'lpsetup/tests/utils.py'
541--- lpsetup/tests/utils.py 2012-06-27 09:20:50 +0000
542+++ lpsetup/tests/utils.py 2012-07-17 10:49:22 +0000
543@@ -15,13 +15,23 @@
544
545 from contextlib import contextmanager
546 from functools import partial
547+import os
548 import random
549+import shutil
550 import string
551 from StringIO import StringIO
552 import sys
553+import tempfile
554+import unittest
555+
556+from shelltoolbox import (
557+ environ,
558+ run,
559+ )
560
561 from lpsetup import argparser
562 from lpsetup.tests import examples
563+from lpsetup.utils import call
564
565
566 @contextmanager
567@@ -39,6 +49,65 @@
568 capture_error = partial(capture, 'stderr')
569
570
571+class CaptureTest(unittest.TestCase):
572+ """Tests for the *capture* context manager."""
573+
574+ message = 'message'
575+
576+ def test_capture_output(self):
577+ with capture_output() as stream:
578+ print self.message
579+ self.assertEqual(self.message + '\n', stream.getvalue())
580+
581+ def test_capture_error(self):
582+ with capture_error() as stream:
583+ print >> sys.stderr, self.message
584+ self.assertEqual(self.message + '\n', stream.getvalue())
585+
586+
587+def create_test_branch(template_dir=None):
588+ """Create a temporary test branch containing files in *template_dir*.
589+
590+ If *template_dir* is None, the files in `lpsetup/tests/test-branch`
591+ are used.
592+
593+ Return the path of the newly created branch.
594+ """
595+ if template_dir is None:
596+ template_dir = os.path.join(os.path.dirname(__file__), 'test-branch')
597+ branch_path = tempfile.mktemp()
598+ shutil.copytree(template_dir, branch_path)
599+ call('bzr', 'init', '--quiet', branch_path)
600+ call('bzr', 'add', '--quiet', branch_path)
601+ call('bzr', 'commit', branch_path, '-m', 'Initial commit.')
602+ return branch_path
603+
604+
605+class CreateTestBranchTest(unittest.TestCase):
606+ """Tests for the *create_test_branch* helper function."""
607+
608+ def setUp(self):
609+ # Create a Bazaar user id inside a temporary home.
610+ self.temp_home = tempfile.mkdtemp()
611+ with environ(HOME=self.temp_home):
612+ run('bzr', 'whoami', 'Example User <user@example.com>')
613+ self.branch_path = create_test_branch()
614+
615+ def tearDown(self):
616+ shutil.rmtree(self.temp_home)
617+ shutil.rmtree(self.branch_path)
618+
619+ def test_revno(self):
620+ # Ensure the bzr branch is correctly created and contains a revision.
621+ revno = run('bzr', 'revno', self.branch_path)
622+ self.assertEqual(1, int(revno.strip()))
623+
624+ def test_working_tree(self):
625+ # Ensure the working tree is present in the newly created branch.
626+ test_file_path = os.path.join(self.branch_path, 'test-file')
627+ self.assertTrue(os.path.exists(test_file_path))
628+
629+
630 def get_random_string(size=10):
631 """Return a random string to be used in tests."""
632 return ''.join(random.sample(string.ascii_letters, size))
633
634=== modified file 'setup.cfg'
635--- setup.cfg 2012-07-11 18:37:31 +0000
636+++ setup.cfg 2012-07-17 10:49:22 +0000
637@@ -1,10 +1,11 @@
638 [nosetests]
639+# Specifying 'where' should not be required but it is a work-around
640+# for a problem presented by having 'ignore-files'.
641+where=lpsetup
642+exclude=handle_testing|create_test_branch
643+include=utils
644 detailed-errors=1
645-exclude=handle_testing
646 with-coverage=1
647 cover-package=lpsetup
648 with-doctest=1
649-# Specifying 'where' should not be required but it is a work-around
650-# for a problem presented by having 'ignore-files'.
651-where=lpsetup
652 ignore-files=disabled*

Subscribers

People subscribed via source and target branches

to all changes: