Merge lp:~bac/lpsetup/lxc-integration into lp:lpsetup
- lxc-integration
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Brad Crittenden |
Approved revision: | 81 |
Merged at revision: | 57 |
Proposed branch: | lp:~bac/lpsetup/lxc-integration |
Merge into: | lp:lpsetup |
Diff against target: |
876 lines (+368/-171) 15 files modified
README.rst (+6/-2) commands.rst (+4/-4) lpsetup/handlers.py (+13/-7) lpsetup/settings.py (+0/-3) lpsetup/subcommands/finish_inithost.py (+18/-11) lpsetup/subcommands/initrepo.py (+24/-12) lpsetup/subcommands/install_lxc.py (+13/-10) lpsetup/subcommands/update.py (+32/-22) lpsetup/tests/integration/common.py (+63/-0) lpsetup/tests/integration/lxc.py (+106/-0) lpsetup/tests/integration/non-lxc.py (+72/-89) lpsetup/tests/subcommands/test_finish_inithost.py (+4/-2) lpsetup/tests/subcommands/test_install_lxc.py (+3/-1) lpsetup/tests/subcommands/test_update.py (+8/-6) pre-commit.sh (+2/-2) |
To merge this branch: | bzr merge lp:~bac/lpsetup/lxc-integration |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+115804@code.launchpad.net |
Commit message
Add lxc-based integration test. Fix some idepotent problems.
Description of the change
Add lxc-based integration test.
Discovered a problem with the handling of some command-line options
(working_dir and code_dir) which required some rejiggering. They were
deleted in favor of specifying the repository and checkout-name
separately.
Refactored the test non-lxc.py in order to share code and structure
with lxc.py.
Fun with bzrlib. In retrospect the win over just shelling out to
'bzr' was miniscule.
Brad Crittenden (bac) wrote : | # |
Benji York (benji) wrote : | # |
The branch looks good, there were a couple of small issues that need to
be addressed and then it will be ready to land.
In lpsetup/
that can be single-line instead (on and about line 518 of the diff).
In lpsetup/
not used and there should be two blank lines before the definition of
IntegrationTest
Also, I /think/ the "help" parameter to the first add_argument in
IntegrationTestBase exceeds our prescribed line width.
As we discussed on the call, I'm not happy with the new OOP and
subclassing -- especially since there is more-or-less no state managed
by the object -- the modules themselves would have been sufficient
organization. That being said, it's not the end of the world.
We also need to get the new non-LXC test to use Juju so the test is run
in an isolated and throw-away environment. We decided that landing the
branch as-is and following up with Juju-ification would be fine.
Launchpad QA Bot (lpqabot) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'README.rst' |
2 | --- README.rst 2012-07-11 16:33:16 +0000 |
3 | +++ README.rst 2012-07-19 21:00:25 +0000 |
4 | @@ -59,11 +59,15 @@ |
5 | Integration tests can be found in lpsetup/tests/integration. They |
6 | require the "ubuntu" juju charm which, at the time of this writing, is |
7 | not available in the charm store. Check it out into the local juju |
8 | -charm repository from lp:~charmers/charms/precise/ubuntu/trunk. |
9 | +charm repository from lp:~charmers/charms/precise/ubuntu/trunk. The |
10 | +test expects your Juju repository to be at ~/juju-charms |
11 | |
12 | The integration tests use a juju environment named "lpsetup-testing" so |
13 | you must create an appropriately configured juju environment with that |
14 | -name before running the tests. |
15 | +name before running the tests in ~/.juju/environments.yaml. |
16 | + |
17 | +The tests bootstrap the environment for you and fail if it is already |
18 | +running. |
19 | |
20 | You may also want to add this to your SSH .config file to suppress the |
21 | many yes/no prompts: |
22 | |
23 | === modified file 'commands.rst' |
24 | --- commands.rst 2012-07-10 19:06:12 +0000 |
25 | +++ commands.rst 2012-07-19 21:00:25 +0000 |
26 | @@ -51,13 +51,13 @@ |
27 | Completely sets up an LXC environment with Launchpad using the sandbox |
28 | development model: |
29 | |
30 | -~/launchpad |
31 | +~/launchpad/lp-branches |
32 | bzr repository with --no-trees option. |
33 | -~/launchpad/devel |
34 | +~/launchpad/lp-branches/devel |
35 | branch with no trees of trunk. |
36 | -~/launchpad/sandbox |
37 | +~/launchpad/lp-branches/sandbox |
38 | lightweight checkout with a tree of ../devel. |
39 | -~/launchpad/bug-xyz |
40 | +~/launchpad/lp-branches/bug-xyz |
41 | branch with no trees of trunk where bug work is done via switching |
42 | inside sandbox. |
43 | |
44 | |
45 | === modified file 'lpsetup/handlers.py' |
46 | --- lpsetup/handlers.py 2012-07-16 16:43:34 +0000 |
47 | +++ lpsetup/handlers.py 2012-07-19 21:00:25 +0000 |
48 | @@ -6,6 +6,7 @@ |
49 | |
50 | __metaclass__ = type |
51 | __all__ = [ |
52 | + 'handle_code_dir', |
53 | 'handle_directories', |
54 | 'handle_lpuser_as_username', |
55 | 'handle_lpuser_from_lplogin', |
56 | @@ -221,13 +222,13 @@ |
57 | |
58 | |
59 | def handle_directories(namespace): |
60 | - """Handle repository, code_dir, and dependencies directories. |
61 | + """Handle repository and dependencies directories. |
62 | |
63 | - The ~ construction is automatically expanded. |
64 | - The validation fails for directories not residing inside the home. |
65 | - The validation fails if the directory contains spaces. |
66 | """ |
67 | - for attr in ('repository', 'code_dir', 'dependencies_dir'): |
68 | + for attr in ('repository', 'dependencies_dir'): |
69 | directory = getattr(namespace, attr, None) |
70 | if directory is None: |
71 | continue |
72 | @@ -285,11 +286,6 @@ |
73 | return os.path.abspath(os.path.expanduser(path)) |
74 | |
75 | |
76 | -def handle_working_dir(namespace): |
77 | - """Handle path to the working directory.""" |
78 | - namespace.working_dir = normalize_path(namespace.working_dir) |
79 | - |
80 | - |
81 | def handle_branch_and_checkout(namespace): |
82 | """Handle branch and checkout names. |
83 | |
84 | @@ -303,6 +299,7 @@ |
85 | |
86 | - branch_name |
87 | - checkout_name |
88 | + - repository |
89 | |
90 | This handler does not modify the namespace. |
91 | """ |
92 | @@ -318,3 +315,12 @@ |
93 | raise ValidationError( |
94 | 'branch and checkout: can not use the same name ({0}).'.format( |
95 | checkout_name)) |
96 | + |
97 | + |
98 | +def handle_code_dir(namespace): |
99 | + """Handle the computed value for `code_dir`. |
100 | + |
101 | + It must be invoked after handle_directories. |
102 | + """ |
103 | + namespace.code_dir = os.path.join( |
104 | + namespace.repository, namespace.checkout_name) |
105 | |
106 | === modified file 'lpsetup/settings.py' |
107 | --- lpsetup/settings.py 2012-07-17 17:55:38 +0000 |
108 | +++ lpsetup/settings.py 2012-07-19 21:00:25 +0000 |
109 | @@ -3,8 +3,6 @@ |
110 | # GNU Affero General Public License version 3 (see the file LICENSE). |
111 | |
112 | """Global settings and defaults for lpsetup.""" |
113 | -import os.path |
114 | - |
115 | |
116 | APT_REPOSITORIES = ( |
117 | 'deb http://archive.ubuntu.com/ubuntu {distro} multiverse', |
118 | @@ -56,7 +54,6 @@ |
119 | LP_BRANCH_NAME = 'devel' |
120 | LP_CHECKOUT_NAME = 'sandbox' |
121 | LP_REPOSITORY_DIR = '~/launchpad/lp-branches' |
122 | -LP_CODE_DIR = os.path.join(LP_REPOSITORY_DIR, LP_CHECKOUT_NAME) |
123 | LP_PACKAGES = [ |
124 | # "launchpad-database-dependencies-9.1" can be removed once 8.x is |
125 | # no longer an option in "launchpad-developer-dependencies. |
126 | |
127 | === modified file 'lpsetup/subcommands/finish_inithost.py' |
128 | --- lpsetup/subcommands/finish_inithost.py 2012-07-16 08:17:05 +0000 |
129 | +++ lpsetup/subcommands/finish_inithost.py 2012-07-19 21:00:25 +0000 |
130 | @@ -29,10 +29,15 @@ |
131 | |
132 | from lpsetup import argparser |
133 | from lpsetup.handlers import ( |
134 | + handle_code_dir, |
135 | handle_directories, |
136 | handle_user, |
137 | ) |
138 | -from lpsetup.settings import LP_CODE_DIR |
139 | +from lpsetup.settings import ( |
140 | + LP_CHECKOUT_NAME, |
141 | + LP_REPOSITORY_DIR, |
142 | + ) |
143 | + |
144 | from lpsetup.utils import call |
145 | |
146 | |
147 | @@ -75,22 +80,24 @@ |
148 | handlers = ( |
149 | handle_user, |
150 | handle_directories, |
151 | + handle_code_dir, |
152 | ) |
153 | |
154 | - @staticmethod |
155 | - def add_common_arguments(parser): |
156 | - parser.add_argument( |
157 | - '-c', '--code-dir', default=LP_CODE_DIR, |
158 | - help='The directory of the Launchpad code checkout. ' |
159 | - 'The directory must reside under the home directory of the ' |
160 | - 'given user (see -u argument). ' |
161 | - '[DEFAULT={0}]'.format(LP_CODE_DIR)) |
162 | - |
163 | def add_arguments(self, parser): |
164 | super(SubCommand, self).add_arguments(parser) |
165 | - self.add_common_arguments(parser) |
166 | parser.add_argument( |
167 | '-u', '--user', |
168 | help='The name of the system user. ' |
169 | 'The current user is used if this script is not run as ' |
170 | 'root and this argument is omitted.') |
171 | + parser.add_argument( |
172 | + '--checkout-name', default=LP_CHECKOUT_NAME, |
173 | + help='Create a checkout with the given name. ' |
174 | + 'Ignored if --no-checkout is specified. ' |
175 | + 'Defaults to {0}.'.format(LP_CHECKOUT_NAME)) |
176 | + parser.add_argument( |
177 | + '-r', '--repository', default=LP_REPOSITORY_DIR, |
178 | + help='The directory of the Launchpad repository to be created. ' |
179 | + 'The directory must reside under the home directory of the ' |
180 | + 'given user (see -u argument). ' |
181 | + '[DEFAULT={0}]'.format(LP_REPOSITORY_DIR)) |
182 | |
183 | === modified file 'lpsetup/subcommands/initrepo.py' |
184 | --- lpsetup/subcommands/initrepo.py 2012-07-16 10:09:48 +0000 |
185 | +++ lpsetup/subcommands/initrepo.py 2012-07-19 21:00:25 +0000 |
186 | @@ -49,27 +49,39 @@ |
187 | is_lightweight = not no_checkout |
188 | no_trees = '--no-trees' if is_lightweight else None |
189 | # Initialize the repository. |
190 | - try: |
191 | - call('bzr', 'init-repo', '--quiet', repository, no_trees) |
192 | - except subprocess.CalledProcessError as err: |
193 | - msg = 'Error: unable to initialize the repository: ' |
194 | - raise exceptions.ExecutionError(msg + err.output) |
195 | + |
196 | + # Skip creating the repository if it already exists. There is the |
197 | + # risk that it is not made with the same `no_trees` option as |
198 | + # specified here. |
199 | + if not os.path.exists(os.path.join(repository, '.bzr')): |
200 | + try: |
201 | + call('bzr', 'init-repo', '--quiet', repository, no_trees) |
202 | + except subprocess.CalledProcessError as err: |
203 | + msg = 'Error: unable to initialize the repository: ' |
204 | + raise exceptions.ExecutionError(msg + err.output) |
205 | # Set up the codebase. |
206 | branch_dir = os.path.join(repository, branch_name) |
207 | # Retrieve the branch. |
208 | + if os.path.exists(branch_dir): |
209 | + # The branch already exists, so we pull to refresh it rather than |
210 | + # creating it. |
211 | + cmd = ['bzr', 'pull', '--overwrite', '-d', branch_dir, source] |
212 | + else: |
213 | + cmd = ['bzr', 'branch', source, branch_dir] |
214 | try: |
215 | - call('bzr', 'branch', source, branch_dir) |
216 | + call(*cmd) |
217 | except subprocess.CalledProcessError as err: |
218 | msg = 'Error: unable to branch source: ' |
219 | raise exceptions.ExecutionError(msg + err.output) |
220 | if is_lightweight: |
221 | checkout_dir = os.path.join(repository, checkout_name) |
222 | - # Create a lightweight checkout. |
223 | - try: |
224 | - call('bzr', 'co', '--lightweight', branch_dir, checkout_dir) |
225 | - except subprocess.CalledProcessError as err: |
226 | - msg = 'Error: unable to create the lightweight checkout: ' |
227 | - raise exceptions.ExecutionError(msg + err.output) |
228 | + # Create a lightweight checkout if it doesn't exist. |
229 | + if not os.path.exists(checkout_dir): |
230 | + try: |
231 | + call('bzr', 'co', '--lightweight', branch_dir, checkout_dir) |
232 | + except subprocess.CalledProcessError as err: |
233 | + msg = 'Error: unable to create the lightweight checkout: ' |
234 | + raise exceptions.ExecutionError(msg + err.output) |
235 | |
236 | |
237 | def setup_bzr_locations( |
238 | |
239 | === modified file 'lpsetup/subcommands/install_lxc.py' |
240 | --- lpsetup/subcommands/install_lxc.py 2012-07-13 14:51:08 +0000 |
241 | +++ lpsetup/subcommands/install_lxc.py 2012-07-19 21:00:25 +0000 |
242 | @@ -13,6 +13,7 @@ |
243 | import os |
244 | |
245 | from lpsetup.handlers import ( |
246 | + handle_code_dir, |
247 | handle_directories, |
248 | handle_source, |
249 | ) |
250 | @@ -21,7 +22,6 @@ |
251 | SCRIPTS, |
252 | ) |
253 | from lpsetup.subcommands import ( |
254 | - finish_inithost, |
255 | initlxc, |
256 | initrepo, |
257 | update, |
258 | @@ -69,11 +69,9 @@ |
259 | |
260 | |
261 | def cmd_in_lxc(lxc_name, ssh_key_path, home_dir, args, as_user=None): |
262 | - print "args = ", args |
263 | cmd = this_command(home_dir, args) |
264 | if as_user is not None: |
265 | cmd = "su {} -c '{}'".format(as_user, cmd) |
266 | - print "CMD = ", cmd |
267 | ssh(lxc_name, cmd, key=ssh_key_path) |
268 | |
269 | |
270 | @@ -91,18 +89,20 @@ |
271 | |
272 | def update_in_lxc(lxc_name, ssh_key_path, home_dir, user, external_path, |
273 | use_http, checkout_name, repository): |
274 | - working_dir = os.path.join(repository, checkout_name) |
275 | args = [ |
276 | - 'update', '--external-path', external_path, '-W', working_dir, |
277 | + 'update', '--external-path', external_path, |
278 | + '-r', repository, '--checkout-name', checkout_name, |
279 | ] |
280 | if use_http: |
281 | args.append('--use-http') |
282 | cmd_in_lxc(lxc_name, ssh_key_path, home_dir, args, as_user=user) |
283 | |
284 | |
285 | -def finish_inithost_in_lxc(lxc_name, ssh_key_path, home_dir, user, code_dir): |
286 | +def finish_inithost_in_lxc(lxc_name, ssh_key_path, home_dir, user, |
287 | + repository, checkout_name): |
288 | args = [ |
289 | - 'finish-init-host', '--user', user, '--code-dir', code_dir, |
290 | + 'finish-init-host', '--user', user, '--repository', repository, |
291 | + '--checkout-name', checkout_name, |
292 | ] |
293 | cmd_in_lxc(lxc_name, ssh_key_path, home_dir, args) |
294 | |
295 | @@ -121,14 +121,18 @@ |
296 | (update_in_lxc, 'lxc_name', 'ssh_key_path', 'home_dir', 'user', |
297 | 'external_path', 'use_http', 'checkout_name', 'repository'), |
298 | (finish_inithost_in_lxc, 'lxc_name', 'ssh_key_path', 'home_dir', |
299 | - 'user', 'code_dir'), |
300 | + 'user', 'repository', 'checkout_name'), |
301 | ) |
302 | |
303 | help = __doc__ |
304 | |
305 | def get_handlers(self, namespace): |
306 | handlers = super(SubCommand, self).get_handlers(namespace) |
307 | - return handlers + (handle_directories, handle_source) |
308 | + return handlers + ( |
309 | + handle_directories, |
310 | + handle_code_dir, |
311 | + handle_source, |
312 | + ) |
313 | |
314 | def call_create_scripts(self, namespace, step, args): |
315 | """Run the `create_scripts` step only if the related flag is set.""" |
316 | @@ -140,7 +144,6 @@ |
317 | # Inherit arguments from subcommands we depend upon. |
318 | initrepo.SubCommand.add_common_arguments(parser) |
319 | update.SubCommand.add_common_arguments(parser) |
320 | - finish_inithost.SubCommand.add_common_arguments(parser) |
321 | # Add parallel testing related arguments. |
322 | parser.add_argument( |
323 | '-C', '--create-scripts', action='store_true', |
324 | |
325 | === modified file 'lpsetup/subcommands/update.py' |
326 | --- lpsetup/subcommands/update.py 2012-07-13 15:55:40 +0000 |
327 | +++ lpsetup/subcommands/update.py 2012-07-19 21:00:25 +0000 |
328 | @@ -19,48 +19,49 @@ |
329 | from lpsetup import argparser |
330 | from lpsetup import handlers |
331 | from lpsetup.settings import ( |
332 | - LP_CODE_DIR, |
333 | + LP_CHECKOUT_NAME, |
334 | + LP_REPOSITORY_DIR, |
335 | LP_SOURCE_DEPS, |
336 | ) |
337 | |
338 | |
339 | -def initialize_directories(working_dir, external_path): |
340 | +def initialize_directories(code_dir, external_path): |
341 | """Initialize the eggs, yui, and sourcecode directories. |
342 | |
343 | Create them if necessary. |
344 | """ |
345 | for dir_ in ['eggs', 'yui', 'sourcecode']: |
346 | - mkdirs(os.path.join(working_dir, external_path, dir_)) |
347 | - |
348 | - |
349 | -def update_dependencies(working_dir, external_path, use_http): |
350 | + mkdirs(os.path.join(code_dir, external_path, dir_)) |
351 | + |
352 | + |
353 | +def update_dependencies(code_dir, external_path, use_http): |
354 | """Update the external dependencies.""" |
355 | use_http_param = '--use-http' if use_http else None |
356 | - cmd = os.path.join(working_dir, 'utilities', 'update-sourcecode') |
357 | + cmd = os.path.join(code_dir, 'utilities', 'update-sourcecode') |
358 | abs_external_path = os.path.abspath( |
359 | - os.path.join(working_dir, external_path)) |
360 | + os.path.join(code_dir, external_path)) |
361 | source_path = os.path.join(abs_external_path, 'sourcecode') |
362 | run(cmd, use_http_param, source_path) |
363 | |
364 | # Update the download cache. |
365 | - download_cache = os.path.join(working_dir, 'download-cache') |
366 | + download_cache = os.path.join(code_dir, 'download-cache') |
367 | if os.path.exists(download_cache): |
368 | run('bzr', 'up', download_cache) |
369 | else: |
370 | run('bzr', 'co', '-v', '--lightweight', LP_SOURCE_DEPS, download_cache) |
371 | |
372 | # Link to the external sourcecode. |
373 | - if abs_external_path != working_dir: |
374 | + if abs_external_path != code_dir: |
375 | cmd = os.path.join( |
376 | - working_dir, 'utilities', 'link-external-sourcecode') |
377 | + code_dir, 'utilities', 'link-external-sourcecode') |
378 | run(cmd, |
379 | - '--target', working_dir, |
380 | + '--target', code_dir, |
381 | '--parent', external_path) |
382 | |
383 | |
384 | -def update_tree(working_dir): |
385 | - """Update the tree at working_dir with the latest LP code.""" |
386 | - with cd(working_dir): |
387 | +def update_tree(code_dir): |
388 | + """Update the tree at code_dir with the latest LP code.""" |
389 | + with cd(code_dir): |
390 | run('bzr', 'pull') |
391 | |
392 | |
393 | @@ -71,14 +72,16 @@ |
394 | """ |
395 | |
396 | steps = ( |
397 | - (initialize_directories, 'working_dir', 'external_path'), |
398 | - (update_dependencies, 'working_dir', 'external_path', 'use_http'), |
399 | - (update_tree, 'working_dir'), |
400 | + (initialize_directories, 'code_dir', 'external_path'), |
401 | + (update_dependencies, 'code_dir', 'external_path', 'use_http'), |
402 | + (update_tree, 'code_dir'), |
403 | ) |
404 | help = __doc__ |
405 | handlers = ( |
406 | # Normalize paths and default to cwd if none exists. |
407 | - handlers.handle_working_dir, |
408 | + handlers.handle_user, |
409 | + handlers.handle_directories, |
410 | + handlers.handle_code_dir, |
411 | ) |
412 | |
413 | @staticmethod |
414 | @@ -97,6 +100,13 @@ |
415 | help='Force bzr to use http to get the sourcecode ' |
416 | 'branches rather than using bzr+ssh.') |
417 | parser.add_argument( |
418 | - '-W', '--working-dir', default=LP_CODE_DIR, |
419 | - help='Path to branch to update. ' |
420 | - '[DEFAULT={0}]'.format(LP_CODE_DIR)) |
421 | + '--checkout-name', default=LP_CHECKOUT_NAME, |
422 | + help='Create a checkout with the given name. ' |
423 | + 'Ignored if --no-checkout is specified. ' |
424 | + 'Defaults to {0}.'.format(LP_CHECKOUT_NAME)) |
425 | + parser.add_argument( |
426 | + '-r', '--repository', default=LP_REPOSITORY_DIR, |
427 | + help='The directory of the Launchpad repository to be created. ' |
428 | + 'The directory must reside under the home directory of the ' |
429 | + 'given user (see -u argument). ' |
430 | + '[DEFAULT={0}]'.format(LP_REPOSITORY_DIR)) |
431 | |
432 | === added file 'lpsetup/tests/integration/common.py' |
433 | --- lpsetup/tests/integration/common.py 1970-01-01 00:00:00 +0000 |
434 | +++ lpsetup/tests/integration/common.py 2012-07-19 21:00:25 +0000 |
435 | @@ -0,0 +1,63 @@ |
436 | +#!/usr/bin/env python |
437 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
438 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
439 | + |
440 | +"""Common resources for integration tests.""" |
441 | + |
442 | +__metaclass__ = type |
443 | +__all__ = [ |
444 | + 'IntegrationTestBase', |
445 | + ] |
446 | + |
447 | +import argparse |
448 | +from datetime import datetime |
449 | + |
450 | + |
451 | +class IntegrationTestBase: |
452 | + |
453 | + banner_width = 70 |
454 | + test_type = None |
455 | + |
456 | + def __init__(self): |
457 | + self.parser = argparse.ArgumentParser() |
458 | + self.parser.add_argument( |
459 | + '--no-tear-down', action='store_true', default=False, |
460 | + help='Do not run the tear down method for debugging.') |
461 | + self.args = self.parser.parse_args() |
462 | + |
463 | + def banner(self, msg, width=banner_width): |
464 | + print '*' * width |
465 | + print '* ' + msg.ljust(width - 4) + ' *' |
466 | + print '*' * width |
467 | + |
468 | + def set_up(self): |
469 | + self.banner( |
470 | + 'Setting up the test environment for {}.'.format(self.test_type)) |
471 | + |
472 | + def tear_down(self): |
473 | + self.banner('Cleaning up.') |
474 | + |
475 | + def check_environment(self): |
476 | + self.banner('Checking test environment.') |
477 | + |
478 | + def do_test(self): |
479 | + self.banner('Running integration tests for {}.'.format(self.test_type)) |
480 | + |
481 | + def run(self): |
482 | + start_time = datetime.now() |
483 | + self.check_environment() |
484 | + try: |
485 | + self.set_up() |
486 | + try: |
487 | + self.do_test() |
488 | + except Exception as err: |
489 | + self.banner('Test failed. Sorry.') |
490 | + print err |
491 | + return 1 |
492 | + else: |
493 | + self.banner('Test succeeded.') |
494 | + return 0 |
495 | + finally: |
496 | + if not self.args.no_tear_down: |
497 | + self.tear_down() |
498 | + print "Run time: ", datetime.now() - start_time |
499 | |
500 | === added file 'lpsetup/tests/integration/lxc.py' |
501 | --- lpsetup/tests/integration/lxc.py 1970-01-01 00:00:00 +0000 |
502 | +++ lpsetup/tests/integration/lxc.py 2012-07-19 21:00:25 +0000 |
503 | @@ -0,0 +1,106 @@ |
504 | +#!/usr/bin/env python |
505 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
506 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
507 | + |
508 | +"""A simple, end-to-end integration test.""" |
509 | + |
510 | +from bzrlib.branch import Branch |
511 | +from bzrlib.errors import NotBranchError |
512 | +from bzrlib.missing import find_unmerged |
513 | +from bzrlib.status import show_tree_status |
514 | +from bzrlib.workingtree import WorkingTree |
515 | +from StringIO import StringIO |
516 | +from subprocess import check_call |
517 | +import sys |
518 | + |
519 | +from shelltoolbox import run |
520 | + |
521 | +from common import IntegrationTestBase |
522 | + |
523 | + |
524 | +LXC_NAME = 'lpsetup-testing' |
525 | + |
526 | + |
527 | +class LXCIntegrationTest(IntegrationTestBase): |
528 | + |
529 | + test_type = 'LXC container' |
530 | + repo = '~/launchpad-testing/lp-branches' |
531 | + |
532 | + def get_branch(self, dir='.'): |
533 | + branch, _ = Branch.open_containing(dir) |
534 | + return branch |
535 | + |
536 | + def get_push_location(self, branch=None): |
537 | + if branch is None: |
538 | + branch = self.get_branch() |
539 | + return branch.get_push_location() |
540 | + |
541 | + def check_environment(self): |
542 | + """Be sure the test environment doesn't exist.""" |
543 | + # We want to be really sure we do not clobber an existing LXC |
544 | + # container, therefore we make sure one doesn't exist before |
545 | + # proceeding. |
546 | + super(LXCIntegrationTest, self).check_environment() |
547 | + cmd = 'sudo lxc-info -n {}'.format(LXC_NAME) |
548 | + results = run(*cmd.split()) |
549 | + if results.split()[1] == 'RUNNING': |
550 | + # The container should not be active. |
551 | + raise RuntimeError( |
552 | + "An LXC container named '{}' is unexpectedly " |
553 | + "running.".format(LXC_NAME)) |
554 | + # Ensure the local branch has been pushed. |
555 | + # For some reason 'bzr missing' cannot use a 'lp:' URL. http or |
556 | + # bzr+ssh URLs work. |
557 | + self.branch = self.get_branch() |
558 | + self.push_location = self.get_push_location(self.branch) |
559 | + if self.push_location.startswith('lp:'): |
560 | + raise RuntimeError( |
561 | + "Push location must use http or bzr+ssh " |
562 | + "protocol: {}".format(self.push_location)) |
563 | + # Ensure the branch has been pushed and has no missing revisions. |
564 | + try: |
565 | + push_branch = self.get_branch(self.push_location) |
566 | + except NotBranchError: |
567 | + raise RuntimeError( |
568 | + 'The branch has never been pushed to Launchpad.') |
569 | + tree, _ = WorkingTree.open_containing_paths(None) |
570 | + stream = StringIO() |
571 | + show_tree_status(tree, to_file=stream) |
572 | + if stream.getvalue(): |
573 | + raise RuntimeError( |
574 | + 'The tree has uncommitted changes. Check them in ' |
575 | + 'and push to Launchpad.') |
576 | + near, far = find_unmerged(self.branch, push_branch) |
577 | + if near or far: |
578 | + raise RuntimeError('The local branch and the pushed version ' |
579 | + 'have diverged.') |
580 | + |
581 | + def set_up(self): |
582 | + """Set up the LXC container environment.""" |
583 | + super(LXCIntegrationTest, self).set_up() |
584 | + cmd = 'sudo python setup.py install' |
585 | + check_call(cmd.split()) |
586 | + |
587 | + def do_test(self): |
588 | + """Run an end-to-end integration tests of the non-LXC lpsetup story.""" |
589 | + super(LXCIntegrationTest, self).do_test() |
590 | + cmd = 'lp-setup install-lxc -B {} -r {} {}'.format( |
591 | + self.push_location, self.repo, LXC_NAME) |
592 | + check_call(cmd.split()) |
593 | + |
594 | + def tear_down(self): |
595 | + super(LXCIntegrationTest, self).tear_down() |
596 | + |
597 | + def lxc_cmd(cmd_name): |
598 | + cmd = 'sudo {} -n {}'.format(cmd_name, LXC_NAME) |
599 | + check_call(cmd.split()) |
600 | + |
601 | + try: |
602 | + lxc_cmd('lxc-stop') |
603 | + lxc_cmd('lxc-destroy') |
604 | + except: |
605 | + pass |
606 | + |
607 | + |
608 | +if __name__ == '__main__': |
609 | + sys.exit(LXCIntegrationTest().run()) |
610 | |
611 | === modified file 'lpsetup/tests/integration/non-lxc.py' |
612 | --- lpsetup/tests/integration/non-lxc.py 2012-07-10 19:58:56 +0000 |
613 | +++ lpsetup/tests/integration/non-lxc.py 2012-07-19 21:00:25 +0000 |
614 | @@ -14,96 +14,79 @@ |
615 | PIPE, |
616 | ) |
617 | |
618 | - |
619 | -def banner(s): |
620 | - width = 70 |
621 | - print '*' * width |
622 | - print '* ' + s.ljust(width - 4) + ' *' |
623 | - print '*' * width |
624 | - |
625 | - |
626 | -def on_remote(args): |
627 | - if type(args) == str: |
628 | - args = args.split() |
629 | - |
630 | - check_call('juju ssh 1 -o StrictHostKeyChecking=no' |
631 | - ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing'.split() |
632 | - + list(args)) |
633 | - |
634 | - |
635 | -def check_environment(): |
636 | - """Be sure the test environment doesn't exist.""" |
637 | - banner('Checking test environment.') |
638 | - # We want to be really sure we do not clobber an already-existing juju |
639 | - # environment. Therefore we make sure one doesn't exist before |
640 | - # bootstrapping. |
641 | - code = os.system('juju status -e lpsetup-testing') |
642 | - if code == 0: |
643 | - # The "juju status" should have failed. |
644 | - raise RuntimeError('A juju environment unexpectedly exists.') |
645 | - |
646 | - |
647 | -def set_up(): |
648 | - """Set up a juju-managed instance to run the tests on.""" |
649 | - banner('Setting up the test environment.') |
650 | - check_call('juju bootstrap -e lpsetup-testing'.split()) |
651 | - # XXX The "ubuntu" charm is broken, so it has to be loaded from the local |
652 | - # repository. Get it from lp:~charmers/charms/precise/ubuntu/trunk. |
653 | - check_call('juju deploy local:ubuntu --repository ~/juju-charms' |
654 | - ' -e lpsetup-testing --constraints instance-type=m1.large'.split()) |
655 | - |
656 | - start_time = time.time() |
657 | - while time.time() - start_time < 600: |
658 | - process = Popen('juju status -e lpsetup-testing'.split(), stdout=PIPE) |
659 | - stdout, stderr = process.communicate() |
660 | - if 'instance-state: running' in stdout: |
661 | - break |
662 | - else: |
663 | - raise RuntimeError('starting the instance took too long') |
664 | - |
665 | - # Even though the instance-state is "running", it's still not ready, so |
666 | - # wait a little while before continuing. |
667 | - time.sleep(30) |
668 | - |
669 | - on_remote('sudo apt-add-repository --yes ppa:yellow/ppa') |
670 | - on_remote('sudo apt-get update') |
671 | - on_remote('sudo apt-get install python-shelltoolbox') |
672 | - check_call('juju scp -o StrictHostKeyChecking=no' |
673 | - ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing' |
674 | - ' -r . 1:lpsetup'.split()) |
675 | - |
676 | - |
677 | -def do_test(): |
678 | - """Run an end-to-end integration tests of the non-LXC lpsetup story.""" |
679 | - banner('Running (non-LXC) integration test.') |
680 | - # Since the most common scenario is to have bzr whoami setup, we do that |
681 | - # instead of providing the arguments directly to lpsetup. |
682 | - on_remote(('bzr', 'whoami', '"Not A Real Person <no@example.com>"')) |
683 | - on_remote('cd lpsetup; ./lp-setup init-host'.split()) |
684 | - |
685 | - |
686 | -def tear_down(): |
687 | - banner('Cleaning up.') |
688 | - code = os.system('echo y | juju destroy-environment -e lpsetup-testing') |
689 | - if code != 0: |
690 | - raise RuntimeError('Destroying the test juju environment failed.') |
691 | - |
692 | - |
693 | -def main(): |
694 | - check_environment() |
695 | - try: |
696 | - set_up() |
697 | - try: |
698 | - do_test() |
699 | - except: |
700 | - banner('Test failed. Sorry.') |
701 | - return 1 |
702 | +from common import IntegrationTestBase |
703 | + |
704 | + |
705 | +class NonLXCIntegrationTest(IntegrationTestBase): |
706 | + |
707 | + test_type = 'non-LXC target' |
708 | + |
709 | + def on_remote(self, args): |
710 | + if type(args) == str: |
711 | + args = args.split() |
712 | + |
713 | + check_call('juju ssh 1 -o StrictHostKeyChecking=no' |
714 | + ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing'.split() |
715 | + + list(args)) |
716 | + |
717 | + def check_environment(self): |
718 | + """Be sure the test environment doesn't exist.""" |
719 | + # We want to be really sure we do not clobber an already-existing juju |
720 | + # environment. Therefore we make sure one doesn't exist before |
721 | + # bootstrapping. |
722 | + super(NonLXCIntegrationTest, self).check_environment() |
723 | + code = os.system('juju status -e lpsetup-testing') |
724 | + if code == 0: |
725 | + # The "juju status" should have failed. |
726 | + raise RuntimeError('A juju environment unexpectedly exists.') |
727 | + |
728 | + def set_up(self): |
729 | + """Set up a juju-managed instance to run the tests on.""" |
730 | + super(NonLXCIntegrationTest, self).set_up() |
731 | + check_call('juju bootstrap -e lpsetup-testing'.split()) |
732 | + # XXX The "ubuntu" charm is broken, so it has to be loaded from the |
733 | + # local repository. Get it from |
734 | + # lp:~charmers/charms/precise/ubuntu/trunk. |
735 | + check_call('juju deploy local:ubuntu --repository ~/juju-charms' |
736 | + ' -e lpsetup-testing --constraints instance-type=m1.large'.split()) |
737 | + |
738 | + start_time = time.time() |
739 | + while time.time() - start_time < 600: |
740 | + process = Popen( |
741 | + 'juju status -e lpsetup-testing'.split(), stdout=PIPE) |
742 | + stdout, stderr = process.communicate() |
743 | + if 'instance-state: running' in stdout: |
744 | + break |
745 | else: |
746 | - banner('Test succeeded.') |
747 | - return 0 |
748 | - finally: |
749 | - tear_down() |
750 | + raise RuntimeError('starting the instance took too long') |
751 | + |
752 | + # Even though the instance-state is "running", it's still not ready, so |
753 | + # wait a little while before continuing. |
754 | + time.sleep(30) |
755 | + |
756 | + self.on_remote('sudo apt-add-repository --yes ppa:yellow/ppa') |
757 | + self.on_remote('sudo apt-get update') |
758 | + self.on_remote('sudo apt-get install python-shelltoolbox') |
759 | + check_call('juju scp -o StrictHostKeyChecking=no' |
760 | + ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing' |
761 | + ' -r . 1:lpsetup'.split()) |
762 | + |
763 | + def do_test(self): |
764 | + """Run an end-to-end integration tests of the non-LXC lpsetup story.""" |
765 | + # Since the most common scenario is to have bzr whoami setup, we do |
766 | + # that instead of providing the arguments directly to lpsetup. |
767 | + super(NonLXCIntegrationTest, self).do_test() |
768 | + self.on_remote( |
769 | + ('bzr', 'whoami', '"Not A Real Person <no@example.com>"')) |
770 | + self.on_remote('cd lpsetup; ./lp-setup init-host'.split()) |
771 | + |
772 | + def tear_down(self): |
773 | + super(NonLXCIntegrationTest, self).tear_down() |
774 | + code = os.system( |
775 | + 'echo y | juju destroy-environment -e lpsetup-testing') |
776 | + if code != 0: |
777 | + raise RuntimeError('Destroying the test juju environment failed.') |
778 | |
779 | |
780 | if __name__ == '__main__': |
781 | - sys.exit(main()) |
782 | + sys.exit(NonLXCIntegrationTest().run()) |
783 | |
784 | === modified file 'lpsetup/tests/subcommands/test_finish_inithost.py' |
785 | --- lpsetup/tests/subcommands/test_finish_inithost.py 2012-07-12 18:23:37 +0000 |
786 | +++ lpsetup/tests/subcommands/test_finish_inithost.py 2012-07-19 21:00:25 +0000 |
787 | @@ -19,9 +19,10 @@ |
788 | |
789 | |
790 | def get_arguments(): |
791 | - code_dir = '~/' + get_random_string() |
792 | + repo = '~/' + get_random_string() |
793 | + checkout = get_random_string() |
794 | user = get_random_string() |
795 | - return ('-c', code_dir, '-u', user) |
796 | + return ('-r', repo, '--checkout-name', checkout, '-u', user) |
797 | |
798 | |
799 | class FinishInitHostTest(StepsBasedSubCommandTestMixin, unittest.TestCase): |
800 | @@ -31,6 +32,7 @@ |
801 | expected_handlers = ( |
802 | handlers.handle_user, |
803 | handlers.handle_directories, |
804 | + handlers.handle_code_dir, |
805 | ) |
806 | |
807 | @property |
808 | |
809 | === modified file 'lpsetup/tests/subcommands/test_install_lxc.py' |
810 | --- lpsetup/tests/subcommands/test_install_lxc.py 2012-07-12 22:12:09 +0000 |
811 | +++ lpsetup/tests/subcommands/test_install_lxc.py 2012-07-19 21:00:25 +0000 |
812 | @@ -34,7 +34,8 @@ |
813 | |
814 | finish_inithost_in_lxc_step = ( |
815 | install_lxc.finish_inithost_in_lxc, [ |
816 | - 'lxc_name', 'ssh_key_path', 'home_dir', 'user', 'code_dir', |
817 | + 'lxc_name', 'ssh_key_path', 'home_dir', 'user', 'repository', |
818 | + 'checkout_name', |
819 | ]) |
820 | |
821 | |
822 | @@ -52,6 +53,7 @@ |
823 | expected_arguments = get_arguments() |
824 | expected_handlers = test_initlxc.InitLxcTest.expected_handlers + ( |
825 | handlers.handle_directories, |
826 | + handlers.handle_code_dir, |
827 | handlers.handle_source, |
828 | ) |
829 | expected_steps = test_initlxc.InitLxcTest.expected_steps + ( |
830 | |
831 | === modified file 'lpsetup/tests/subcommands/test_update.py' |
832 | --- lpsetup/tests/subcommands/test_update.py 2012-07-13 15:55:40 +0000 |
833 | +++ lpsetup/tests/subcommands/test_update.py 2012-07-19 21:00:25 +0000 |
834 | @@ -18,15 +18,15 @@ |
835 | return ( |
836 | '--external-path', get_random_string(), |
837 | '--use-http', |
838 | - '-e', '../devel', |
839 | - '-W', '~/' + get_random_string(), |
840 | + '--repository', get_random_string(), |
841 | + '--checkout-name', get_random_string(), |
842 | ) |
843 | |
844 | init_dir_step = ( |
845 | - update.initialize_directories, ['working_dir', 'external_path']) |
846 | + update.initialize_directories, ['code_dir', 'external_path']) |
847 | update_dep_step = ( |
848 | - update.update_dependencies, ['working_dir', 'external_path', 'use_http']) |
849 | -update_tree_step = (update.update_tree, ['working_dir']) |
850 | + update.update_dependencies, ['code_dir', 'external_path', 'use_http']) |
851 | +update_tree_step = (update.update_tree, ['code_dir']) |
852 | |
853 | |
854 | class UpdateTest(StepsBasedSubCommandTestMixin, unittest.TestCase): |
855 | @@ -35,7 +35,9 @@ |
856 | sub_command_class = update.SubCommand |
857 | expected_arguments = get_arguments() |
858 | expected_handlers = ( |
859 | - handlers.handle_working_dir, |
860 | + handlers.handle_user, |
861 | + handlers.handle_directories, |
862 | + handlers.handle_code_dir, |
863 | ) |
864 | expected_steps = ( |
865 | init_dir_step, |
866 | |
867 | === modified file 'pre-commit.sh' |
868 | --- pre-commit.sh 2012-07-11 15:50:01 +0000 |
869 | +++ pre-commit.sh 2012-07-19 21:00:25 +0000 |
870 | @@ -1,4 +1,4 @@ |
871 | #!/bin/bash |
872 | |
873 | -pyfiles=`find . -name "*.py" | grep -v distribute_setup.py` |
874 | -pocketlint $pyfiles && pep8 $pyfiles && nosetests |
875 | +pyfiles=`find . -name build -prune -o -name "*.py" | grep -v distribute_setup.py` |
876 | +pocketlint $pyfiles && pep8 --exclude=build $pyfiles && nosetests |
The LXC-base test can re-use an existing launchpad repository (~/launchpad- testing) if it exists, which speeds things up considerably. With an existing repository the test took 9m13.