Merge lp:~frankban/lpsetup/commands-unittests into lp:lpsetup
- commands-unittests
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Francesco Banconi |
Approved revision: | 43 |
Merged at revision: | 34 |
Proposed branch: | lp:~frankban/lpsetup/commands-unittests |
Merge into: | lp:lpsetup |
Diff against target: |
627 lines (+376/-51) 11 files modified
lpsetup/argparser.py (+39/-24) lpsetup/subcommands/get.py (+0/-1) lpsetup/subcommands/install.py (+4/-2) lpsetup/subcommands/lxcinstall.py (+7/-10) lpsetup/tests/subcommands/test_get.py (+66/-0) lpsetup/tests/subcommands/test_inithost.py (+50/-0) lpsetup/tests/subcommands/test_install.py (+55/-0) lpsetup/tests/subcommands/test_lxcinstall.py (+75/-0) lpsetup/tests/subcommands/test_version.py (+0/-1) lpsetup/tests/test_argparser.py (+14/-8) lpsetup/tests/utils.py (+66/-5) |
To merge this branch: | bzr merge lp:~frankban/lpsetup/commands-unittests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+112094@code.launchpad.net |
Commit message
Added sub commands unit tests.
Description of the change
== Changes ==
sub command tests: added unit tests for the get, inithost, install and lxcinstall sub commands. Each TestCase is a subclass of `tests.
Note that update and branch sub commands are not tested: they are obsolete and will either go away or be heavily modified before the end of the process.
argparser.
argparser.
get: removed *needs_root* to avoid confusion: *needs_root* is unnecessary since the command overrides *get_needs_root()*.
lxcinstall: now the command subclasses install and reuses inithost, install and get steps. A consequence of this change is that now the command correctly handles `directory` and `dependencies_dir` arguments.
tests.utils.
tests.utils: added a helper function returning a random string.
tests.test_
Benji York (benji) wrote : | # |
Francesco Banconi (frankban) wrote : | # |
Thanks Benji, I've updated the branch following your suggestions, except for the *get_arguments* method as we discussed in IRC.
Preview Diff
1 | === modified file 'lpsetup/argparser.py' | |||
2 | --- lpsetup/argparser.py 2012-03-30 11:02:11 +0000 | |||
3 | +++ lpsetup/argparser.py 2012-06-26 15:04:24 +0000 | |||
4 | @@ -416,15 +416,15 @@ | |||
5 | 416 | 416 | ||
6 | 417 | steps = () | 417 | steps = () |
7 | 418 | 418 | ||
17 | 419 | def __init__(self, *args, **kwargs): | 419 | def add_arguments(self, parser): |
18 | 420 | super(StepsBasedSubCommand, self).__init__(*args, **kwargs) | 420 | super(StepsBasedSubCommand, self).add_arguments(parser) |
19 | 421 | self._step_names = [] | 421 | step_names = [self._get_step_name(i[0]) for i in self.steps] |
20 | 422 | self._steps = {} | 422 | parser.add_argument( |
21 | 423 | for step_args in self.steps: | 423 | '-s', '--steps', nargs='+', choices=step_names, |
22 | 424 | step, args = step_args[0], step_args[1:] | 424 | help='Call one or more internal functions.') |
23 | 425 | step_name = self._get_step_name(step) | 425 | parser.add_argument( |
24 | 426 | self._step_names.append(step_name) | 426 | '--skip-steps', nargs='+', choices=step_names, |
25 | 427 | self._steps[step_name] = (step, args) | 427 | help='Skip one or more internal functions.') |
26 | 428 | 428 | ||
27 | 429 | def _get_step_name(self, step): | 429 | def _get_step_name(self, step): |
28 | 430 | """Return the string representation of a step callable. | 430 | """Return the string representation of a step callable. |
29 | @@ -450,28 +450,43 @@ | |||
30 | 450 | except AttributeError: | 450 | except AttributeError: |
31 | 451 | return step.__name__ | 451 | return step.__name__ |
32 | 452 | 452 | ||
41 | 453 | def add_arguments(self, parser): | 453 | def _include_step(self, step_name, namespace): |
42 | 454 | super(StepsBasedSubCommand, self).add_arguments(parser) | 454 | """Return True if the given *step_name* must be run, False otherwise. |
43 | 455 | parser.add_argument( | 455 | |
44 | 456 | '-s', '--steps', nargs='+', choices=self._step_names, | 456 | A step is included in the command execution if the step included in |
45 | 457 | help='Call one or more internal functions.') | 457 | `--steps` (or steps is None) and is not included in `--skip-steps`. |
46 | 458 | parser.add_argument( | 458 | """ |
47 | 459 | '--skip-steps', nargs='+', choices=self._step_names, | 459 | steps_to_skip = namespace.skip_steps or [] |
48 | 460 | help='Skip one or more internal functions.') | 460 | steps_to_run = namespace.steps |
49 | 461 | # If explicitly told to skip a step, then skip it. | ||
50 | 462 | if step_name in steps_to_skip: | ||
51 | 463 | return False | ||
52 | 464 | # If no list of steps was provided then any non-skipped are to be run. | ||
53 | 465 | if steps_to_run is None: | ||
54 | 466 | return True | ||
55 | 467 | # A list of steps to run was provided, so the step has to be included | ||
56 | 468 | # in order to be run. | ||
57 | 469 | return step_name in steps_to_run | ||
58 | 470 | |||
59 | 471 | def get_steps(self, namespace): | ||
60 | 472 | """Return a list of *(step_name, step_callable, step_args)* tuples.""" | ||
61 | 473 | steps = [] | ||
62 | 474 | for step_arg_names in self.steps: | ||
63 | 475 | step, arg_names = step_arg_names[0], step_arg_names[1:] | ||
64 | 476 | step_name = self._get_step_name(step) | ||
65 | 477 | if self._include_step(step_name, namespace): | ||
66 | 478 | args = [getattr(namespace, i) for i in arg_names] | ||
67 | 479 | steps.append((step_name, step, args)) | ||
68 | 480 | return steps | ||
69 | 461 | 481 | ||
70 | 462 | def _call_step(self, namespace, step, args): | 482 | def _call_step(self, namespace, step, args): |
71 | 463 | """Default callable used to run a `step`, using given `args`.""" | 483 | """Default callable used to run a `step`, using given `args`.""" |
72 | 464 | return step(*args) | 484 | return step(*args) |
73 | 465 | 485 | ||
74 | 466 | def handle(self, namespace): | 486 | def handle(self, namespace): |
75 | 467 | skip_steps = namespace.skip_steps or [] | ||
76 | 468 | step_names = filter( | ||
77 | 469 | lambda step_name: step_name not in skip_steps, | ||
78 | 470 | namespace.steps or self._step_names) | ||
79 | 471 | default_step_runner = self._call_step | 487 | default_step_runner = self._call_step |
83 | 472 | for step_name in step_names: | 488 | for step_name, step, args in self.get_steps(namespace): |
84 | 473 | step, arg_names = self._steps[step_name] | 489 | # Run the step using a dynamic dispatcher. |
82 | 474 | args = [getattr(namespace, i) for i in arg_names] | ||
85 | 475 | step_runner = getattr( | 490 | step_runner = getattr( |
86 | 476 | self, 'call_' + step_name, default_step_runner) | 491 | self, 'call_' + step_name, default_step_runner) |
87 | 477 | try: | 492 | try: |
88 | 478 | 493 | ||
89 | === modified file 'lpsetup/subcommands/get.py' | |||
90 | --- lpsetup/subcommands/get.py 2012-06-25 19:24:56 +0000 | |||
91 | +++ lpsetup/subcommands/get.py 2012-06-26 15:04:24 +0000 | |||
92 | @@ -158,7 +158,6 @@ | |||
93 | 158 | ) | 158 | ) |
94 | 159 | 159 | ||
95 | 160 | help = __doc__ | 160 | help = __doc__ |
96 | 161 | needs_root = True | ||
97 | 162 | validators = ( | 161 | validators = ( |
98 | 163 | handlers.handle_user, | 162 | handlers.handle_user, |
99 | 164 | handlers.handle_lpuser, | 163 | handlers.handle_lpuser, |
100 | 165 | 164 | ||
101 | === modified file 'lpsetup/subcommands/install.py' | |||
102 | --- lpsetup/subcommands/install.py 2012-06-22 20:50:41 +0000 | |||
103 | +++ lpsetup/subcommands/install.py 2012-06-26 15:04:24 +0000 | |||
104 | @@ -99,11 +99,13 @@ | |||
105 | 99 | """Install the Launchpad environment.""" | 99 | """Install the Launchpad environment.""" |
106 | 100 | 100 | ||
107 | 101 | # The steps for "install" are a superset of the steps for "inithost". | 101 | # The steps for "install" are a superset of the steps for "inithost". |
108 | 102 | setup_bzr_locations_step = (setup_bzr_locations, | ||
109 | 103 | 'user', 'lpuser', 'directory') | ||
110 | 104 | |||
111 | 102 | steps = ( | 105 | steps = ( |
112 | 103 | inithost.SubCommand.initialize_step, | 106 | inithost.SubCommand.initialize_step, |
113 | 104 | get.SubCommand.fetch_step, | 107 | get.SubCommand.fetch_step, |
116 | 105 | (setup_bzr_locations, | 108 | setup_bzr_locations_step, |
115 | 106 | 'user', 'lpuser', 'directory'), | ||
117 | 107 | inithost.SubCommand.setup_apt_step, | 109 | inithost.SubCommand.setup_apt_step, |
118 | 108 | (setup_launchpad, | 110 | (setup_launchpad, |
119 | 109 | 'user', 'dependencies_dir', 'directory', 'valid_ssh_keys'), | 111 | 'user', 'dependencies_dir', 'directory', 'valid_ssh_keys'), |
120 | 110 | 112 | ||
121 | === modified file 'lpsetup/subcommands/lxcinstall.py' | |||
122 | --- lpsetup/subcommands/lxcinstall.py 2012-06-25 17:55:10 +0000 | |||
123 | +++ lpsetup/subcommands/lxcinstall.py 2012-06-26 15:04:24 +0000 | |||
124 | @@ -43,6 +43,7 @@ | |||
125 | 43 | SCRIPTS, | 43 | SCRIPTS, |
126 | 44 | ) | 44 | ) |
127 | 45 | from lpsetup.subcommands import ( | 45 | from lpsetup.subcommands import ( |
128 | 46 | get, | ||
129 | 46 | inithost, | 47 | inithost, |
130 | 47 | install, | 48 | install, |
131 | 48 | ) | 49 | ) |
132 | @@ -201,16 +202,13 @@ | |||
133 | 201 | subprocess.call(['lxc-stop', '-n', lxc_name]) | 202 | subprocess.call(['lxc-stop', '-n', lxc_name]) |
134 | 202 | 203 | ||
135 | 203 | 204 | ||
137 | 204 | class SubCommand(inithost.SubCommand): | 205 | class SubCommand(install.SubCommand): |
138 | 205 | """Install the Launchpad environment inside an LXC.""" | 206 | """Install the Launchpad environment inside an LXC.""" |
139 | 206 | 207 | ||
140 | 207 | steps = ( | 208 | steps = ( |
147 | 208 | (inithost.initialize, | 209 | inithost.SubCommand.initialize_step, |
148 | 209 | 'user', 'full_name', 'email', 'lpuser', | 210 | get.SubCommand.fetch_step, |
149 | 210 | 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path', | 211 | install.SubCommand.setup_bzr_locations_step, |
144 | 211 | 'feed_random', 'dependencies_dir', 'directory'), | ||
145 | 212 | (install.setup_bzr_locations, | ||
146 | 213 | 'user', 'lpuser', 'directory'), | ||
150 | 214 | (create_scripts, | 212 | (create_scripts, |
151 | 215 | 'user', 'lxc_name', 'ssh_key_path'), | 213 | 'user', 'lxc_name', 'ssh_key_path'), |
152 | 216 | (create_lxc, | 214 | (create_lxc, |
153 | @@ -231,8 +229,7 @@ | |||
154 | 231 | 229 | ||
155 | 232 | def get_validators(self, namespace): | 230 | def get_validators(self, namespace): |
156 | 233 | validators = super(SubCommand, self).get_validators(namespace) | 231 | validators = super(SubCommand, self).get_validators(namespace) |
159 | 234 | return validators + ( | 232 | return validators + (handlers.handle_testing,) |
158 | 235 | handlers.handle_testing, handlers.handle_directories) | ||
160 | 236 | 233 | ||
161 | 237 | def call_create_scripts(self, namespace, step, args): | 234 | def call_create_scripts(self, namespace, step, args): |
162 | 238 | """Run the `create_scripts` step only if the related flag is set.""" | 235 | """Run the `create_scripts` step only if the related flag is set.""" |
163 | @@ -257,7 +254,7 @@ | |||
164 | 257 | parser.add_argument( | 254 | parser.add_argument( |
165 | 258 | '-C', '--create-scripts', action='store_true', | 255 | '-C', '--create-scripts', action='store_true', |
166 | 259 | help='Create the scripts used by buildbot for parallel testing.') | 256 | help='Create the scripts used by buildbot for parallel testing.') |
168 | 260 | # The following flag is not present in the install sub command since | 257 | # The following flag is not present in the inithost sub command since |
169 | 261 | # subunit is always installed there as a dependency of | 258 | # subunit is always installed there as a dependency of |
170 | 262 | # launchpad-developer-dependencies. | 259 | # launchpad-developer-dependencies. |
171 | 263 | parser.add_argument( | 260 | parser.add_argument( |
172 | 264 | 261 | ||
173 | === added file 'lpsetup/tests/subcommands/test_get.py' | |||
174 | --- lpsetup/tests/subcommands/test_get.py 1970-01-01 00:00:00 +0000 | |||
175 | +++ lpsetup/tests/subcommands/test_get.py 2012-06-26 15:04:24 +0000 | |||
176 | @@ -0,0 +1,66 @@ | |||
177 | 1 | #!/usr/bin/env python | ||
178 | 2 | # Copyright 2012 Canonical Ltd. This software is licensed under the | ||
179 | 3 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
180 | 4 | |||
181 | 5 | """Tests for the get sub command.""" | ||
182 | 6 | |||
183 | 7 | import unittest | ||
184 | 8 | |||
185 | 9 | from lpsetup import handlers | ||
186 | 10 | from lpsetup.subcommands import get | ||
187 | 11 | from lpsetup.tests.utils import ( | ||
188 | 12 | get_random_string, | ||
189 | 13 | StepsBasedSubCommandTestMixin, | ||
190 | 14 | ) | ||
191 | 15 | |||
192 | 16 | |||
193 | 17 | fetch_step = ( | ||
194 | 18 | get.fetch, ['user', 'directory', 'dependencies_dir', 'valid_ssh_keys']) | ||
195 | 19 | update_launchpad_step = ( | ||
196 | 20 | get.update_launchpad, ['user', 'dependencies_dir', 'directory', | ||
197 | 21 | 'make_schema', 'apt']) | ||
198 | 22 | link_sourcecode_in_branches_step = ( | ||
199 | 23 | get.link_sourcecode_in_branches, ['user', 'dependencies_dir', | ||
200 | 24 | 'directory']) | ||
201 | 25 | |||
202 | 26 | |||
203 | 27 | def get_update_arguments(): | ||
204 | 28 | user = get_random_string() | ||
205 | 29 | directory = '~/' + get_random_string() | ||
206 | 30 | dependencies_dir = '~/' + get_random_string() | ||
207 | 31 | ssh_key_name = get_random_string() | ||
208 | 32 | return ( | ||
209 | 33 | '-u', user, '-c', directory, '--make-schema', | ||
210 | 34 | '-d', dependencies_dir, '-S', ssh_key_name, | ||
211 | 35 | ) | ||
212 | 36 | |||
213 | 37 | |||
214 | 38 | def get_arguments(): | ||
215 | 39 | email = get_random_string() | ||
216 | 40 | full_name = get_random_string() + '@example.com' | ||
217 | 41 | lpuser = get_random_string() | ||
218 | 42 | private_key = get_random_string() | ||
219 | 43 | public_key = get_random_string() | ||
220 | 44 | return get_update_arguments() + ( | ||
221 | 45 | '-e', email, '-f', full_name, '-l', lpuser, | ||
222 | 46 | '-v', private_key, '-b', public_key, | ||
223 | 47 | '--no-repositories', '--feed-random') | ||
224 | 48 | |||
225 | 49 | |||
226 | 50 | class GetTest(StepsBasedSubCommandTestMixin, unittest.TestCase): | ||
227 | 51 | |||
228 | 52 | sub_command_class = get.SubCommand | ||
229 | 53 | expected_arguments = get_arguments() | ||
230 | 54 | expected_handlers = ( | ||
231 | 55 | handlers.handle_user, | ||
232 | 56 | handlers.handle_lpuser, | ||
233 | 57 | handlers.handle_userdata, | ||
234 | 58 | handlers.handle_ssh_keys, | ||
235 | 59 | handlers.handle_directories, | ||
236 | 60 | ) | ||
237 | 61 | expected_steps = ( | ||
238 | 62 | fetch_step, | ||
239 | 63 | update_launchpad_step, | ||
240 | 64 | link_sourcecode_in_branches_step, | ||
241 | 65 | ) | ||
242 | 66 | needs_root = False | ||
243 | 0 | 67 | ||
244 | === added file 'lpsetup/tests/subcommands/test_inithost.py' | |||
245 | --- lpsetup/tests/subcommands/test_inithost.py 1970-01-01 00:00:00 +0000 | |||
246 | +++ lpsetup/tests/subcommands/test_inithost.py 2012-06-26 15:04:24 +0000 | |||
247 | @@ -0,0 +1,50 @@ | |||
248 | 1 | #!/usr/bin/env python | ||
249 | 2 | # Copyright 2012 Canonical Ltd. This software is licensed under the | ||
250 | 3 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
251 | 4 | |||
252 | 5 | """Tests for the inithost sub command.""" | ||
253 | 6 | |||
254 | 7 | import unittest | ||
255 | 8 | |||
256 | 9 | from lpsetup import handlers | ||
257 | 10 | from lpsetup.subcommands import inithost | ||
258 | 11 | from lpsetup.tests.utils import ( | ||
259 | 12 | get_random_string, | ||
260 | 13 | StepsBasedSubCommandTestMixin, | ||
261 | 14 | ) | ||
262 | 15 | |||
263 | 16 | |||
264 | 17 | initialize_step = ( | ||
265 | 18 | inithost.initialize, ['user', 'full_name', 'email', 'lpuser', | ||
266 | 19 | 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path', | ||
267 | 20 | 'feed_random']) | ||
268 | 21 | setup_apt_step = (inithost.setup_apt, ['no_repositories']) | ||
269 | 22 | |||
270 | 23 | |||
271 | 24 | def get_arguments(): | ||
272 | 25 | user = get_random_string() | ||
273 | 26 | email = get_random_string() | ||
274 | 27 | full_name = get_random_string() + '@example.com' | ||
275 | 28 | lpuser = get_random_string() | ||
276 | 29 | private_key = get_random_string() | ||
277 | 30 | public_key = get_random_string() | ||
278 | 31 | ssh_key_name = get_random_string() | ||
279 | 32 | return ( | ||
280 | 33 | '-u', user, '-e', email, '-f', full_name, '-l', lpuser, | ||
281 | 34 | '-v', private_key, '-b', public_key, '-S', ssh_key_name) | ||
282 | 35 | |||
283 | 36 | |||
284 | 37 | class InithostTest(StepsBasedSubCommandTestMixin, unittest.TestCase): | ||
285 | 38 | |||
286 | 39 | sub_command_class = inithost.SubCommand | ||
287 | 40 | expected_arguments = get_arguments() | ||
288 | 41 | expected_handlers = ( | ||
289 | 42 | handlers.handle_user, | ||
290 | 43 | handlers.handle_lpuser, | ||
291 | 44 | handlers.handle_userdata, | ||
292 | 45 | handlers.handle_ssh_keys, | ||
293 | 46 | ) | ||
294 | 47 | expected_steps = (initialize_step, setup_apt_step) | ||
295 | 48 | needs_root = True | ||
296 | 49 | |||
297 | 50 | |||
298 | 0 | 51 | ||
299 | === added file 'lpsetup/tests/subcommands/test_install.py' | |||
300 | --- lpsetup/tests/subcommands/test_install.py 1970-01-01 00:00:00 +0000 | |||
301 | +++ lpsetup/tests/subcommands/test_install.py 2012-06-26 15:04:24 +0000 | |||
302 | @@ -0,0 +1,55 @@ | |||
303 | 1 | #!/usr/bin/env python | ||
304 | 2 | # Copyright 2012 Canonical Ltd. This software is licensed under the | ||
305 | 3 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
306 | 4 | |||
307 | 5 | """Tests for the install sub command.""" | ||
308 | 6 | |||
309 | 7 | import unittest | ||
310 | 8 | |||
311 | 9 | from lpsetup import handlers | ||
312 | 10 | from lpsetup.subcommands import install | ||
313 | 11 | from lpsetup.tests.subcommands import ( | ||
314 | 12 | test_get, | ||
315 | 13 | test_inithost, | ||
316 | 14 | ) | ||
317 | 15 | from lpsetup.tests.utils import ( | ||
318 | 16 | get_random_string, | ||
319 | 17 | StepsBasedSubCommandTestMixin, | ||
320 | 18 | ) | ||
321 | 19 | |||
322 | 20 | |||
323 | 21 | setup_bzr_locations_step = ( | ||
324 | 22 | install.setup_bzr_locations, ['user', 'lpuser', 'directory']) | ||
325 | 23 | setup_launchpad_step = ( | ||
326 | 24 | install.setup_launchpad, ['user', 'dependencies_dir', 'directory', | ||
327 | 25 | 'valid_ssh_keys']) | ||
328 | 26 | |||
329 | 27 | |||
330 | 28 | def get_arguments(): | ||
331 | 29 | inithost_arguments = test_inithost.get_arguments() | ||
332 | 30 | dependencies_dir = '~/' + get_random_string() | ||
333 | 31 | directory = '~/' + get_random_string() | ||
334 | 32 | return inithost_arguments + ('-d', dependencies_dir, '-c', directory) | ||
335 | 33 | |||
336 | 34 | |||
337 | 35 | class InstallTest(StepsBasedSubCommandTestMixin, unittest.TestCase): | ||
338 | 36 | |||
339 | 37 | sub_command_class = install.SubCommand | ||
340 | 38 | expected_arguments = get_arguments() | ||
341 | 39 | expected_handlers = ( | ||
342 | 40 | handlers.handle_user, | ||
343 | 41 | handlers.handle_lpuser, | ||
344 | 42 | handlers.handle_userdata, | ||
345 | 43 | handlers.handle_ssh_keys, | ||
346 | 44 | handlers.handle_directories, | ||
347 | 45 | ) | ||
348 | 46 | expected_steps = ( | ||
349 | 47 | test_inithost.initialize_step, | ||
350 | 48 | test_get.fetch_step, | ||
351 | 49 | setup_bzr_locations_step, | ||
352 | 50 | test_inithost.setup_apt_step, | ||
353 | 51 | setup_launchpad_step, | ||
354 | 52 | ) | ||
355 | 53 | needs_root = True | ||
356 | 54 | |||
357 | 55 | |||
358 | 0 | 56 | ||
359 | === added file 'lpsetup/tests/subcommands/test_lxcinstall.py' | |||
360 | --- lpsetup/tests/subcommands/test_lxcinstall.py 1970-01-01 00:00:00 +0000 | |||
361 | +++ lpsetup/tests/subcommands/test_lxcinstall.py 2012-06-26 15:04:24 +0000 | |||
362 | @@ -0,0 +1,75 @@ | |||
363 | 1 | #!/usr/bin/env python | ||
364 | 2 | # Copyright 2012 Canonical Ltd. This software is licensed under the | ||
365 | 3 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
366 | 4 | |||
367 | 5 | """Tests for the lxcinstall sub command.""" | ||
368 | 6 | |||
369 | 7 | import random | ||
370 | 8 | import unittest | ||
371 | 9 | |||
372 | 10 | from lpsetup import ( | ||
373 | 11 | handlers, | ||
374 | 12 | settings, | ||
375 | 13 | ) | ||
376 | 14 | from lpsetup.subcommands import lxcinstall | ||
377 | 15 | from lpsetup.tests.subcommands import ( | ||
378 | 16 | test_get, | ||
379 | 17 | test_inithost, | ||
380 | 18 | test_install, | ||
381 | 19 | ) | ||
382 | 20 | from lpsetup.tests.utils import ( | ||
383 | 21 | get_random_string, | ||
384 | 22 | StepsBasedSubCommandTestMixin, | ||
385 | 23 | ) | ||
386 | 24 | |||
387 | 25 | |||
388 | 26 | create_scripts_step = ( | ||
389 | 27 | lxcinstall.create_scripts, ['user', 'lxc_name', 'ssh_key_path']) | ||
390 | 28 | create_lxc_step = ( | ||
391 | 29 | lxcinstall.create_lxc, ['user', 'lxc_name', 'lxc_arch', 'lxc_os', | ||
392 | 30 | 'install_subunit']) | ||
393 | 31 | start_lxc_step = (lxcinstall.start_lxc, ['lxc_name']) | ||
394 | 32 | wait_for_lxc_step = (lxcinstall.wait_for_lxc, ['lxc_name', 'ssh_key_path']) | ||
395 | 33 | initialize_lxc_step = ( | ||
396 | 34 | lxcinstall.initialize_lxc, ['lxc_name', 'ssh_key_path', 'lxc_os']) | ||
397 | 35 | setup_launchpad_lxc_step = ( | ||
398 | 36 | lxcinstall.setup_launchpad_lxc, ['user', 'dependencies_dir', 'directory', | ||
399 | 37 | 'valid_ssh_keys', 'ssh_key_path', 'lxc_name']) | ||
400 | 38 | stop_lxc_step = (lxcinstall.stop_lxc, ['lxc_name', 'ssh_key_path']) | ||
401 | 39 | |||
402 | 40 | |||
403 | 41 | def get_arguments(): | ||
404 | 42 | lxc_name = get_random_string() | ||
405 | 43 | lxc_arch = random.choice(['i386', 'amd64']) | ||
406 | 44 | lxc_os = random.choice(settings.LXC_GUEST_CHOICES) | ||
407 | 45 | return test_inithost.get_arguments() + ( | ||
408 | 46 | '-n', lxc_name, '-A', lxc_arch, '-r', lxc_os, | ||
409 | 47 | '--create-scripts', '--install-subunit', '--testing' | ||
410 | 48 | ) | ||
411 | 49 | |||
412 | 50 | |||
413 | 51 | class LxcInstallTest(StepsBasedSubCommandTestMixin, unittest.TestCase): | ||
414 | 52 | |||
415 | 53 | sub_command_class = lxcinstall.SubCommand | ||
416 | 54 | expected_arguments = get_arguments() | ||
417 | 55 | expected_handlers = ( | ||
418 | 56 | handlers.handle_user, | ||
419 | 57 | handlers.handle_lpuser, | ||
420 | 58 | handlers.handle_userdata, | ||
421 | 59 | handlers.handle_ssh_keys, | ||
422 | 60 | handlers.handle_directories, | ||
423 | 61 | handlers.handle_testing, | ||
424 | 62 | ) | ||
425 | 63 | expected_steps = ( | ||
426 | 64 | test_inithost.initialize_step, | ||
427 | 65 | test_get.fetch_step, | ||
428 | 66 | test_install.setup_bzr_locations_step, | ||
429 | 67 | create_scripts_step, | ||
430 | 68 | create_lxc_step, | ||
431 | 69 | start_lxc_step, | ||
432 | 70 | wait_for_lxc_step, | ||
433 | 71 | initialize_lxc_step, | ||
434 | 72 | setup_launchpad_lxc_step, | ||
435 | 73 | stop_lxc_step, | ||
436 | 74 | ) | ||
437 | 75 | needs_root = True | ||
438 | 0 | 76 | ||
439 | === modified file 'lpsetup/tests/subcommands/test_version.py' | |||
440 | --- lpsetup/tests/subcommands/test_version.py 2012-05-22 09:36:57 +0000 | |||
441 | +++ lpsetup/tests/subcommands/test_version.py 2012-06-26 15:04:24 +0000 | |||
442 | @@ -17,7 +17,6 @@ | |||
443 | 17 | class VersionTest(SubCommandTestMixin, unittest.TestCase): | 17 | class VersionTest(SubCommandTestMixin, unittest.TestCase): |
444 | 18 | 18 | ||
445 | 19 | sub_command_class = version.SubCommand | 19 | sub_command_class = version.SubCommand |
446 | 20 | sub_command_name = 'subcmd' | ||
447 | 21 | 20 | ||
448 | 22 | def test_sub_command(self): | 21 | def test_sub_command(self): |
449 | 23 | with capture_output() as output: | 22 | with capture_output() as output: |
450 | 24 | 23 | ||
451 | === modified file 'lpsetup/tests/test_argparser.py' | |||
452 | --- lpsetup/tests/test_argparser.py 2012-05-22 09:36:57 +0000 | |||
453 | +++ lpsetup/tests/test_argparser.py 2012-06-26 15:04:24 +0000 | |||
454 | @@ -96,7 +96,7 @@ | |||
455 | 96 | 96 | ||
456 | 97 | def test_arguments(self): | 97 | def test_arguments(self): |
457 | 98 | # Ensure the sub command arguments are correctly handled. | 98 | # Ensure the sub command arguments are correctly handled. |
459 | 99 | namespace = self.parse_and_call_main('--foo eggs') | 99 | namespace = self.parse_and_call_main('--foo', 'eggs') |
460 | 100 | self.assertEqual('eggs', namespace.foo) | 100 | self.assertEqual('eggs', namespace.foo) |
461 | 101 | 101 | ||
462 | 102 | def test_successful_validation(self): | 102 | def test_successful_validation(self): |
463 | @@ -121,6 +121,12 @@ | |||
464 | 121 | self.assertIn(self.sub_command.name, help) | 121 | self.assertIn(self.sub_command.name, help) |
465 | 122 | self.assertIn(self.sub_command.help, help) | 122 | self.assertIn(self.sub_command.help, help) |
466 | 123 | 123 | ||
467 | 124 | def test_init_namespace(self): | ||
468 | 125 | # The namespace is initialized with current user info. | ||
469 | 126 | namespace = self.parse() | ||
470 | 127 | self.assertIsInstance(namespace.euid, int) | ||
471 | 128 | self.assertIsInstance(namespace.run_as_root, bool) | ||
472 | 129 | |||
473 | 124 | 130 | ||
474 | 125 | class StepsBasedSubCommandTest(SubCommandTestMixin, unittest.TestCase): | 131 | class StepsBasedSubCommandTest(SubCommandTestMixin, unittest.TestCase): |
475 | 126 | 132 | ||
476 | @@ -129,23 +135,23 @@ | |||
477 | 129 | def test_steps(self): | 135 | def test_steps(self): |
478 | 130 | # Ensure steps are executed in the order they are provided. | 136 | # Ensure steps are executed in the order they are provided. |
479 | 131 | with capture_output() as output: | 137 | with capture_output() as output: |
481 | 132 | self.parse_and_call_main('--foo eggs --bar spam') | 138 | self.parse_and_call_main('--foo', 'eggs', '--bar', 'spam') |
482 | 133 | self.check_output( | 139 | self.check_output( |
483 | 134 | ['step1 received eggs', 'step2 received eggs and spam'], | 140 | ['step1 received eggs', 'step2 received eggs and spam'], |
484 | 135 | output) | 141 | output) |
485 | 136 | 142 | ||
487 | 137 | def test_step_flag(self): | 143 | def test_steps_flag(self): |
488 | 138 | # A special argument `-s` or `--steps` is automatically added to the | 144 | # A special argument `-s` or `--steps` is automatically added to the |
489 | 139 | # parser. It can be used to execute only one or a subset of steps. | 145 | # parser. It can be used to execute only one or a subset of steps. |
490 | 140 | with capture_output() as output: | 146 | with capture_output() as output: |
492 | 141 | self.parse_and_call_main('--foo eggs -s step1') | 147 | self.parse_and_call_main('--foo', 'eggs', '-s', 'step1') |
493 | 142 | self.check_output(['step1 received eggs'], output) | 148 | self.check_output(['step1 received eggs'], output) |
494 | 143 | 149 | ||
496 | 144 | def test_skip_steps(self): | 150 | def test_skip_steps_flag(self): |
497 | 145 | # A special argument `--skip-steps` is automatically added to the | 151 | # A special argument `--skip-steps` is automatically added to the |
498 | 146 | # parser. It can be used to skip one or more steps. | 152 | # parser. It can be used to skip one or more steps. |
499 | 147 | with capture_output() as output: | 153 | with capture_output() as output: |
501 | 148 | self.parse_and_call_main('--foo eggs --skip-steps step1') | 154 | self.parse_and_call_main('--foo', 'eggs', '--skip-steps', 'step1') |
502 | 149 | self.check_output(['step2 received eggs and None'], output) | 155 | self.check_output(['step2 received eggs and None'], output) |
503 | 150 | 156 | ||
504 | 151 | def test_step_name(self): | 157 | def test_step_name(self): |
505 | @@ -164,7 +170,7 @@ | |||
506 | 164 | # Ensure the steps execution is stopped if a step raises | 170 | # Ensure the steps execution is stopped if a step raises |
507 | 165 | # `subprocess.CalledProcessError`. | 171 | # `subprocess.CalledProcessError`. |
508 | 166 | with capture_output() as output: | 172 | with capture_output() as output: |
510 | 167 | error = self.parse_and_call_main('--foo eggs') | 173 | error = self.parse_and_call_main('--foo', 'eggs') |
511 | 168 | self.assertEqual(1, error.returncode) | 174 | self.assertEqual(1, error.returncode) |
512 | 169 | self.check_output(['step1 received eggs'], output) | 175 | self.check_output(['step1 received eggs'], output) |
513 | 170 | 176 | ||
514 | @@ -177,7 +183,7 @@ | |||
515 | 177 | # The test runner calls a function named 'call_[step name]' if it is | 183 | # The test runner calls a function named 'call_[step name]' if it is |
516 | 178 | # defined. | 184 | # defined. |
517 | 179 | with capture_output() as output: | 185 | with capture_output() as output: |
519 | 180 | self.parse_and_call_main('--foo eggs --bar spam') | 186 | self.parse_and_call_main('--foo', 'eggs', '--bar', 'spam') |
520 | 181 | expected = [ | 187 | expected = [ |
521 | 182 | 'running step1 with eggs while bar is spam', | 188 | 'running step1 with eggs while bar is spam', |
522 | 183 | 'step1 received eggs', | 189 | 'step1 received eggs', |
523 | 184 | 190 | ||
524 | === modified file 'lpsetup/tests/utils.py' | |||
525 | --- lpsetup/tests/utils.py 2012-05-22 09:36:57 +0000 | |||
526 | +++ lpsetup/tests/utils.py 2012-06-26 15:04:24 +0000 | |||
527 | @@ -8,10 +8,15 @@ | |||
528 | 8 | __all__ = [ | 8 | __all__ = [ |
529 | 9 | 'capture_error', | 9 | 'capture_error', |
530 | 10 | 'capture_output', | 10 | 'capture_output', |
531 | 11 | 'get_random_string', | ||
532 | 12 | 'StepsBasedSubCommandTestMixin', | ||
533 | 13 | 'SubCommandTestMixin', | ||
534 | 11 | ] | 14 | ] |
535 | 12 | 15 | ||
536 | 13 | from contextlib import contextmanager | 16 | from contextlib import contextmanager |
537 | 14 | from functools import partial | 17 | from functools import partial |
538 | 18 | import random | ||
539 | 19 | import string | ||
540 | 15 | from StringIO import StringIO | 20 | from StringIO import StringIO |
541 | 16 | import sys | 21 | import sys |
542 | 17 | 22 | ||
543 | @@ -34,23 +39,79 @@ | |||
544 | 34 | capture_error = partial(capture, 'stderr') | 39 | capture_error = partial(capture, 'stderr') |
545 | 35 | 40 | ||
546 | 36 | 41 | ||
547 | 42 | def get_random_string(size=10): | ||
548 | 43 | """Return a random string to be used in tests.""" | ||
549 | 44 | return ''.join(random.sample(string.ascii_letters, size)) | ||
550 | 45 | |||
551 | 46 | |||
552 | 37 | class SubCommandTestMixin(object): | 47 | class SubCommandTestMixin(object): |
553 | 38 | 48 | ||
554 | 39 | sub_command_class = examples.SubCommand | 49 | sub_command_class = examples.SubCommand |
555 | 40 | sub_command_name = 'subcmd' | 50 | sub_command_name = 'subcmd' |
556 | 41 | 51 | ||
557 | 42 | def setUp(self): | 52 | def setUp(self): |
558 | 53 | """Set up an argument parser and instantiate *self.sub_command_class*. | ||
559 | 54 | |||
560 | 55 | The name used to create the sub command instance is | ||
561 | 56 | *self.sub_command_name*. | ||
562 | 57 | """ | ||
563 | 43 | self.parser = argparser.ArgumentParser() | 58 | self.parser = argparser.ArgumentParser() |
564 | 44 | self.sub_command = self.parser.register_subcommand( | 59 | self.sub_command = self.parser.register_subcommand( |
565 | 45 | self.sub_command_name, self.sub_command_class) | 60 | self.sub_command_name, self.sub_command_class) |
566 | 46 | 61 | ||
572 | 47 | def parse_and_call_main(self, arguments=None): | 62 | def parse(self, *args): |
573 | 48 | args = [self.sub_command_name] | 63 | """Parse given *args* and return an initialized namespace object.""" |
574 | 49 | if arguments is not None: | 64 | namespace = self.parser.parse_args((self.sub_command_name,) + args) |
575 | 50 | args.extend(arguments.split()) | 65 | sub_command = self.sub_command |
576 | 51 | namespace = self.parser.parse_args(args) | 66 | sub_command.init_namespace(namespace) |
577 | 67 | sub_command.validate(self.parser, namespace) | ||
578 | 68 | return namespace | ||
579 | 69 | |||
580 | 70 | def parse_and_call_main(self, *args): | ||
581 | 71 | """Create a namespace using the given *args* and invoke main.""" | ||
582 | 72 | namespace = self.parse(*args) | ||
583 | 52 | return namespace.main(namespace) | 73 | return namespace.main(namespace) |
584 | 53 | 74 | ||
585 | 54 | def check_output(self, expected, output): | 75 | def check_output(self, expected, output): |
586 | 55 | value = filter(None, output.getvalue().split('\n')) | 76 | value = filter(None, output.getvalue().split('\n')) |
587 | 56 | self.assertSequenceEqual(expected, value) | 77 | self.assertSequenceEqual(expected, value) |
588 | 78 | |||
589 | 79 | |||
590 | 80 | class StepsBasedSubCommandTestMixin(SubCommandTestMixin): | ||
591 | 81 | """This mixin can be used to test sub commands steps and handlers. | ||
592 | 82 | |||
593 | 83 | Real TestCases subclassing this mixin must define: | ||
594 | 84 | |||
595 | 85 | - expected_arguments: a sequence of command line arguments | ||
596 | 86 | used by the current tested sub command | ||
597 | 87 | - expected_handlers: a sequence of expected handler callables | ||
598 | 88 | - expected_steps: a sequence of expected *(step_callable, arg_names)* | ||
599 | 89 | - needs_root: True if this sub command must be run as root | ||
600 | 90 | |||
601 | 91 | At this point steps and handlers are automatically tested, and the test | ||
602 | 92 | case also checks if root is required by the sub command. | ||
603 | 93 | """ | ||
604 | 94 | def setUp(self): | ||
605 | 95 | """Set up a namespace using *self.expected_arguments*.""" | ||
606 | 96 | super(StepsBasedSubCommandTestMixin, self).setUp() | ||
607 | 97 | self.namespace = self.parse(*self.expected_arguments) | ||
608 | 98 | |||
609 | 99 | def test_handlers(self): | ||
610 | 100 | # Ensure this sub command uses the expected handlers. | ||
611 | 101 | handlers = self.sub_command.get_validators(self.namespace) | ||
612 | 102 | self.assertSequenceEqual(self.expected_handlers, handlers) | ||
613 | 103 | |||
614 | 104 | def test_steps(self): | ||
615 | 105 | # Ensure this sub command wants to run the expected steps. | ||
616 | 106 | steps = self.sub_command.get_steps(self.namespace) | ||
617 | 107 | real_steps = [[step, list(args)] for _, step, args in steps] | ||
618 | 108 | expected_steps = [] | ||
619 | 109 | for step, arg_names in self.expected_steps: | ||
620 | 110 | args = [getattr(self.namespace, name) for name in arg_names] | ||
621 | 111 | expected_steps.append([step, args]) | ||
622 | 112 | self.assertListEqual(expected_steps, real_steps) | ||
623 | 113 | |||
624 | 114 | def test_needs_root(self): | ||
625 | 115 | # The root user may or may not be required to run this sub command. | ||
626 | 116 | needs_root = self.sub_command.get_needs_root(self.namespace) | ||
627 | 117 | self.assertEqual(self.needs_root, needs_root) |
This branch is really good. These tests will help us quite a bit while we
implement the LEP. Here are my thoughts from reading through the diff:
The combination of the negative test for step_name ("not in steps_to_skip") and
the ternary operator in _include_step is really hard for me to understand.
Maybe this instead:
def _include_step(self, step_name, namespace):
"""Return True if the given *step_name* must be run, False otherwise.
A step is included in the command execution if the step included in skip_steps or []
`--steps` (or steps is None) and is not included in `--skip-steps`.
"""
steps_to_skip = namespace.
steps_to_run = namespace.steps
# If explicitly told to skip a step, then skip it.
if step_name in steps_to_skip:
return False
# If no list of steps was provided then any non-skipped are to be run.
if steps_to_run is None:
return True
# A list of steps to run was provided, so the step has to be included in
# order to be run.
return step_name in steps_to_run
I am not entirely sure the above is right, which bolsters my feeling that the
original was hard to understand.
Since the subcommand tests always have a get_arguments function which is called
and assigned to expected_arguments, it might be slightly better to just have a
required get_arguments method instead.
I don't feel strongly about it, but for the sake of diversity: here is a
version of get_random_string that is a bit shorter and more direct:
def get_random_ string( size=10) : random. sample( string. ascii_letters, size))
"""Return a random string to be used in tests."""
return ''.join(
I really like the explanation in the StepsBasedSubCo mmandTestMixin docstring of
how to use the class. I wonder if it would be good to leave the "must define"
attributes undefined so AttributeErrors are generated if a subclass leaves them
out.