Merge lp:~bac/lpsetup/lxc-integration into lp:lpsetup
- lxc-integration
- Merge into trunk
| Status: | Merged |
|---|---|
| Approved by: | Brad Crittenden on 2012-07-19 |
| 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 | 2012-07-19 | Approve on 2012-07-19 |
|
Review via email:
|
|||
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.