Merge ~sylvain-pineau/checkbox-ng:drop-legacy-commands into checkbox-ng:master
- Git
- lp:~sylvain-pineau/checkbox-ng
- drop-legacy-commands
- Merge into master
Status: | Merged |
---|---|
Approved by: | Taihsiang Ho |
Approved revision: | 61decd188b368123be99f6998b09e9ab9b2810d0 |
Merged at revision: | 6442bbd13f4d4c3572913fd002b9c9a0bad7bb62 |
Proposed branch: | ~sylvain-pineau/checkbox-ng:drop-legacy-commands |
Merge into: | checkbox-ng:master |
Diff against target: |
2863 lines (+83/-428) 6 files modified
checkbox_ng/config.py (+0/-41) checkbox_ng/launcher/checkbox_cli.py (+5/-4) checkbox_ng/launcher/subcommands.py (+71/-4) dev/null (+0/-363) po/POTFILES.in (+7/-13) setup.py (+0/-3) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Taihsiang Ho | Approve | ||
Maciej Kisielewski | Approve | ||
Review via email:
|
Commit message
Description of the change
Drop legacy commands (including the checkbox sru) to only keep checkbox-cli as the unique entry point.
The two missing commands (check-config and submit) have been transfered to checkbox-cli subcommands.
With the removal of checkbox sru, we can also say goodbye to the old textland bits and drop all sru specific options from the config module.
- checkbox-cli as a launcher tested with 16.04 certification test plan.
- check-config ok (properly see ~/.config/
- submit ok (tested both xml and tar.xz with both prod and staging endpoints - only staging for the submission service).
PLEASE DO NOT MERGE UNTIL SRU TOOLS ARE UPDATED IN THE LAB. (Contact: Tai)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Taihsiang Ho (tai271828) wrote : | # |
SRU tools is ready to merge. Please refer to:
https:/
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Taihsiang Ho (tai271828) wrote : | # |
It should be this https:/
Preview Diff
1 | diff --git a/checkbox_ng/commands/__init__.py b/checkbox_ng/commands/__init__.py |
2 | deleted file mode 100644 |
3 | index a7ea4d3..0000000 |
4 | --- a/checkbox_ng/commands/__init__.py |
5 | +++ /dev/null |
6 | @@ -1,63 +0,0 @@ |
7 | -# This file is part of Checkbox. |
8 | -# |
9 | -# Copyright 2014 Canonical Ltd. |
10 | -# Written by: |
11 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
12 | -# |
13 | -# Checkbox is free software: you can redistribute it and/or modify |
14 | -# it under the terms of the GNU General Public License version 3, |
15 | -# as published by the Free Software Foundation. |
16 | -# |
17 | -# Checkbox is distributed in the hope that it will be useful, |
18 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | -# GNU General Public License for more details. |
21 | -# |
22 | -# You should have received a copy of the GNU General Public License |
23 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
24 | - |
25 | -""" |
26 | -:mod:`checkbox_ng.commands` -- shared code for checkbox-ng sub-commands |
27 | -======================================================================= |
28 | -""" |
29 | - |
30 | -from plainbox.impl.clitools import CommandBase |
31 | - |
32 | - |
33 | -class CheckboxCommand(CommandBase): |
34 | - """ |
35 | - Simple interface class for checkbox-ng commands. |
36 | - |
37 | - Command objects like this are consumed by CheckBoxNGTool subclasses to |
38 | - implement hierarchical command system. The API supports arbitrary many sub |
39 | - commands in arbitrary nesting arrangement. |
40 | - """ |
41 | - |
42 | - gettext_domain = "checkbox-ng" |
43 | - |
44 | - def __init__(self, provider_loader, config_loader): |
45 | - """ |
46 | - Initialize a command with the specified arguments. |
47 | - |
48 | - :param provider_loader: |
49 | - A callable returning a list of Provider1 objects |
50 | - :param config_loader: |
51 | - A callable returning a Config object |
52 | - """ |
53 | - self._provider_loader = provider_loader |
54 | - self._config_loader = config_loader |
55 | - |
56 | - @property |
57 | - def provider_loader(self): |
58 | - """ |
59 | - a callable returning a list of PlainBox providers associated with this |
60 | - command |
61 | - """ |
62 | - return self._provider_loader |
63 | - |
64 | - @property |
65 | - def config_loader(self): |
66 | - """ |
67 | - a callable returning a Config object |
68 | - """ |
69 | - return self._config_loader |
70 | diff --git a/checkbox_ng/commands/cli.py b/checkbox_ng/commands/cli.py |
71 | deleted file mode 100644 |
72 | index e7d6c45..0000000 |
73 | --- a/checkbox_ng/commands/cli.py |
74 | +++ /dev/null |
75 | @@ -1,82 +0,0 @@ |
76 | -# This file is part of Checkbox. |
77 | -# |
78 | -# Copyright 2013-2014 Canonical Ltd. |
79 | -# Written by: |
80 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
81 | -# |
82 | -# Checkbox is free software: you can redistribute it and/or modify |
83 | -# it under the terms of the GNU General Public License version 3, |
84 | -# as published by the Free Software Foundation. |
85 | -# |
86 | -# Checkbox is distributed in the hope that it will be useful, |
87 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
88 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
89 | -# GNU General Public License for more details. |
90 | -# |
91 | -# You should have received a copy of the GNU General Public License |
92 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
93 | - |
94 | -""" |
95 | -:mod:`checkbox_ng.commands.cli` -- Command line sub-command |
96 | -=========================================================== |
97 | - |
98 | -.. warning:: |
99 | - |
100 | - THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
101 | -""" |
102 | - |
103 | -from argparse import SUPPRESS |
104 | -from gettext import gettext as _ |
105 | -from logging import getLogger |
106 | - |
107 | -from plainbox.impl.commands import PlainBoxCommand |
108 | -from plainbox.impl.commands.cmd_checkbox import CheckBoxCommandMixIn |
109 | -from plainbox.impl.commands.inv_check_config import CheckConfigInvocation |
110 | - |
111 | -from checkbox_ng.commands.newcli import CliInvocation2 |
112 | - |
113 | - |
114 | -logger = getLogger("checkbox.ng.commands.cli") |
115 | - |
116 | - |
117 | -class CliCommand(PlainBoxCommand, CheckBoxCommandMixIn): |
118 | - """ |
119 | - Command for running tests using the command line UI. |
120 | - """ |
121 | - gettext_domain = "checkbox-ng" |
122 | - |
123 | - def __init__(self, provider_loader, config_loader, settings): |
124 | - self.provider_loader = provider_loader |
125 | - self.config_loader = config_loader |
126 | - self.settings = settings |
127 | - |
128 | - def invoked(self, ns): |
129 | - # Run check-config, if requested |
130 | - if ns.check_config: |
131 | - retval = CheckConfigInvocation(self.config_loader).run() |
132 | - return retval |
133 | - return CliInvocation2( |
134 | - self.provider_loader, self.loader_config, ns, self.settings |
135 | - ).run() |
136 | - |
137 | - def register_parser(self, subparsers): |
138 | - parser = subparsers.add_parser(self.settings['subparser_name'], |
139 | - help=self.settings['subparser_help']) |
140 | - parser.set_defaults(command=self) |
141 | - parser.set_defaults(dry_run=False) |
142 | - parser.add_argument( |
143 | - "--check-config", |
144 | - action="store_true", |
145 | - help=_("run check-config")) |
146 | - group = parser.add_argument_group(title=_("user interface options")) |
147 | - parser.set_defaults(color=None) |
148 | - group.add_argument( |
149 | - '--no-color', dest='color', action='store_false', help=SUPPRESS) |
150 | - group.add_argument( |
151 | - '--non-interactive', action='store_true', |
152 | - help=_("skip tests that require interactivity")) |
153 | - group.add_argument( |
154 | - '--dont-suppress-output', action="store_true", default=False, |
155 | - help=_("don't suppress the output of certain job plugin types")) |
156 | - # Call enhance_parser from CheckBoxCommandMixIn |
157 | - self.enhance_parser(parser) |
158 | diff --git a/checkbox_ng/commands/launcher.py b/checkbox_ng/commands/launcher.py |
159 | deleted file mode 100644 |
160 | index 87eb180..0000000 |
161 | --- a/checkbox_ng/commands/launcher.py |
162 | +++ /dev/null |
163 | @@ -1,115 +0,0 @@ |
164 | -# This file is part of Checkbox. |
165 | -# |
166 | -# Copyright 2014 Canonical Ltd. |
167 | -# Written by: |
168 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
169 | -# |
170 | -# Checkbox is free software: you can redistribute it and/or modify |
171 | -# it under the terms of the GNU General Public License version 3, |
172 | -# as published by the Free Software Foundation. |
173 | - |
174 | -# |
175 | -# Checkbox is distributed in the hope that it will be useful, |
176 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
177 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
178 | -# GNU General Public License for more details. |
179 | -# |
180 | -# You should have received a copy of the GNU General Public License |
181 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
182 | - |
183 | -""" |
184 | -:mod:`checkbox_ng.commands.launcher` -- `checkbox launcher` command |
185 | -=================================================================== |
186 | -""" |
187 | - |
188 | -from argparse import SUPPRESS |
189 | -from gettext import gettext as _ |
190 | -import itertools |
191 | -import logging |
192 | -import os |
193 | - |
194 | -from checkbox_ng.commands import CheckboxCommand |
195 | -from checkbox_ng.commands.newcli import CliInvocation2 |
196 | -from checkbox_ng.commands.submit import SubmitCommand |
197 | -from checkbox_ng.config import CheckBoxConfig |
198 | - |
199 | -from plainbox.impl.commands.cmd_checkbox import CheckBoxCommandMixIn |
200 | -from plainbox.impl.launcher import LauncherDefinition |
201 | - |
202 | -logger = logging.getLogger("checkbox.ng.commands.launcher") |
203 | - |
204 | - |
205 | -class LauncherCommand(CheckboxCommand, CheckBoxCommandMixIn, SubmitCommand): |
206 | - """ |
207 | - run a customized testing session |
208 | - |
209 | - This command can be used as an interpreter for the so-called launchers. |
210 | - Those launchers are small text files that define the parameters of the test |
211 | - and can be executed directly to run a customized checkbox-ng testing |
212 | - session. |
213 | - """ |
214 | - |
215 | - def __init__(self, provider_loader, config_loader): |
216 | - self._provider_loader = provider_loader |
217 | - self.config = config_loader() |
218 | - |
219 | - def invoked(self, ns): |
220 | - try: |
221 | - with open(ns.launcher, 'rt', encoding='UTF-8') as stream: |
222 | - first_line = stream.readline() |
223 | - if not first_line.startswith("#!"): |
224 | - stream.seek(0) |
225 | - text = stream.read() |
226 | - except IOError as exc: |
227 | - logger.error(_("Unable to load launcher definition: %s"), exc) |
228 | - return 1 |
229 | - generic_launcher = LauncherDefinition() |
230 | - generic_launcher.read_string(text) |
231 | - launcher = generic_launcher.get_concrete_launcher() |
232 | - launcher.read_string(text) |
233 | - if launcher.problem_list: |
234 | - logger.error(_("Unable to start launcher because of errors:")) |
235 | - for problem in launcher.problem_list: |
236 | - logger.error("%s", str(problem)) |
237 | - return 1 |
238 | - # Override the default CheckBox configuration with the one provided |
239 | - # by the launcher |
240 | - self.config.Meta.filename_list = list( |
241 | - itertools.chain( |
242 | - *zip( |
243 | - itertools.islice( |
244 | - CheckBoxConfig.Meta.filename_list, 0, None, 2), |
245 | - itertools.islice( |
246 | - CheckBoxConfig.Meta.filename_list, 1, None, 2), |
247 | - ('/etc/xdg/{}'.format(launcher.config_filename), |
248 | - os.path.expanduser( |
249 | - '~/.config/{}'.format(launcher.config_filename))))) |
250 | - ) |
251 | - self.config.read(self.config.Meta.filename_list) |
252 | - ns.dry_run = False |
253 | - ns.dont_suppress_output = launcher.dont_suppress_output |
254 | - return CliInvocation2( |
255 | - self.provider_loader, lambda: self.config, ns, launcher |
256 | - ).run() |
257 | - |
258 | - def register_parser(self, subparsers): |
259 | - parser = self.add_subcommand(subparsers) |
260 | - self.register_arguments(parser) |
261 | - |
262 | - def register_arguments(self, parser): |
263 | - parser.add_argument( |
264 | - '--no-color', dest='color', action='store_false', help=SUPPRESS) |
265 | - parser.set_defaults(color=None) |
266 | - parser.add_argument( |
267 | - "launcher", metavar=_("LAUNCHER"), |
268 | - help=_("launcher definition file to use")) |
269 | - parser.set_defaults(command=self) |
270 | - parser.conflict_handler = 'resolve' |
271 | - # Call enhance_parser from CheckBoxCommandMixIn |
272 | - self.enhance_parser(parser) |
273 | - group = parser.add_argument_group(title=_("user interface options")) |
274 | - group.add_argument( |
275 | - '--non-interactive', action='store_true', |
276 | - help=_("skip tests that require interactivity")) |
277 | - # Call register_optional_arguments from SubmitCommand |
278 | - self.register_optional_arguments(parser) |
279 | diff --git a/checkbox_ng/commands/newcli.py b/checkbox_ng/commands/newcli.py |
280 | deleted file mode 100644 |
281 | index 8d359c0..0000000 |
282 | --- a/checkbox_ng/commands/newcli.py |
283 | +++ /dev/null |
284 | @@ -1,491 +0,0 @@ |
285 | -# This file is part of Checkbox. |
286 | -# |
287 | -# Copyright 2013-2014 Canonical Ltd. |
288 | -# Written by: |
289 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
290 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
291 | -# |
292 | -# Checkbox is free software: you can redistribute it and/or modify |
293 | -# it under the terms of the GNU General Public License version 3, |
294 | -# as published by the Free Software Foundation. |
295 | -# |
296 | -# Checkbox is distributed in the hope that it will be useful, |
297 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
298 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
299 | -# GNU General Public License for more details. |
300 | -# |
301 | -# You should have received a copy of the GNU General Public License |
302 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
303 | - |
304 | -""" |
305 | -:mod:`checkbox_ng.commands.cli` -- Command line sub-command |
306 | -=========================================================== |
307 | - |
308 | -.. warning:: |
309 | - |
310 | - THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
311 | -""" |
312 | - |
313 | -from gettext import gettext as _ |
314 | -from logging import getLogger |
315 | -from shutil import copyfileobj |
316 | -import io |
317 | -import operator |
318 | -import os |
319 | -import re |
320 | -import subprocess |
321 | -import sys |
322 | - |
323 | -from plainbox.abc import IJobResult |
324 | -from plainbox.impl.commands.inv_run import RunInvocation |
325 | -from plainbox.impl.exporter import ByteStringStreamTranslator |
326 | -from plainbox.impl.secure.config import Unset, ValidationError |
327 | -from plainbox.impl.secure.origin import CommandLineTextSource |
328 | -from plainbox.impl.secure.origin import Origin |
329 | -from plainbox.impl.secure.qualifiers import FieldQualifier |
330 | -from plainbox.impl.secure.qualifiers import OperatorMatcher |
331 | -from plainbox.impl.secure.qualifiers import RegExpJobQualifier |
332 | -from plainbox.impl.secure.qualifiers import select_jobs |
333 | -from plainbox.impl.session import SessionMetaData |
334 | -from plainbox.impl.session.jobs import InhibitionCause |
335 | -from plainbox.impl.transport import TransportError |
336 | -from plainbox.impl.transport import get_all_transports |
337 | -from plainbox.vendor.textland import get_display |
338 | - |
339 | -from checkbox_ng.misc import SelectableJobTreeNode |
340 | -from checkbox_ng.ui import ScrollableTreeNode |
341 | -from checkbox_ng.ui import ShowMenu |
342 | -from checkbox_ng.ui import ShowRerun |
343 | -from checkbox_ng.ui import ShowWelcome |
344 | - |
345 | - |
346 | -logger = getLogger("checkbox.ng.commands.newcli") |
347 | - |
348 | - |
349 | -class CliInvocation2(RunInvocation): |
350 | - """ |
351 | - Invocation of the 'checkbox cli' command. |
352 | - |
353 | - :ivar ns: |
354 | - The argparse namespace obtained from CliCommand |
355 | - :ivar _launcher: |
356 | - launcher specific to 'checkbox cli' |
357 | - :ivar _display: |
358 | - A textland display object |
359 | - :ivar _qualifier_list: |
360 | - A list of job qualifiers used to build the session desired_job_list |
361 | - """ |
362 | - |
363 | - def __init__(self, provider_loader, config_loader, ns, launcher, |
364 | - display=None): |
365 | - super().__init__(provider_loader, config_loader, ns, ns.color) |
366 | - if display is None: |
367 | - display = get_display() |
368 | - self._launcher = launcher |
369 | - self._display = display |
370 | - self._qualifier_list = [] |
371 | - self._testplan_list = [] |
372 | - self.select_qualifier_list() |
373 | - # MAAS-deployed server images need "tput reset" to keep ugliness |
374 | - # from happening.... |
375 | - subprocess.check_call(['tput', 'reset']) |
376 | - |
377 | - @property |
378 | - def launcher(self): |
379 | - """ |
380 | - TBD: 'checkbox cli' specific launcher settings |
381 | - """ |
382 | - return self._launcher |
383 | - |
384 | - @property |
385 | - def display(self): |
386 | - """ |
387 | - A TextLand display object |
388 | - """ |
389 | - return self._display |
390 | - |
391 | - def select_qualifier_list(self): |
392 | - # Add whitelists |
393 | - if 'whitelist' in self.ns and self.ns.whitelist: |
394 | - for whitelist_file in self.ns.whitelist: |
395 | - qualifier = self.get_whitelist_from_file( |
396 | - whitelist_file.name, whitelist_file) |
397 | - if qualifier is not None: |
398 | - self._qualifier_list.append(qualifier) |
399 | - # Add all the --include jobs |
400 | - for pattern in self.ns.include_pattern_list: |
401 | - origin = Origin(CommandLineTextSource('-i', pattern), None, None) |
402 | - try: |
403 | - qualifier = RegExpJobQualifier( |
404 | - '^{}$'.format(pattern), origin, inclusive=True) |
405 | - except Exception as exc: |
406 | - logger.warning( |
407 | - _("Incorrect pattern %r: %s"), pattern, exc) |
408 | - else: |
409 | - self._qualifier_list.append(qualifier) |
410 | - # Add all the --exclude jobs |
411 | - for pattern in self.ns.exclude_pattern_list: |
412 | - origin = Origin(CommandLineTextSource('-x', pattern), None, None) |
413 | - try: |
414 | - qualifier = RegExpJobQualifier( |
415 | - '^{}$'.format(pattern), origin, inclusive=False) |
416 | - except Exception as exc: |
417 | - logger.warning( |
418 | - _("Incorrect pattern %r: %s"), pattern, exc) |
419 | - else: |
420 | - self._qualifier_list.append(qualifier) |
421 | - if self.config.whitelist is not Unset: |
422 | - self._qualifier_list.append( |
423 | - self.get_whitelist_from_file(self.config.whitelist)) |
424 | - |
425 | - def select_testplan(self): |
426 | - # Add the test plan |
427 | - if self.ns.test_plan is not None: |
428 | - for provider in self.provider_list: |
429 | - for unit in provider.id_map[self.ns.test_plan]: |
430 | - if unit.Meta.name == 'test plan': |
431 | - self._qualifier_list.append(unit.get_qualifier()) |
432 | - self._testplan_list.append(unit) |
433 | - return |
434 | - else: |
435 | - logger.error(_("There is no test plan: %s"), self.ns.test_plan) |
436 | - |
437 | - def run(self): |
438 | - return self.do_normal_sequence() |
439 | - |
440 | - def do_normal_sequence(self): |
441 | - """ |
442 | - Proceed through normal set of steps that are required to runs jobs |
443 | - |
444 | - .. note:: |
445 | - This version is overridden as there is no better way to manage this |
446 | - pile rather than having a copy-paste + edits piece of text until |
447 | - arrowhead replaced plainbox run internals with a flow chart that |
448 | - can be derived meaningfully. |
449 | - |
450 | - For now just look for changes as compared to run.py's version. |
451 | - """ |
452 | - # Create transport early so that we can handle bugs before starting the |
453 | - # session. |
454 | - self.create_transport() |
455 | - if self.is_interactive: |
456 | - resumed = self.maybe_resume_session() |
457 | - else: |
458 | - self.create_manager(None) |
459 | - resumed = False |
460 | - # XXX: we don't want to know about new jobs just yet |
461 | - self.state.on_job_added.disconnect(self.on_job_added) |
462 | - # Create the job runner so that we can do stuff |
463 | - self.create_runner() |
464 | - # If we haven't resumed then do some one-time initialization |
465 | - if not resumed: |
466 | - # Show the welcome message |
467 | - self.show_welcome_screen() |
468 | - # Process testplan command line options |
469 | - self.select_testplan() |
470 | - # Maybe allow the user to do a manual whitelist selection |
471 | - if not self._qualifier_list: |
472 | - self.maybe_interactively_select_testplans() |
473 | - if self._testplan_list: |
474 | - self.manager.test_plans = tuple(self._testplan_list) |
475 | - # Store the application-identifying meta-data and checkpoint the |
476 | - # session. |
477 | - self.store_application_metadata() |
478 | - self.metadata.flags.add(SessionMetaData.FLAG_INCOMPLETE) |
479 | - self.manager.checkpoint() |
480 | - # Run all the local jobs. We need to do this to see all the things |
481 | - # the user may select |
482 | - if self.is_interactive: |
483 | - self.select_local_jobs() |
484 | - self.run_all_selected_jobs() |
485 | - self.interactively_pick_jobs_to_run() |
486 | - # Maybe ask the secure launcher to prompt for the password now. This is |
487 | - # imperfect as we are going to run local jobs and we cannot see if they |
488 | - # might need root or not. This cannot be fixed before template jobs are |
489 | - # added and local jobs deprecated and removed (at least not being a |
490 | - # part of the session we want to execute). |
491 | - self.maybe_warm_up_authentication() |
492 | - self.print_estimated_duration() |
493 | - self.run_all_selected_jobs() |
494 | - if self.is_interactive: |
495 | - while True: |
496 | - if self.maybe_rerun_jobs(): |
497 | - continue |
498 | - else: |
499 | - break |
500 | - self.export_and_send_results() |
501 | - if SessionMetaData.FLAG_INCOMPLETE in self.metadata.flags: |
502 | - print(self.C.header("Session Complete!", "GREEN")) |
503 | - self.metadata.flags.remove(SessionMetaData.FLAG_INCOMPLETE) |
504 | - self.manager.checkpoint() |
505 | - return 0 |
506 | - |
507 | - def store_application_metadata(self): |
508 | - super().store_application_metadata() |
509 | - self.metadata.app_blob = b'' |
510 | - |
511 | - def show_welcome_screen(self): |
512 | - text = self.launcher.text |
513 | - if self.is_interactive and text: |
514 | - self.display.run(ShowWelcome(text)) |
515 | - |
516 | - def maybe_interactively_select_testplans(self): |
517 | - if self.launcher.skip_whitelist_selection: |
518 | - self._qualifier_list.extend(self.get_default_testplans()) |
519 | - elif self.is_interactive: |
520 | - self._qualifier_list.extend( |
521 | - self.get_interactively_picked_testplans()) |
522 | - elif self.launcher.whitelist_selection: |
523 | - self._qualifier_list.extend(self.get_default_testplans()) |
524 | - logger.info(_("Selected testplans: %r"), self._qualifier_list) |
525 | - |
526 | - def get_interactively_picked_testplans(self): |
527 | - """ |
528 | - Show an interactive dialog that allows the user to pick a list of |
529 | - testplans. The set of testplans is limited to those offered by the |
530 | - 'default_providers' setting. |
531 | - |
532 | - :returns: |
533 | - A list of selected testplans |
534 | - """ |
535 | - testplans = [] |
536 | - testplan_selection = [] |
537 | - for provider in self.provider_list: |
538 | - testplans.extend( |
539 | - [unit for unit in provider.unit_list if |
540 | - unit.Meta.name == 'test plan' and |
541 | - re.search(self.launcher.whitelist_filter, unit.partial_id)]) |
542 | - testplan_name_list = [testplan.tr_name() for testplan in testplans] |
543 | - testplan_selection = [ |
544 | - testplans.index(t) for t in testplans if |
545 | - re.search(self.launcher.whitelist_selection, t.partial_id)] |
546 | - selected_list = self.display.run( |
547 | - ShowMenu(_("Suite selection"), testplan_name_list, |
548 | - testplan_selection)) |
549 | - if not selected_list: |
550 | - raise SystemExit(_("No testplan selected, aborting")) |
551 | - self._testplan_list.extend( |
552 | - [testplans[selected_index] for selected_index in selected_list]) |
553 | - return [testplans[selected_index].get_qualifier() for selected_index |
554 | - in selected_list] |
555 | - |
556 | - def get_default_testplans(self): |
557 | - testplans = [] |
558 | - for provider in self.provider_list: |
559 | - testplans.extend([ |
560 | - unit.get_qualifier() for unit in provider.unit_list if |
561 | - unit.Meta.name == 'test plan' and re.search( |
562 | - self.launcher.whitelist_selection, unit.partial_id)]) |
563 | - return testplans |
564 | - |
565 | - def create_transport(self): |
566 | - """ |
567 | - Create the ISessionStateTransport based on the command line options |
568 | - |
569 | - This sets the :ivar:`_transport`. |
570 | - """ |
571 | - # TODO: |
572 | - self._transport = None |
573 | - |
574 | - @property |
575 | - def expected_app_id(self): |
576 | - return 'checkbox' |
577 | - |
578 | - def select_local_jobs(self): |
579 | - print(self.C.header(_("Selecting Job Generators"))) |
580 | - # Create a qualifier list that will pick all local jobs out of the |
581 | - # subset of jobs also enumerated by the whitelists we've already |
582 | - # picked. |
583 | - # |
584 | - # Since each whitelist is a qualifier that selects jobs enumerated |
585 | - # within, we only need to and an exclusive qualifier that deselects |
586 | - # non-local jobs and we're done. |
587 | - qualifier_list = [] |
588 | - qualifier_list.extend(self._qualifier_list) |
589 | - origin = Origin.get_caller_origin() |
590 | - qualifier_list.append(FieldQualifier( |
591 | - 'plugin', OperatorMatcher(operator.ne, 'local'), origin, |
592 | - inclusive=False)) |
593 | - local_job_list = select_jobs( |
594 | - self.manager.state.job_list, qualifier_list) |
595 | - self._update_desired_job_list(local_job_list) |
596 | - |
597 | - def interactively_pick_jobs_to_run(self): |
598 | - print(self.C.header(_("Selecting Jobs For Execution"))) |
599 | - self._update_desired_job_list(select_jobs( |
600 | - self.manager.state.job_list, self._qualifier_list)) |
601 | - if self.launcher.skip_test_selection or not self.is_interactive: |
602 | - return |
603 | - tree = SelectableJobTreeNode.create_tree( |
604 | - self.manager.state, self.manager.state.run_list) |
605 | - title = _('Choose tests to run on your system:') |
606 | - self.display.run(ScrollableTreeNode(tree, title)) |
607 | - # NOTE: tree.selection is correct but ordered badly. To retain |
608 | - # the original ordering we should just treat it as a mask and |
609 | - # use it to filter jobs from desired_job_list. |
610 | - wanted_set = frozenset(tree.selection + tree.resource_jobs) |
611 | - job_list = [job for job in self.manager.state.run_list |
612 | - if job in wanted_set] |
613 | - self._update_desired_job_list(job_list) |
614 | - |
615 | - def export_and_send_results(self): |
616 | - if self.is_interactive: |
617 | - print(self.C.header(_("Results"))) |
618 | - exporter = self.manager.create_exporter( |
619 | - '2013.com.canonical.plainbox::text') |
620 | - exported_stream = io.BytesIO() |
621 | - exporter.dump_from_session_manager(self.manager, exported_stream) |
622 | - exported_stream.seek(0) # Need to rewind the file, puagh |
623 | - # This requires a bit more finesse, as exporters output bytes |
624 | - # and stdout needs a string. |
625 | - translating_stream = ByteStringStreamTranslator( |
626 | - sys.stdout, "utf-8") |
627 | - copyfileobj(exported_stream, translating_stream) |
628 | - # FIXME: this should probably not go to plainbox but checkbox-ng |
629 | - base_dir = os.path.join( |
630 | - os.getenv( |
631 | - 'XDG_DATA_HOME', os.path.expanduser("~/.local/share/")), |
632 | - "plainbox") |
633 | - if not os.path.exists(base_dir): |
634 | - os.makedirs(base_dir) |
635 | - exp_options = ['with-sys-info', 'with-summary', 'with-job-description', |
636 | - 'with-text-attachments', 'with-certification-status', |
637 | - 'with-job-defs', 'with-io-log', 'with-comments'] |
638 | - print() |
639 | - if self.launcher.exporter is not Unset: |
640 | - exporters = self.launcher.exporter |
641 | - else: |
642 | - exporters = [ |
643 | - '2013.com.canonical.plainbox::hexr', |
644 | - '2013.com.canonical.plainbox::html', |
645 | - '2013.com.canonical.plainbox::xlsx', |
646 | - '2013.com.canonical.plainbox::json', |
647 | - ] |
648 | - for unit_name in exporters: |
649 | - exporter = self.manager.create_exporter( |
650 | - unit_name, exp_options, strict=False) |
651 | - extension = exporter.unit.file_extension |
652 | - results_path = os.path.join( |
653 | - base_dir, 'submission.{}'.format(extension)) |
654 | - with open(results_path, "wb") as stream: |
655 | - exporter.dump_from_session_manager(self.manager, stream) |
656 | - print(_("View results") + " ({}): file://{}".format( |
657 | - extension, results_path)) |
658 | - self.submission_file = os.path.join(base_dir, 'submission.xml') |
659 | - if self.launcher.submit_to is not Unset: |
660 | - if self.launcher.submit_to == 'certification': |
661 | - # If we supplied a submit_url in the launcher, it |
662 | - # should override the one in the config. |
663 | - if self.launcher.submit_url: |
664 | - self.config.c3_url = self.launcher.submit_url |
665 | - # Same behavior for submit_to_hexr (a boolean flag which |
666 | - # should result in adding "submit_to_hexr=1" to transport |
667 | - # options later on) |
668 | - if self.launcher.submit_to_hexr: |
669 | - self.config.submit_to_hexr = True |
670 | - # for secure_id, config (which is user-writable) should |
671 | - # override launcher (which is not) |
672 | - if not self.config.secure_id: |
673 | - self.config.secure_id = self.launcher.secure_id |
674 | - # Override the secure_id configuration with the one provided |
675 | - # by the command-line option |
676 | - if self.ns.secure_id: |
677 | - self.config.secure_id = self.ns.secure_id |
678 | - if self.config.secure_id is Unset: |
679 | - again = True |
680 | - if not self.is_interactive: |
681 | - again = False |
682 | - while again: |
683 | - # TRANSLATORS: Do not translate the {} format marker. |
684 | - if self.ask_for_confirmation( |
685 | - _("\nSubmit results to {0}?".format( |
686 | - self.launcher.submit_url))): |
687 | - try: |
688 | - self.config.secure_id = input(_("Secure ID: ")) |
689 | - except ValidationError: |
690 | - print( |
691 | - _("ERROR: Secure ID must be 15-character " |
692 | - "(or more) alphanumeric string")) |
693 | - else: |
694 | - again = False |
695 | - self.submit_certification_results() |
696 | - else: |
697 | - again = False |
698 | - else: |
699 | - # Automatically try to submit results if the secure_id is |
700 | - # valid |
701 | - self.submit_certification_results() |
702 | - |
703 | - def submit_certification_results(self): |
704 | - from checkbox_ng.certification import InvalidSecureIDError |
705 | - transport_cls = get_all_transports().get('certification') |
706 | - # TRANSLATORS: Do not translate the {} format markers. |
707 | - print(_("Submitting results to {0} for secure_id {1}").format( |
708 | - self.config.c3_url, self.config.secure_id)) |
709 | - option_chunks = [] |
710 | - option_chunks.append("secure_id={0}".format(self.config.secure_id)) |
711 | - if self.config.submit_to_hexr: |
712 | - option_chunks.append("submit_to_hexr=1") |
713 | - # Assemble the option string |
714 | - options_string = ",".join(option_chunks) |
715 | - # Create the transport object |
716 | - try: |
717 | - transport = transport_cls( |
718 | - self.config.c3_url, options_string) |
719 | - except InvalidSecureIDError as exc: |
720 | - print(exc) |
721 | - return False |
722 | - with open(self.submission_file, "r", encoding='utf-8') as stream: |
723 | - try: |
724 | - # Send the data, reading from the fallback file |
725 | - result = transport.send(stream, self.config) |
726 | - if 'url' in result: |
727 | - # TRANSLATORS: Do not translate the {} format marker. |
728 | - print(_("Successfully sent, submission status" |
729 | - " at {0}").format(result['url'])) |
730 | - else: |
731 | - # TRANSLATORS: Do not translate the {} format marker. |
732 | - print(_("Successfully sent, server response" |
733 | - ": {0}").format(result)) |
734 | - except TransportError as exc: |
735 | - print(str(exc)) |
736 | - |
737 | - def maybe_rerun_jobs(self): |
738 | - # create a list of jobs that qualify for rerunning |
739 | - rerun_candidates = [] |
740 | - for job in self.manager.state.run_list: |
741 | - job_state = self.manager.state.job_state_map[job.id] |
742 | - if job_state.result.outcome in ( |
743 | - IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH, |
744 | - IJobResult.OUTCOME_NOT_SUPPORTED): |
745 | - rerun_candidates.append(job) |
746 | - |
747 | - # bail-out early if no job qualifies for rerunning |
748 | - if not rerun_candidates: |
749 | - return False |
750 | - tree = SelectableJobTreeNode.create_tree( |
751 | - self.manager.state, rerun_candidates) |
752 | - # nothing to select in root node and categories - bailing out |
753 | - if not tree.jobs and not tree._categories: |
754 | - return False |
755 | - # deselect all by default |
756 | - tree.set_descendants_state(False) |
757 | - self.display.run(ShowRerun(tree, _("Select jobs to re-run"))) |
758 | - wanted_set = frozenset(tree.selection) |
759 | - if not wanted_set: |
760 | - # nothing selected - nothing to run |
761 | - return False |
762 | - # include resource jobs that selected jobs depend on |
763 | - resources_to_rerun = [] |
764 | - for job in wanted_set: |
765 | - job_state = self.manager.state.job_state_map[job.id] |
766 | - for inhibitor in job_state.readiness_inhibitor_list: |
767 | - if inhibitor.cause == InhibitionCause.FAILED_DEP: |
768 | - resources_to_rerun.append(inhibitor.related_job) |
769 | - # reset outcome of jobs that are selected for re-running |
770 | - for job in list(wanted_set) + resources_to_rerun: |
771 | - from plainbox.impl.result import MemoryJobResult |
772 | - self.manager.state.job_state_map[job.id].result = \ |
773 | - MemoryJobResult({}) |
774 | - self.run_all_selected_jobs() |
775 | - return True |
776 | diff --git a/checkbox_ng/commands/sru.py b/checkbox_ng/commands/sru.py |
777 | deleted file mode 100644 |
778 | index 7db7b71..0000000 |
779 | --- a/checkbox_ng/commands/sru.py |
780 | +++ /dev/null |
781 | @@ -1,174 +0,0 @@ |
782 | -# This file is part of Checkbox. |
783 | -# |
784 | -# |
785 | -# Copyright 2013 Canonical Ltd. |
786 | -# Written by: |
787 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
788 | -# |
789 | -# Checkbox is free software: you can redistribute it and/or modify |
790 | -# it under the terms of the GNU General Public License version 3, |
791 | -# as published by the Free Software Foundation. |
792 | - |
793 | -# |
794 | -# Checkbox is distributed in the hope that it will be useful, |
795 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
796 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
797 | -# GNU General Public License for more details. |
798 | -# |
799 | -# You should have received a copy of the GNU General Public License |
800 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
801 | - |
802 | -""" |
803 | -:mod:`checkbox_ng.commands.sru` -- sru sub-command |
804 | -================================================== |
805 | - |
806 | -.. warning:: |
807 | - |
808 | - THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
809 | -""" |
810 | -import sys |
811 | - |
812 | -from gettext import gettext as _ |
813 | -from plainbox.impl.commands import PlainBoxCommand |
814 | -from plainbox.impl.commands.inv_check_config import CheckConfigInvocation |
815 | -from plainbox.impl.ingredients import CanonicalCommand |
816 | -from plainbox.impl.secure.config import ValidationError, Unset |
817 | - |
818 | - |
819 | -class sru(CanonicalCommand): |
820 | - |
821 | - """ |
822 | - Run stable release update (sru) tests. |
823 | - |
824 | - Stable release updates are periodic fixes for nominated bugs that land in |
825 | - existing supported Ubuntu releases. To ensure a certain level of quality |
826 | - all SRU updates affecting hardware enablement are automatically tested |
827 | - on a pool of certified machines. |
828 | - """ |
829 | - |
830 | - def __init__(self, config): |
831 | - """Init method to store the config settings.""" |
832 | - self.config = config |
833 | - if not self.config.test_plan: |
834 | - self.config.test_plan = "2013.com.canonical.certification::sru" |
835 | - |
836 | - def register_arguments(self, parser): |
837 | - """Method called to register command line arguments.""" |
838 | - parser.add_argument( |
839 | - '--secure_id', metavar=_("SECURE-ID"), |
840 | - # NOTE: --secure-id is optional only when set in a config file |
841 | - required=self.config.secure_id is Unset, |
842 | - help=_("Canonical hardware identifier")) |
843 | - parser.add_argument( |
844 | - '-T', '--test-plan', |
845 | - action="store", |
846 | - metavar=_("TEST-PLAN-ID"), |
847 | - default=None, |
848 | - # TRANSLATORS: this is in imperative form |
849 | - help=_("load the specified test plan")) |
850 | - parser.add_argument( |
851 | - '--staging', action='store_true', default=False, |
852 | - help=_("Send the data to non-production test server")) |
853 | - parser.add_argument( |
854 | - "--check-config", |
855 | - action="store_true", |
856 | - help=_("run check-config before starting")) |
857 | - |
858 | - def invoked(self, ctx): |
859 | - """Method called when the command is invoked.""" |
860 | - # Copy command-line arguments over configuration variables |
861 | - try: |
862 | - if ctx.args.secure_id: |
863 | - self.config.secure_id = ctx.args.secure_id |
864 | - if ctx.args.test_plan: |
865 | - self.config.test_plan = ctx.args.test_plan |
866 | - if ctx.args.staging: |
867 | - self.config.staging = ctx.args.staging |
868 | - except ValidationError as exc: |
869 | - print(_("Configuration problems prevent running SRU tests")) |
870 | - print(exc) |
871 | - return 1 |
872 | - ctx.sa.use_alternate_configuration(self.config) |
873 | - # Run check-config, if requested |
874 | - if ctx.args.check_config: |
875 | - retval = CheckConfigInvocation(lambda: self.config).run() |
876 | - if retval != 0: |
877 | - return retval |
878 | - self.transport = self._create_transport( |
879 | - ctx.sa, self.config.secure_id, self.config.staging) |
880 | - self.ctx = ctx |
881 | - try: |
882 | - self._collect_info(ctx.rc, ctx.sa) |
883 | - self._save_results(ctx.rc, ctx.sa) |
884 | - self._send_results( |
885 | - ctx.rc, ctx.sa, self.config.secure_id, self.config.staging) |
886 | - except KeyboardInterrupt: |
887 | - return 1 |
888 | - |
889 | - def _save_results(self, rc, sa): |
890 | - rc.reset() |
891 | - rc.padding = (1, 1, 0, 1) |
892 | - path = sa.export_to_file( |
893 | - "2013.com.canonical.plainbox::hexr", (), '/tmp') |
894 | - rc.para(_("Results saved to {0}").format(path)) |
895 | - |
896 | - def _send_results(self, rc, sa, secure_id, staging): |
897 | - rc.reset() |
898 | - rc.padding = (1, 1, 0, 1) |
899 | - rc.para(_("Sending hardware report to Canonical Certification")) |
900 | - rc.para(_("Server URL is: {0}").format(self.transport.url)) |
901 | - result = sa.export_to_transport( |
902 | - "2013.com.canonical.plainbox::hexr", self.transport) |
903 | - if 'url' in result: |
904 | - rc.para(result['url']) |
905 | - |
906 | - def _create_transport(self, sa, secure_id, staging): |
907 | - return sa.get_canonical_certification_transport( |
908 | - secure_id, staging=staging) |
909 | - |
910 | - def _collect_info(self, rc, sa): |
911 | - sa.select_providers('*') |
912 | - sa.start_new_session(_("SRU Session")) |
913 | - sa.select_test_plan(self.config.test_plan) |
914 | - sa.bootstrap() |
915 | - for job_id in sa.get_static_todo_list(): |
916 | - job = sa.get_job(job_id) |
917 | - builder = sa.run_job(job_id, 'silent', False) |
918 | - result = builder.get_result() |
919 | - sa.use_job_result(job_id, result) |
920 | - rc.para("- {0}: {1}".format(job.id, result)) |
921 | - if result.comments: |
922 | - rc.padding = (0, 0, 0, 2) |
923 | - rc.para("{0}".format(result.comments)) |
924 | - rc.reset() |
925 | - |
926 | - |
927 | -class SRUCommand(PlainBoxCommand): |
928 | - |
929 | - """ |
930 | - Command for running Stable Release Update (SRU) tests. |
931 | - |
932 | - Stable release updates are periodic fixes for nominated bugs that land in |
933 | - existing supported Ubuntu releases. To ensure a certain level of quality |
934 | - all SRU updates affecting hardware enablement are automatically tested |
935 | - on a pool of certified machines. |
936 | - """ |
937 | - |
938 | - gettext_domain = "checkbox-ng" |
939 | - |
940 | - def __init__(self, provider_loader, config_loader): |
941 | - self.provider_loader = provider_loader |
942 | - # This command does funky things to the command line parser and it |
943 | - # needs to load the config subsystem *early* so let's just load it now. |
944 | - self.config = config_loader() |
945 | - |
946 | - def invoked(self, ns): |
947 | - """Method called when the command is invoked.""" |
948 | - return sru(self.config).main(sys.argv[2:], exit=False) |
949 | - |
950 | - def register_parser(self, subparsers): |
951 | - """Method called to register command line arguments.""" |
952 | - parser = subparsers.add_parser( |
953 | - "sru", help=_("run automated stable release update tests")) |
954 | - parser.set_defaults(command=self) |
955 | - sru(self.config).register_arguments(parser) |
956 | diff --git a/checkbox_ng/commands/submit.py b/checkbox_ng/commands/submit.py |
957 | deleted file mode 100644 |
958 | index 6d24a8a..0000000 |
959 | --- a/checkbox_ng/commands/submit.py |
960 | +++ /dev/null |
961 | @@ -1,154 +0,0 @@ |
962 | -# This file is part of Checkbox. |
963 | -# |
964 | -# Copyright 2014 Canonical Ltd. |
965 | -# Written by: |
966 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
967 | -# |
968 | -# Checkbox is free software: you can redistribute it and/or modify |
969 | -# it under the terms of the GNU General Public License version 3, |
970 | -# as published by the Free Software Foundation. |
971 | -# |
972 | -# Checkbox is distributed in the hope that it will be useful, |
973 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
974 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
975 | -# GNU General Public License for more details. |
976 | -# |
977 | -# You should have received a copy of the GNU General Public License |
978 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
979 | - |
980 | -""" |
981 | -:mod:`checkbox_ng.commands.submit` -- the submit sub-command |
982 | -============================================================ |
983 | - |
984 | -.. warning:: |
985 | - |
986 | - THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
987 | -""" |
988 | - |
989 | -from argparse import ArgumentTypeError |
990 | -from plainbox.i18n import docstring |
991 | -from plainbox.i18n import gettext as _ |
992 | -from plainbox.i18n import gettext_noop as N_ |
993 | -import re |
994 | - |
995 | -from plainbox.impl.commands import PlainBoxCommand |
996 | -from plainbox.impl.secure.config import Unset |
997 | -from plainbox.impl.transport import TransportError |
998 | -from plainbox.impl.transport import SECURE_ID_PATTERN |
999 | - |
1000 | -from checkbox_ng.certification import CertificationTransport |
1001 | - |
1002 | - |
1003 | -class SubmitInvocation: |
1004 | - """ |
1005 | - Helper class instantiated to perform a particular invocation of the submit |
1006 | - command. Unlike the SRU command itself, this class is instantiated each |
1007 | - time. |
1008 | - """ |
1009 | - |
1010 | - def __init__(self, ns): |
1011 | - self.ns = ns |
1012 | - |
1013 | - def run(self): |
1014 | - options_string = "secure_id={0}".format(self.ns.secure_id) |
1015 | - transport = CertificationTransport(self.ns.url, options_string) |
1016 | - |
1017 | - try: |
1018 | - with open(self.ns.submission, "r", encoding='utf-8') as subm_file: |
1019 | - result = transport.send(subm_file) |
1020 | - except (TransportError, OSError) as exc: |
1021 | - raise SystemExit(exc) |
1022 | - else: |
1023 | - if 'url' in result: |
1024 | - # TRANSLATORS: Do not translate the {} format marker. |
1025 | - print(_("Successfully sent, submission status" |
1026 | - " at {0}").format(result['url'])) |
1027 | - else: |
1028 | - # TRANSLATORS: Do not translate the {} format marker. |
1029 | - print(_("Successfully sent, server response" |
1030 | - ": {0}").format(result)) |
1031 | - |
1032 | - |
1033 | -@docstring( |
1034 | - # TRANSLATORS: please leave various options (both long and short forms), |
1035 | - # environment variables and paths in their original form. Also keep the |
1036 | - # special @EPILOG@ string. The first line of the translation is special and |
1037 | - # is used as the help message. Please keep the pseudo-statement form and |
1038 | - # don't finish the sentence with a dot. Pay extra attention to whitespace. |
1039 | - # It must be correctly preserved or the result won't work. In particular |
1040 | - # the leading whitespace *must* be preserved and *must* have the same |
1041 | - # length on each line. |
1042 | - N_(""" |
1043 | - submit test results to the Canonical certification website |
1044 | - |
1045 | - This command sends the XML results file to the Certification website. |
1046 | - """)) |
1047 | -class SubmitCommand(PlainBoxCommand): |
1048 | - |
1049 | - gettext_domain = "checkbox-ng" |
1050 | - |
1051 | - def __init__(self, config_loader): |
1052 | - self.config = config_loader() |
1053 | - |
1054 | - def invoked(self, ns): |
1055 | - return SubmitInvocation(ns).run() |
1056 | - |
1057 | - def register_parser(self, subparsers): |
1058 | - parser = subparsers.add_parser("submit", help=_( |
1059 | - "submit test results to the Canonical certification website")) |
1060 | - self.register_arguments(parser) |
1061 | - |
1062 | - def register_arguments(self, parser): |
1063 | - parser.set_defaults(command=self) |
1064 | - parser.add_argument( |
1065 | - 'submission', help=_("The path to the results xml file")) |
1066 | - self.register_optional_arguments(parser, required=True) |
1067 | - |
1068 | - def register_optional_arguments(self, parser, required=False): |
1069 | - if self.config.secure_id is not Unset: |
1070 | - parser.set_defaults(secure_id=self.config.secure_id) |
1071 | - |
1072 | - def secureid(secure_id): |
1073 | - if not re.match(SECURE_ID_PATTERN, secure_id): |
1074 | - raise ArgumentTypeError( |
1075 | - _("must be 15-character (or more) alphanumeric string")) |
1076 | - return secure_id |
1077 | - |
1078 | - required_check = False |
1079 | - if required: |
1080 | - required_check = self.config.secure_id is Unset |
1081 | - parser.add_argument( |
1082 | - '--secure_id', metavar=_("SECURE-ID"), |
1083 | - required=required_check, |
1084 | - type=secureid, |
1085 | - help=_("associate submission with a machine using this SECURE-ID")) |
1086 | - |
1087 | - # Interpret this setting here |
1088 | - # Please remember the Unset.__bool__() return False |
1089 | - # After Interpret the setting, |
1090 | - # self.config.submit_to_c3 should has value or be Unset. |
1091 | - try: |
1092 | - if (self.config.submit_to_c3 and |
1093 | - (self.config.submit_to_c3.lower() in ('yes', 'true') or |
1094 | - int(self.config.submit_to_c3) == 1)): |
1095 | - # self.config.c3_url has a default value written in config.py |
1096 | - parser.set_defaults(url=self.config.c3_url) |
1097 | - else: |
1098 | - # if submit_to_c3 is castable to int but not 1 |
1099 | - # this is still set as Unset |
1100 | - # otherwise url requirement will be None |
1101 | - self.config.submit_to_c3 = Unset |
1102 | - except ValueError: |
1103 | - # When submit_to_c3 is something other than 'yes', 'true', |
1104 | - # castable to integer, it raises ValueError. |
1105 | - # e.g. 'no', 'false', 'asdf' ...etc. |
1106 | - # In this case, it is still set as Unset. |
1107 | - self.config.submit_to_c3 = Unset |
1108 | - |
1109 | - required_check = False |
1110 | - if required: |
1111 | - required_check = self.config.submit_to_c3 is Unset |
1112 | - parser.add_argument( |
1113 | - '--url', metavar=_("URL"), |
1114 | - required=required_check, |
1115 | - help=_("destination to submit to")) |
1116 | diff --git a/checkbox_ng/commands/test_sru.py b/checkbox_ng/commands/test_sru.py |
1117 | deleted file mode 100644 |
1118 | index 8d456ee..0000000 |
1119 | --- a/checkbox_ng/commands/test_sru.py |
1120 | +++ /dev/null |
1121 | @@ -1,56 +0,0 @@ |
1122 | -# This file is part of Checkbox. |
1123 | -# |
1124 | -# Copyright 2013 Canonical Ltd. |
1125 | -# Written by: |
1126 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
1127 | -# |
1128 | -# Checkbox is free software: you can redistribute it and/or modify |
1129 | -# it under the terms of the GNU General Public License version 3, |
1130 | -# as published by the Free Software Foundation. |
1131 | - |
1132 | -# |
1133 | -# Checkbox is distributed in the hope that it will be useful, |
1134 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1135 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1136 | -# GNU General Public License for more details. |
1137 | -# |
1138 | -# You should have received a copy of the GNU General Public License |
1139 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1140 | - |
1141 | -""" |
1142 | -plainbox.impl.commands.test_sru |
1143 | -=============================== |
1144 | - |
1145 | -Test definitions for plainbox.impl.box module |
1146 | -""" |
1147 | - |
1148 | -from inspect import cleandoc |
1149 | -from unittest import TestCase |
1150 | - |
1151 | -from plainbox.testing_utils.io import TestIO |
1152 | - |
1153 | -from checkbox_ng.main import main |
1154 | - |
1155 | - |
1156 | -class TestSru(TestCase): |
1157 | - |
1158 | - def test_help(self): |
1159 | - with TestIO(combined=True) as io: |
1160 | - with self.assertRaises(SystemExit) as call: |
1161 | - main(['sru', '--help']) |
1162 | - self.assertEqual(call.exception.args, (0,)) |
1163 | - self.maxDiff = None |
1164 | - expected = """ |
1165 | - usage: checkbox sru [-h] --secure_id SECURE-ID [-T TEST-PLAN-ID] [--staging] |
1166 | - [--check-config] |
1167 | - |
1168 | - optional arguments: |
1169 | - -h, --help show this help message and exit |
1170 | - --secure_id SECURE-ID |
1171 | - Canonical hardware identifier |
1172 | - -T TEST-PLAN-ID, --test-plan TEST-PLAN-ID |
1173 | - load the specified test plan |
1174 | - --staging Send the data to non-production test server |
1175 | - --check-config run check-config before starting |
1176 | - """ |
1177 | - self.assertEqual(io.combined, cleandoc(expected) + "\n") |
1178 | diff --git a/checkbox_ng/config.py b/checkbox_ng/config.py |
1179 | index 7107dc0..c84f2b8 100644 |
1180 | --- a/checkbox_ng/config.py |
1181 | +++ b/checkbox_ng/config.py |
1182 | @@ -22,13 +22,10 @@ |
1183 | ===================================================== |
1184 | """ |
1185 | |
1186 | -from gettext import gettext as _ |
1187 | import itertools |
1188 | import os |
1189 | |
1190 | from plainbox.impl.applogic import PlainBoxConfig |
1191 | -from plainbox.impl.secure import config |
1192 | -from plainbox.impl.transport import SECURE_ID_PATTERN |
1193 | |
1194 | |
1195 | class CheckBoxConfig(PlainBoxConfig): |
1196 | @@ -36,44 +33,6 @@ class CheckBoxConfig(PlainBoxConfig): |
1197 | Configuration for checkbox-ng |
1198 | """ |
1199 | |
1200 | - secure_id = config.Variable( |
1201 | - section="sru", |
1202 | - help_text=_("Secure ID of the system"), |
1203 | - validator_list=[config.PatternValidator(SECURE_ID_PATTERN)]) |
1204 | - |
1205 | - submit_to_c3 = config.Variable( |
1206 | - section="submission", |
1207 | - help_text=_("Whether to send the submission data to c3")) |
1208 | - |
1209 | - submit_to_hexr = config.Variable( |
1210 | - section="submission", |
1211 | - help_text=_("Whether to also send the submission data to HEXR"), |
1212 | - kind=bool) |
1213 | - |
1214 | - # TODO: Add a validator to check if URL looks fine |
1215 | - c3_url = config.Variable( |
1216 | - section="sru", |
1217 | - help_text=_("URL of the certification website"), |
1218 | - default="https://certification.canonical.com/submissions/submit/") |
1219 | - |
1220 | - fallback_file = config.Variable( |
1221 | - section="sru", |
1222 | - help_text=_("Location of the fallback file")) |
1223 | - |
1224 | - whitelist = config.Variable( |
1225 | - section="sru", |
1226 | - help_text=_("Optional whitelist with which to run SRU testing")) |
1227 | - |
1228 | - test_plan = config.Variable( |
1229 | - section="sru", |
1230 | - help_text=_("Optional test plan with which to run SRU testing")) |
1231 | - |
1232 | - staging = config.Variable( |
1233 | - section="sru", |
1234 | - kind=bool, |
1235 | - default=False, |
1236 | - help_text=_("Send the data to non-production test server")) |
1237 | - |
1238 | class Meta(PlainBoxConfig.Meta): |
1239 | # TODO: properly depend on xdg and use real code that also handles |
1240 | # XDG_CONFIG_HOME. |
1241 | diff --git a/checkbox_ng/launcher/checkbox_cli.py b/checkbox_ng/launcher/checkbox_cli.py |
1242 | index 1317ed2..7c368c7 100644 |
1243 | --- a/checkbox_ng/launcher/checkbox_cli.py |
1244 | +++ b/checkbox_ng/launcher/checkbox_cli.py |
1245 | @@ -38,16 +38,15 @@ from plainbox.impl.ingredients import RenderingContextIngredient |
1246 | from plainbox.impl.ingredients import SessionAssistantIngredient |
1247 | from plainbox.impl.launcher import DefaultLauncherDefinition |
1248 | from plainbox.impl.launcher import LauncherDefinition |
1249 | -from plainbox.vendor.textland import get_display |
1250 | |
1251 | from checkbox_ng.launcher.subcommands import ( |
1252 | - Launcher, List, Run, StartProvider, ListBootstrapped |
1253 | + CheckConfig, Launcher, List, Run, StartProvider, Submit, ListBootstrapped |
1254 | ) |
1255 | |
1256 | |
1257 | _ = gettext.gettext |
1258 | |
1259 | -_logger = logging.getLogger("checkbox-launcher") |
1260 | +_logger = logging.getLogger("checkbox-cli") |
1261 | |
1262 | |
1263 | class DisplayIngredient(Ingredient): |
1264 | @@ -125,7 +124,6 @@ class CheckboxCommandRecipe(CommandRecipe): |
1265 | LauncherIngredient(), |
1266 | SessionAssistantIngredient(), |
1267 | RenderingContextIngredient(), |
1268 | - DisplayIngredient(), |
1269 | ] |
1270 | |
1271 | |
1272 | @@ -141,10 +139,12 @@ class CheckboxCommand(CanonicalCommand): |
1273 | bug_report_url = "https://bugs.launchpad.net/checkbox-ng/+filebug" |
1274 | |
1275 | sub_commands = ( |
1276 | + ('check-config', CheckConfig), |
1277 | ('launcher', Launcher), |
1278 | ('list', List), |
1279 | ('run', Run), |
1280 | ('startprovider', StartProvider), |
1281 | + ('submit', Submit), |
1282 | ('list-bootstrapped', ListBootstrapped), |
1283 | ) |
1284 | |
1285 | @@ -179,6 +179,7 @@ def main(): |
1286 | # $ checkbox-cli launcher my-launcher -> same as ^ |
1287 | # to achieve that the following code 'injects launcher subcommand to argv |
1288 | known_cmds = [x[0] for x in CheckboxCommand.sub_commands] |
1289 | + known_cmds += ['-h', '--help'] |
1290 | if not (set(known_cmds) & set(sys.argv[1:])): |
1291 | sys.argv.insert(1, 'launcher') |
1292 | CheckboxCommand().main() |
1293 | diff --git a/checkbox_ng/launcher/subcommands.py b/checkbox_ng/launcher/subcommands.py |
1294 | index 7f52cda..155824a 100644 |
1295 | --- a/checkbox_ng/launcher/subcommands.py |
1296 | +++ b/checkbox_ng/launcher/subcommands.py |
1297 | @@ -37,6 +37,7 @@ from guacamole import Command |
1298 | from plainbox.abc import IJobResult |
1299 | from plainbox.i18n import ngettext |
1300 | from plainbox.impl.color import Colorizer |
1301 | +from plainbox.impl.commands.inv_check_config import CheckConfigInvocation |
1302 | from plainbox.impl.commands.inv_run import Action |
1303 | from plainbox.impl.commands.inv_run import NormalUI |
1304 | from plainbox.impl.commands.inv_startprovider import ( |
1305 | @@ -55,8 +56,10 @@ from plainbox.impl.session.restart import get_strategy_by_name |
1306 | from plainbox.impl.transport import TransportError |
1307 | from plainbox.impl.transport import InvalidSecureIDError |
1308 | from plainbox.impl.transport import get_all_transports |
1309 | +from plainbox.impl.transport import SECURE_ID_PATTERN |
1310 | from plainbox.public import get_providers |
1311 | |
1312 | +from checkbox_ng.config import CheckBoxConfig |
1313 | from checkbox_ng.launcher.stages import MainLoopStage |
1314 | from checkbox_ng.urwid_ui import CategoryBrowser |
1315 | from checkbox_ng.urwid_ui import ReRunBrowser |
1316 | @@ -67,6 +70,73 @@ _ = gettext.gettext |
1317 | _logger = logging.getLogger("checkbox-ng.launcher.subcommands") |
1318 | |
1319 | |
1320 | +class CheckConfig(Command): |
1321 | + def invoked(self, ctx): |
1322 | + return CheckConfigInvocation(lambda: CheckBoxConfig.get()).run() |
1323 | + |
1324 | + |
1325 | +class Submit(Command): |
1326 | + def register_arguments(self, parser): |
1327 | + def secureid(secure_id): |
1328 | + if not re.match(SECURE_ID_PATTERN, secure_id): |
1329 | + raise ArgumentTypeError( |
1330 | + _("must be 15-character (or more) alphanumeric string")) |
1331 | + return secure_id |
1332 | + parser.add_argument( |
1333 | + 'secure_id', metavar=_("SECURE-ID"), |
1334 | + type=secureid, |
1335 | + help=_("associate submission with a machine using this SECURE-ID")) |
1336 | + parser.add_argument( |
1337 | + "submission", metavar=_("SUBMISSION"), |
1338 | + help=_("The path to the results file")) |
1339 | + parser.add_argument( |
1340 | + "-s", "--staging", action="store_true", |
1341 | + help=_("Use staging environment")) |
1342 | + |
1343 | + def invoked(self, ctx): |
1344 | + transport_cls = None |
1345 | + enc = None |
1346 | + mode = 'rb' |
1347 | + options_string = "secure_id={0}".format(ctx.args.secure_id) |
1348 | + url = ('https://submission.canonical.com/' |
1349 | + 'v1/submission/hardware/{}'.format(ctx.args.secure_id)) |
1350 | + if ctx.args.staging: |
1351 | + url = ('https://submission.staging.canonical.com/' |
1352 | + 'v1/submission/hardware/{}'.format(ctx.args.secure_id)) |
1353 | + if ctx.args.submission.endswith('xml'): |
1354 | + from checkbox_ng.certification import CertificationTransport |
1355 | + transport_cls = CertificationTransport |
1356 | + mode = 'r' |
1357 | + enc = 'utf-8' |
1358 | + url = ('https://certification.canonical.com/' |
1359 | + 'submissions/submit/') |
1360 | + if ctx.args.staging: |
1361 | + url = ('https://certification.staging.canonical.com/' |
1362 | + 'submissions/submit/') |
1363 | + else: |
1364 | + from checkbox_ng.certification import SubmissionServiceTransport |
1365 | + transport_cls = SubmissionServiceTransport |
1366 | + transport = transport_cls(url, options_string) |
1367 | + try: |
1368 | + with open(ctx.args.submission, mode, encoding=enc) as subm_file: |
1369 | + result = transport.send(subm_file) |
1370 | + except (TransportError, OSError) as exc: |
1371 | + raise SystemExit(exc) |
1372 | + else: |
1373 | + if result and 'url' in result: |
1374 | + # TRANSLATORS: Do not translate the {} format marker. |
1375 | + print(_("Successfully sent, submission status" |
1376 | + " at {0}").format(result['url'])) |
1377 | + elif result and 'status_url' in result: |
1378 | + # TRANSLATORS: Do not translate the {} format marker. |
1379 | + print(_("Successfully sent, submission status" |
1380 | + " at {0}").format(result['status_url'])) |
1381 | + else: |
1382 | + # TRANSLATORS: Do not translate the {} format marker. |
1383 | + print(_("Successfully sent, server response" |
1384 | + ": {0}").format(result)) |
1385 | + |
1386 | + |
1387 | class StartProvider(Command): |
1388 | def register_arguments(self, parser): |
1389 | parser.add_argument( |
1390 | @@ -114,10 +184,6 @@ class Launcher(Command, MainLoopStage): |
1391 | print(_("Launcher seems valid.")) |
1392 | return |
1393 | self.launcher = ctx.cmd_toplevel.launcher |
1394 | - if not self.launcher.launcher_version: |
1395 | - # it's a legacy launcher, use legacy way of running commands |
1396 | - from checkbox_ng.tools import CheckboxLauncherTool |
1397 | - raise SystemExit(CheckboxLauncherTool().main(sys.argv[1:])) |
1398 | logging_level = { |
1399 | 'normal': logging.WARNING, |
1400 | 'verbose': logging.INFO, |
1401 | @@ -897,6 +963,7 @@ class List(Command): |
1402 | print(_("--format applies only to 'all-jobs' group. Ignoring...")) |
1403 | print_objs(ctx.args.GROUP, ctx.args.attrs) |
1404 | |
1405 | + |
1406 | class ListBootstrapped(Command): |
1407 | name = 'list-bootstrapped' |
1408 | |
1409 | diff --git a/checkbox_ng/main.py b/checkbox_ng/main.py |
1410 | deleted file mode 100644 |
1411 | index 178edd9..0000000 |
1412 | --- a/checkbox_ng/main.py |
1413 | +++ /dev/null |
1414 | @@ -1,61 +0,0 @@ |
1415 | -# This file is part of Checkbox. |
1416 | -# |
1417 | -# Copyright 2012-2014 Canonical Ltd. |
1418 | -# Written by: |
1419 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
1420 | -# |
1421 | -# Checkbox is free software: you can redistribute it and/or modify |
1422 | -# it under the terms of the GNU General Public License version 3, |
1423 | -# as published by the Free Software Foundation. |
1424 | -# |
1425 | -# Checkbox is distributed in the hope that it will be useful, |
1426 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1427 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1428 | -# GNU General Public License for more details. |
1429 | -# |
1430 | -# You should have received a copy of the GNU General Public License |
1431 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1432 | - |
1433 | -""" |
1434 | -:mod:`checkbox_ng.main` -- entry points for command line tools |
1435 | -============================================================== |
1436 | -""" |
1437 | - |
1438 | -import logging |
1439 | - |
1440 | -from plainbox.impl.logging import setup_logging |
1441 | - |
1442 | -from checkbox_ng.tools import CheckboxLauncherTool |
1443 | -from checkbox_ng.tools import CheckboxSubmitTool |
1444 | -from checkbox_ng.tools import CheckboxTool |
1445 | - |
1446 | - |
1447 | -logger = logging.getLogger("checkbox.ng.main") |
1448 | - |
1449 | - |
1450 | -def main(argv=None): |
1451 | - """ |
1452 | - checkbox command line utility |
1453 | - """ |
1454 | - raise SystemExit(CheckboxTool().main(argv)) |
1455 | - |
1456 | - |
1457 | -def submit(argv=None): |
1458 | - """ |
1459 | - checkbox-submit command line utility |
1460 | - """ |
1461 | - raise SystemExit(CheckboxSubmitTool().main(argv)) |
1462 | - |
1463 | - |
1464 | -def launcher(argv=None): |
1465 | - """ |
1466 | - checkbox-launcher command line utility |
1467 | - """ |
1468 | - raise SystemExit(CheckboxLauncherTool().main(argv)) |
1469 | - |
1470 | - |
1471 | -# Setup logging before anything else starts working. |
1472 | -# If we do it in main() or some other place then unit tests will see |
1473 | -# "leaked" log files which are really closed when the runtime shuts |
1474 | -# down but not when the tests are finishing |
1475 | -setup_logging() |
1476 | diff --git a/checkbox_ng/misc.py b/checkbox_ng/misc.py |
1477 | deleted file mode 100644 |
1478 | index e5547cd..0000000 |
1479 | --- a/checkbox_ng/misc.py |
1480 | +++ /dev/null |
1481 | @@ -1,476 +0,0 @@ |
1482 | -# This file is part of Checkbox. |
1483 | -# |
1484 | -# Copyright 2013-2014 Canonical Ltd. |
1485 | -# Written by: |
1486 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
1487 | -# |
1488 | -# Checkbox is free software: you can redistribute it and/or modify |
1489 | -# it under the terms of the GNU General Public License version 3, |
1490 | -# as published by the Free Software Foundation. |
1491 | -# |
1492 | -# Checkbox is distributed in the hope that it will be useful, |
1493 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1494 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1495 | -# GNU General Public License for more details. |
1496 | -# |
1497 | -# You should have received a copy of the GNU General Public License |
1498 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1499 | - |
1500 | -""" |
1501 | -:mod:`checkbox_ng.misc` -- Other stuff |
1502 | -====================================== |
1503 | - |
1504 | -.. warning:: |
1505 | - |
1506 | - THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
1507 | -""" |
1508 | - |
1509 | -from gettext import gettext as _ |
1510 | -from logging import getLogger |
1511 | - |
1512 | -from plainbox.abc import IJobResult |
1513 | - |
1514 | - |
1515 | -logger = getLogger("checkbox.ng.commands.cli") |
1516 | - |
1517 | - |
1518 | -class JobTreeNode: |
1519 | - |
1520 | - r""" |
1521 | - JobTreeNode class is used to store a tree structure. |
1522 | - |
1523 | - A tree consists of a collection of JobTreeNode instances connected in a |
1524 | - hierarchical way where nodes are used as categories, jobs belonging to a |
1525 | - category are listed in the node leaves. |
1526 | - |
1527 | - Example:: |
1528 | - / Job A |
1529 | - Root-| |
1530 | - | / Job B |
1531 | - \--- Category X | |
1532 | - \ Job C |
1533 | - """ |
1534 | - |
1535 | - def __init__(self, name=None): |
1536 | - """ Initialize the job tree node with a given name. """ |
1537 | - self._name = name if name else 'Root' |
1538 | - self._parent = None |
1539 | - self._categories = [] |
1540 | - self._jobs = [] |
1541 | - |
1542 | - @property |
1543 | - def name(self): |
1544 | - """ name of this node. """ |
1545 | - return self._name |
1546 | - |
1547 | - @property |
1548 | - def parent(self): |
1549 | - """ parent node for this node. """ |
1550 | - return self._parent |
1551 | - |
1552 | - @property |
1553 | - def categories(self): |
1554 | - """ list of sub categories. """ |
1555 | - return self._categories |
1556 | - |
1557 | - @property |
1558 | - def jobs(self): |
1559 | - """ job(s) belonging to this node/category. """ |
1560 | - return self._jobs |
1561 | - |
1562 | - @property |
1563 | - def depth(self): |
1564 | - """ level of depth for this node. """ |
1565 | - return (self._parent.depth + 1) if self._parent else 0 |
1566 | - |
1567 | - def __str__(self): |
1568 | - """ same as self.name. """ |
1569 | - return self.name |
1570 | - |
1571 | - def __repr__(self): |
1572 | - """ Get a representation of this node for debugging. """ |
1573 | - return "<JobTreeNode name:{!r}>".format(self.name) |
1574 | - |
1575 | - def add_category(self, category): |
1576 | - """ |
1577 | - Add a new category to this node. |
1578 | - |
1579 | - :param category: |
1580 | - The node instance to be added as a category. |
1581 | - """ |
1582 | - self._categories.append(category) |
1583 | - # Always keep this list sorted to easily find a given child by index |
1584 | - self._categories.sort(key=lambda item: item.name) |
1585 | - category._parent = self |
1586 | - |
1587 | - def add_job(self, job): |
1588 | - """ |
1589 | - Add a new job to this node. |
1590 | - |
1591 | - :param job: |
1592 | - The job instance to be added to this node. |
1593 | - """ |
1594 | - self._jobs.append(job) |
1595 | - # Always keep this list sorted to easily find a given leaf by index |
1596 | - # Note bisect.insort(a, x) cannot be used here as JobDefinition are |
1597 | - # not sortable |
1598 | - self._jobs.sort(key=lambda item: item.id) |
1599 | - |
1600 | - def get_ancestors(self): |
1601 | - """ Get the list of ancestors from here to the root of the tree. """ |
1602 | - ancestors = [] |
1603 | - node = self |
1604 | - while node.parent is not None: |
1605 | - ancestors.append(node.parent) |
1606 | - node = node.parent |
1607 | - return ancestors |
1608 | - |
1609 | - def get_descendants(self): |
1610 | - """ Return a list of all descendant category nodes. """ |
1611 | - descendants = [] |
1612 | - for category in self.categories: |
1613 | - descendants.append(category) |
1614 | - descendants.extend(category.get_descendants()) |
1615 | - return descendants |
1616 | - |
1617 | - @classmethod |
1618 | - def create_tree(cls, session_state, job_list): |
1619 | - """ |
1620 | - Build a rooted JobTreeNode from a job list. |
1621 | - |
1622 | - :argument session_state: |
1623 | - A session state object |
1624 | - :argument job_list: |
1625 | - List of jobs to consider for building the tree. |
1626 | - """ |
1627 | - builder = TreeBuilder(session_state, cls) |
1628 | - for job in job_list: |
1629 | - builder.auto_add_job(job) |
1630 | - return builder.root_node |
1631 | - |
1632 | - @classmethod |
1633 | - def create_simple_tree(cls, sa, job_list): |
1634 | - """ |
1635 | - Build a rooted JobTreeNode from a job list. |
1636 | - |
1637 | - :argument sa: |
1638 | - A session assistant object |
1639 | - :argument job_list: |
1640 | - List of jobs to consider for building the tree. |
1641 | - """ |
1642 | - root_node = cls() |
1643 | - for job in job_list: |
1644 | - cat_id = sa.get_job_state(job.id).effective_category_id |
1645 | - cat_name = sa.get_category(cat_id).tr_name() |
1646 | - matches = [n for n in root_node.categories if n.name == cat_name] |
1647 | - if not matches: |
1648 | - node = cls(cat_name) |
1649 | - root_node.add_category(node) |
1650 | - else: |
1651 | - node = matches[0] |
1652 | - node.add_job(job) |
1653 | - return root_node |
1654 | - |
1655 | - @classmethod |
1656 | - def create_rerun_tree(cls, sa, job_list): |
1657 | - """ |
1658 | - Build a rooted JobTreeNode from a job list for the re-run screen. |
1659 | - The jobs are categorized by their outcome (failed, skipped, ...) |
1660 | - instead of by the category they belong to. |
1661 | - |
1662 | - :argument sa: |
1663 | - A session assistant object |
1664 | - :argument job_list: |
1665 | - List of jobs to consider for building the tree. |
1666 | - """ |
1667 | - section_names = { |
1668 | - IJobResult.OUTCOME_FAIL: _("Failed Jobs"), |
1669 | - IJobResult.OUTCOME_SKIP: _("Skipped Jobs"), |
1670 | - IJobResult.OUTCOME_CRASH: _("Crashed Jobs"), |
1671 | - } |
1672 | - root_node = cls() |
1673 | - for job in job_list: |
1674 | - cat_id = sa.get_job_state(job.id).effective_category_id |
1675 | - cat_name = sa.get_category(cat_id).tr_name() |
1676 | - job_outcome = sa.get_job_state(job.id).result.outcome |
1677 | - job_section = section_names[job_outcome] |
1678 | - matches = [n for n in root_node.categories if n.name == job_section] |
1679 | - if not matches: |
1680 | - node = cls(job_section) |
1681 | - root_node.add_category(node) |
1682 | - else: |
1683 | - node = matches[0] |
1684 | - |
1685 | - node.add_job(job) |
1686 | - return root_node |
1687 | - |
1688 | - |
1689 | -class TreeBuilder: |
1690 | - |
1691 | - """ |
1692 | - Builder for :class:`JobTreeNode`. |
1693 | - |
1694 | - |
1695 | - Helper class that assists in building a tree of :class:`JobTreeNode` |
1696 | - objects out of job definitions and their associations, as expressed by |
1697 | - :attr:`JobState.via_job` associated with each job. |
1698 | - |
1699 | - The builder is a single-use object and should be re-created for each new |
1700 | - construct. Internally it stores the job_state_map of the |
1701 | - :class:`SessionState` it was created with as well as additional helper |
1702 | - state. |
1703 | - """ |
1704 | - |
1705 | - def __init__(self, session_state: "SessionState", node_cls): |
1706 | - self._job_state_map = session_state.job_state_map |
1707 | - self._node_cls = node_cls |
1708 | - self._root_node = node_cls() |
1709 | - self._category_node_map = {} # id -> node |
1710 | - |
1711 | - @property |
1712 | - def root_node(self): |
1713 | - return self._root_node |
1714 | - |
1715 | - def auto_add_job(self, job): |
1716 | - """ |
1717 | - Add a job to the tree, automatically creating category nodes as needed. |
1718 | - |
1719 | - :param job: |
1720 | - The job definition to add. |
1721 | - """ |
1722 | - if job.plugin == 'local': |
1723 | - # For local jobs, just create the category node but don't add the |
1724 | - # local job itself there. |
1725 | - self.get_or_create_category_node(job) |
1726 | - else: |
1727 | - # For all other jobs, look at the parent job (if any) and create |
1728 | - # the category node out of that node. This never fails as "None" is |
1729 | - # the root_node object. |
1730 | - state = self._job_state_map[job.id] |
1731 | - node = self.get_or_create_category_node(state.via_job) |
1732 | - # Then add that job to the category node |
1733 | - node.add_job(job) |
1734 | - |
1735 | - def get_or_create_category_node(self, category_job): |
1736 | - """ |
1737 | - Get a category node for a given job. |
1738 | - |
1739 | - Get or create a :class:`JobTreeNode` that corresponds to the |
1740 | - category defined (somehow) by the job ``category_job``. |
1741 | - |
1742 | - :param category_job: |
1743 | - The job that describes the category. This is either a |
1744 | - plugin="local" job or a plugin="resource" job. This can also be |
1745 | - None, which is a shorthand to say "root node". |
1746 | - :returns: |
1747 | - The ``root_node`` if ``category_job`` is None. A freshly |
1748 | - created node, created with :func:`create_category_node()` if |
1749 | - the category_job was never seen before (as recorded by the |
1750 | - category_node_map). |
1751 | - """ |
1752 | - logger.debug("get_or_create_category_node(%r)", category_job) |
1753 | - if category_job is None: |
1754 | - return self._root_node |
1755 | - if category_job.id not in self._category_node_map: |
1756 | - category_node = self.create_category_node(category_job) |
1757 | - # The category is added to its parent, that's either the root |
1758 | - # (if we're standalone) or the non-root category this one |
1759 | - # belongs to. |
1760 | - category_state = self._job_state_map[category_job.id] |
1761 | - if category_state.via_job is not None: |
1762 | - parent_category_node = self.get_or_create_category_node( |
1763 | - category_state.via_job) |
1764 | - else: |
1765 | - parent_category_node = self._root_node |
1766 | - parent_category_node.add_category(category_node) |
1767 | - else: |
1768 | - category_node = self._category_node_map[category_job.id] |
1769 | - return category_node |
1770 | - |
1771 | - def create_category_node(self, category_job): |
1772 | - """ |
1773 | - Create a category node for a given job. |
1774 | - |
1775 | - Create a :class:`JobTreeNode` that corresponds to the category defined |
1776 | - (somehow) by the job ``category_job``. |
1777 | - |
1778 | - :param category_job: |
1779 | - The job that describes the node to create. |
1780 | - :returns: |
1781 | - A fresh node with appropriate data. |
1782 | - """ |
1783 | - logger.debug("create_category_node(%r)", category_job) |
1784 | - if category_job.summary == category_job.partial_id: |
1785 | - category_node = self._node_cls(category_job.description) |
1786 | - else: |
1787 | - category_node = self._node_cls(category_job.summary) |
1788 | - self._category_node_map[category_job.id] = category_node |
1789 | - return category_node |
1790 | - |
1791 | - |
1792 | -class SelectableJobTreeNode(JobTreeNode): |
1793 | - """ |
1794 | - Implementation of a node in a tree that can be selected/deselected |
1795 | - """ |
1796 | - def __init__(self, job=None): |
1797 | - super().__init__(job) |
1798 | - self.selected = True |
1799 | - self.job_selection = {} |
1800 | - self.expanded = True |
1801 | - self.current_index = 0 |
1802 | - self._resource_jobs = [] |
1803 | - |
1804 | - def __len__(self): |
1805 | - l = 0 |
1806 | - if self.expanded: |
1807 | - for category in self.categories: |
1808 | - l += 1 + len(category) |
1809 | - for job in self.jobs: |
1810 | - l += 1 |
1811 | - return l |
1812 | - |
1813 | - def get_node_by_index(self, index, tree=None): |
1814 | - """ |
1815 | - Return the node found at the position given by index considering the |
1816 | - tree from a top-down list view. |
1817 | - """ |
1818 | - if tree is None: |
1819 | - tree = self |
1820 | - if self.expanded: |
1821 | - for category in self.categories: |
1822 | - if index == tree.current_index: |
1823 | - tree.current_index = 0 |
1824 | - return (category, None) |
1825 | - else: |
1826 | - tree.current_index += 1 |
1827 | - result = category.get_node_by_index(index, tree) |
1828 | - if result[0] is not None and result[1] is not None: |
1829 | - return result |
1830 | - for job in self.jobs: |
1831 | - if index == tree.current_index: |
1832 | - tree.current_index = 0 |
1833 | - return (job, self) |
1834 | - else: |
1835 | - tree.current_index += 1 |
1836 | - return (None, None) |
1837 | - |
1838 | - def render(self, cols=80, as_summary=True): |
1839 | - """ |
1840 | - Return the tree as a simple list of categories and jobs suitable for |
1841 | - display. Jobs are properly indented to respect the tree hierarchy |
1842 | - and selection marks are added automatically at the beginning of each |
1843 | - element. |
1844 | - |
1845 | - The node titles should not exceed the width of a the terminal and |
1846 | - thus are cut to fit inside. |
1847 | - |
1848 | - :param cols: |
1849 | - The number of columns to render. |
1850 | - :param as_summary: |
1851 | - Whether we display the job summaries or their partial IDs. |
1852 | - """ |
1853 | - self._flat_list = [] |
1854 | - if self.expanded: |
1855 | - for category in self.categories: |
1856 | - prefix = '[ ]' |
1857 | - if category.selected: |
1858 | - prefix = '[X]' |
1859 | - line = '' |
1860 | - title = category.name |
1861 | - if category.jobs or category.categories: |
1862 | - if category.expanded: |
1863 | - line = prefix + self.depth * ' ' + ' - ' + title |
1864 | - else: |
1865 | - line = prefix + self.depth * ' ' + ' + ' + title |
1866 | - else: |
1867 | - line = prefix + self.depth * ' ' + ' ' + title |
1868 | - if len(line) > cols: |
1869 | - col_max = cols - 4 # includes len('...') + a space |
1870 | - line = line[:col_max] + '...' |
1871 | - self._flat_list.append(line) |
1872 | - self._flat_list.extend(category.render(cols, as_summary)) |
1873 | - for job in self.jobs: |
1874 | - prefix = '[ ]' |
1875 | - if self.job_selection[job]: |
1876 | - prefix = '[X]' |
1877 | - if as_summary: |
1878 | - title = job.tr_summary() |
1879 | - else: |
1880 | - title = job.partial_id |
1881 | - line = prefix + self.depth * ' ' + ' ' + title |
1882 | - if len(line) > cols: |
1883 | - col_max = cols - 4 # includes len('...') + a space |
1884 | - line = line[:col_max] + '...' |
1885 | - self._flat_list.append(line) |
1886 | - return self._flat_list |
1887 | - |
1888 | - def add_job(self, job): |
1889 | - if job.plugin == 'resource': |
1890 | - # I don't want the user to see resources but I need to keep |
1891 | - # track of them to put them in the final selection. I also |
1892 | - # don't want to add them to the tree. |
1893 | - self._resource_jobs.append(job) |
1894 | - return |
1895 | - super().add_job(job) |
1896 | - self.job_selection[job] = True |
1897 | - |
1898 | - @property |
1899 | - def selection(self): |
1900 | - """ |
1901 | - Return all the jobs currently selected |
1902 | - """ |
1903 | - self._selection_list = [] |
1904 | - for category in self.categories: |
1905 | - self._selection_list.extend(category.selection) |
1906 | - for job in self.job_selection: |
1907 | - if self.job_selection[job]: |
1908 | - self._selection_list.append(job) |
1909 | - return self._selection_list |
1910 | - |
1911 | - @property |
1912 | - def resource_jobs(self): |
1913 | - """Return all the resource jobs.""" |
1914 | - return self._resource_jobs |
1915 | - |
1916 | - def set_ancestors_state(self, new_state): |
1917 | - """ |
1918 | - Set the selection state of all ancestors consistently |
1919 | - """ |
1920 | - # If child is set, then all ancestors must be set |
1921 | - if new_state: |
1922 | - parent = self.parent |
1923 | - while parent: |
1924 | - parent.selected = new_state |
1925 | - parent = parent.parent |
1926 | - # If child is not set, then all ancestors mustn't be set |
1927 | - # unless another child of the ancestor is set |
1928 | - else: |
1929 | - parent = self.parent |
1930 | - while parent: |
1931 | - if any((category.selected |
1932 | - for category in parent.categories)): |
1933 | - break |
1934 | - if any((parent.job_selection[job] |
1935 | - for job in parent.job_selection)): |
1936 | - break |
1937 | - parent.selected = new_state |
1938 | - parent = parent.parent |
1939 | - |
1940 | - def update_selected_state(self): |
1941 | - """ |
1942 | - Update the category state according to its job selection |
1943 | - """ |
1944 | - if any((self.job_selection[job] for job in self.job_selection)): |
1945 | - self.selected = True |
1946 | - else: |
1947 | - self.selected = False |
1948 | - |
1949 | - def set_descendants_state(self, new_state): |
1950 | - """ |
1951 | - Set the selection state of all descendants recursively |
1952 | - """ |
1953 | - self.selected = new_state |
1954 | - for job in self.job_selection: |
1955 | - self.job_selection[job] = new_state |
1956 | - for category in self.categories: |
1957 | - category.set_descendants_state(new_state) |
1958 | diff --git a/checkbox_ng/test_config.py b/checkbox_ng/test_config.py |
1959 | deleted file mode 100644 |
1960 | index 33e3a24..0000000 |
1961 | --- a/checkbox_ng/test_config.py |
1962 | +++ /dev/null |
1963 | @@ -1,46 +0,0 @@ |
1964 | -# This file is part of Checkbox. |
1965 | -# |
1966 | -# Copyright 2013 Canonical Ltd. |
1967 | -# Written by: |
1968 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
1969 | -# |
1970 | -# Checkbox is free software: you can redistribute it and/or modify |
1971 | -# it under the terms of the GNU General Public License version 3, |
1972 | -# as published by the Free Software Foundation. |
1973 | - |
1974 | -# |
1975 | -# Checkbox is distributed in the hope that it will be useful, |
1976 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1977 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1978 | -# GNU General Public License for more details. |
1979 | -# |
1980 | -# You should have received a copy of the GNU General Public License |
1981 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1982 | - |
1983 | -""" |
1984 | -checkbox_ng.test_config |
1985 | -======================= |
1986 | - |
1987 | -Test definitions for checkbox_ng.config module |
1988 | -""" |
1989 | - |
1990 | -from unittest import TestCase |
1991 | - |
1992 | -from plainbox.impl.secure.config import Unset |
1993 | - |
1994 | -from checkbox_ng.config import CheckBoxConfig |
1995 | - |
1996 | - |
1997 | -class PlainBoxConfigTests(TestCase): |
1998 | - |
1999 | - def test_smoke(self): |
2000 | - config = CheckBoxConfig() |
2001 | - self.assertIs(config.secure_id, Unset) |
2002 | - secure_id = "0123456789ABCDE" |
2003 | - config.secure_id = secure_id |
2004 | - self.assertEqual(config.secure_id, secure_id) |
2005 | - with self.assertRaises(ValueError): |
2006 | - config.secure_id = "bork" |
2007 | - self.assertEqual(config.secure_id, secure_id) |
2008 | - del config.secure_id |
2009 | - self.assertIs(config.secure_id, Unset) |
2010 | diff --git a/checkbox_ng/test_main.py b/checkbox_ng/test_main.py |
2011 | deleted file mode 100644 |
2012 | index 1d2981d..0000000 |
2013 | --- a/checkbox_ng/test_main.py |
2014 | +++ /dev/null |
2015 | @@ -1,93 +0,0 @@ |
2016 | -# This file is part of Checkbox. |
2017 | -# |
2018 | -# Copyright 2012-2014 Canonical Ltd. |
2019 | -# Written by: |
2020 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
2021 | -# |
2022 | -# Checkbox is free software: you can redistribute it and/or modify |
2023 | -# it under the terms of the GNU General Public License version 3, |
2024 | -# as published by the Free Software Foundation. |
2025 | - |
2026 | -# |
2027 | -# Checkbox is distributed in the hope that it will be useful, |
2028 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2029 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2030 | -# GNU General Public License for more details. |
2031 | -# |
2032 | -# You should have received a copy of the GNU General Public License |
2033 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2034 | - |
2035 | -""" |
2036 | -checkbox_ng.test_main |
2037 | -===================== |
2038 | - |
2039 | -Test definitions for checkbox_ng.main module |
2040 | -""" |
2041 | - |
2042 | -from inspect import cleandoc |
2043 | -from unittest import TestCase |
2044 | - |
2045 | -from plainbox.impl.clitools import ToolBase |
2046 | -from plainbox.testing_utils.io import TestIO |
2047 | - |
2048 | -from checkbox_ng import __version__ as version |
2049 | -from checkbox_ng.main import main |
2050 | - |
2051 | - |
2052 | -class TestMain(TestCase): |
2053 | - |
2054 | - def test_version(self): |
2055 | - with TestIO(combined=True) as io: |
2056 | - with self.assertRaises(SystemExit) as call: |
2057 | - main(['--version']) |
2058 | - self.assertEqual(call.exception.args, (0,)) |
2059 | - self.assertEqual(io.combined, "{}\n".format(version)) |
2060 | - |
2061 | - def test_help(self): |
2062 | - with TestIO(combined=True) as io: |
2063 | - with self.assertRaises(SystemExit) as call: |
2064 | - main(['--help']) |
2065 | - self.assertEqual(call.exception.args, (0,)) |
2066 | - self.maxDiff = None |
2067 | - expected = """ |
2068 | - usage: checkbox [-h] [--version] [-v] [-D] [-C] [-T LOGGER] [-P] [-I] |
2069 | - {sru,check-config,submit,launcher,self-test} ... |
2070 | - |
2071 | - positional arguments: |
2072 | - {sru,check-config,submit,launcher,self-test} |
2073 | - sru run automated stable release update tests |
2074 | - check-config check and display plainbox configuration |
2075 | - submit submit test results to the Canonical certification |
2076 | - website |
2077 | - launcher run a customized testing session |
2078 | - self-test run unit and integration tests |
2079 | - |
2080 | - optional arguments: |
2081 | - -h, --help show this help message and exit |
2082 | - --version show program's version number and exit |
2083 | - |
2084 | - logging and debugging: |
2085 | - -v, --verbose be more verbose (same as --log-level=INFO) |
2086 | - -D, --debug enable DEBUG messages on the root logger |
2087 | - -C, --debug-console display DEBUG messages in the console |
2088 | - -T LOGGER, --trace LOGGER |
2089 | - enable DEBUG messages on the specified logger (can be |
2090 | - used multiple times) |
2091 | - -P, --pdb jump into pdb (python debugger) when a command crashes |
2092 | - -I, --debug-interrupt |
2093 | - crash on SIGINT/KeyboardInterrupt, useful with --pdb |
2094 | - """ |
2095 | - self.assertEqual(io.combined, cleandoc(expected) + "\n") |
2096 | - |
2097 | - def test_run_without_args(self): |
2098 | - with TestIO(combined=True) as io: |
2099 | - with self.assertRaises(SystemExit) as call: |
2100 | - main([]) |
2101 | - self.assertEqual(call.exception.args, (2,)) |
2102 | - expected = """ |
2103 | - usage: checkbox [-h] [--version] [-v] [-D] [-C] [-T LOGGER] [-P] [-I] |
2104 | - {sru,check-config,submit,launcher,self-test} ... |
2105 | - checkbox: error: too few arguments |
2106 | - |
2107 | - """ |
2108 | - self.assertEqual(io.combined, cleandoc(expected) + "\n") |
2109 | diff --git a/checkbox_ng/test_misc.py b/checkbox_ng/test_misc.py |
2110 | deleted file mode 100644 |
2111 | index efb26ac..0000000 |
2112 | --- a/checkbox_ng/test_misc.py |
2113 | +++ /dev/null |
2114 | @@ -1,183 +0,0 @@ |
2115 | -# This file is part of Checkbox. |
2116 | -# |
2117 | -# Copyright 2014 Canonical Ltd. |
2118 | -# Written by: |
2119 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
2120 | -# |
2121 | -# Checkbox is free software: you can redistribute it and/or modify |
2122 | -# it under the terms of the GNU General Public License version 3, |
2123 | -# as published by the Free Software Foundation. |
2124 | - |
2125 | -# |
2126 | -# Checkbox is distributed in the hope that it will be useful, |
2127 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2128 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2129 | -# GNU General Public License for more details. |
2130 | -# |
2131 | -# You should have received a copy of the GNU General Public License |
2132 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2133 | - |
2134 | -""" |
2135 | -checkbox_ng.commands.test_cli |
2136 | -============================= |
2137 | - |
2138 | -Test definitions for checkbox_ng.commands.cli module |
2139 | -""" |
2140 | - |
2141 | -from unittest import TestCase |
2142 | - |
2143 | -from plainbox.impl.session import SessionState |
2144 | -from plainbox.impl.testing_utils import make_job |
2145 | -from plainbox.impl.unit.job import JobDefinition |
2146 | - |
2147 | -from checkbox_ng.misc import JobTreeNode |
2148 | -from checkbox_ng.misc import SelectableJobTreeNode |
2149 | - |
2150 | - |
2151 | -class TestJobTreeNode(TestCase): |
2152 | - |
2153 | - def setUp(self): |
2154 | - A = make_job('A') |
2155 | - B = make_job('B', plugin='local', description='foo') |
2156 | - C = make_job('C') |
2157 | - D = make_job('D', plugin='shell') |
2158 | - E = make_job('E', plugin='local', description='bar') |
2159 | - F = make_job('F', plugin='shell') |
2160 | - G = make_job('G', plugin='local', description='baz') |
2161 | - R = make_job('R', plugin='resource') |
2162 | - Z = make_job('Z', plugin='local', description='zaz') |
2163 | - state = SessionState([A, B, C, D, E, F, G, R, Z]) |
2164 | - # D and E are a child of B |
2165 | - state.job_state_map[D.id].via_job = B |
2166 | - state.job_state_map[E.id].via_job = B |
2167 | - # F is a child of E |
2168 | - state.job_state_map[F.id].via_job = E |
2169 | - self.tree = JobTreeNode.create_tree( |
2170 | - state, [R, B, C, D, E, F, G, A, Z]) |
2171 | - |
2172 | - def test_create_tree(self): |
2173 | - self.assertIsInstance(self.tree, JobTreeNode) |
2174 | - self.assertEqual(len(self.tree.categories), 3) |
2175 | - [self.assertIsInstance(c, JobTreeNode) for c in self.tree.categories] |
2176 | - self.assertEqual(len(self.tree.jobs), 3) |
2177 | - [self.assertIsInstance(j, JobDefinition) for j in self.tree.jobs] |
2178 | - self.assertIsNone(self.tree.parent) |
2179 | - self.assertEqual(self.tree.depth, 0) |
2180 | - node = self.tree.categories[1] |
2181 | - self.assertEqual(node.name, 'foo') |
2182 | - self.assertEqual(len(node.categories), 1) |
2183 | - [self.assertIsInstance(c, JobTreeNode) for c in node.categories] |
2184 | - self.assertEqual(len(node.jobs), 1) |
2185 | - [self.assertIsInstance(j, JobDefinition) for j in node.jobs] |
2186 | - |
2187 | - |
2188 | -class TestSelectableJobTreeNode(TestCase): |
2189 | - |
2190 | - def setUp(self): |
2191 | - self.A = make_job('a', name='A') |
2192 | - self.B = make_job('b', name='B', plugin='local', description='foo') |
2193 | - self.C = make_job('c', name='C') |
2194 | - self.D = make_job('d', name='D', plugin='shell') |
2195 | - self.E = make_job('e', name='E', plugin='shell') |
2196 | - self.F = make_job('f', name='F', plugin='resource', description='baz') |
2197 | - state = SessionState([self.A, self.B, self.C, self.D, self.E, self.F]) |
2198 | - # D and E are a child of B |
2199 | - state.job_state_map[self.D.id].via_job = self.B |
2200 | - state.job_state_map[self.E.id].via_job = self.B |
2201 | - self.tree = SelectableJobTreeNode.create_tree(state, [ |
2202 | - self.A, |
2203 | - self.B, |
2204 | - self.C, |
2205 | - self.D, |
2206 | - self.E, |
2207 | - self.F |
2208 | - ]) |
2209 | - |
2210 | - def test_create_tree(self): |
2211 | - self.assertIsInstance(self.tree, SelectableJobTreeNode) |
2212 | - self.assertEqual(len(self.tree.categories), 1) |
2213 | - [self.assertIsInstance(c, SelectableJobTreeNode) |
2214 | - for c in self.tree.categories] |
2215 | - self.assertEqual(len(self.tree.jobs), 2) |
2216 | - [self.assertIsInstance(j, JobDefinition) for j in self.tree.jobs] |
2217 | - self.assertTrue(self.tree.selected) |
2218 | - [self.assertTrue(self.tree.job_selection[j]) |
2219 | - for j in self.tree.job_selection] |
2220 | - self.assertTrue(self.tree.expanded) |
2221 | - self.assertIsNone(self.tree.parent) |
2222 | - self.assertEqual(self.tree.depth, 0) |
2223 | - |
2224 | - def test_get_node_by_index(self): |
2225 | - self.assertEqual(self.tree.get_node_by_index(0)[0].name, 'foo') |
2226 | - self.assertEqual(self.tree.get_node_by_index(1)[0].name, 'D') |
2227 | - self.assertEqual(self.tree.get_node_by_index(2)[0].name, 'E') |
2228 | - self.assertEqual(self.tree.get_node_by_index(3)[0].name, 'A') |
2229 | - self.assertEqual(self.tree.get_node_by_index(4)[0].name, 'C') |
2230 | - self.assertIsNone(self.tree.get_node_by_index(5)[0]) |
2231 | - |
2232 | - def test_render(self): |
2233 | - expected = ['[X] - foo', |
2234 | - '[X] d', |
2235 | - '[X] e', |
2236 | - '[X] a', |
2237 | - '[X] c'] |
2238 | - self.assertEqual(self.tree.render(), expected) |
2239 | - |
2240 | - def test_render_deselected_all(self): |
2241 | - self.tree.set_descendants_state(False) |
2242 | - expected = ['[ ] - foo', |
2243 | - '[ ] d', |
2244 | - '[ ] e', |
2245 | - '[ ] a', |
2246 | - '[ ] c'] |
2247 | - self.assertEqual(self.tree.render(), expected) |
2248 | - |
2249 | - def test_render_reselected_all(self): |
2250 | - self.tree.set_descendants_state(False) |
2251 | - self.tree.set_descendants_state(True) |
2252 | - expected = ['[X] - foo', |
2253 | - '[X] d', |
2254 | - '[X] e', |
2255 | - '[X] a', |
2256 | - '[X] c'] |
2257 | - self.assertEqual(self.tree.render(), expected) |
2258 | - |
2259 | - def test_render_with_child_collapsed(self): |
2260 | - self.tree.categories[0].expanded = False |
2261 | - expected = ['[X] + foo', |
2262 | - '[X] a', |
2263 | - '[X] c'] |
2264 | - self.assertEqual(self.tree.render(), expected) |
2265 | - |
2266 | - def test_set_ancestors_state(self): |
2267 | - self.tree.set_descendants_state(False) |
2268 | - node = self.tree.categories[0] |
2269 | - node.job_selection[self.E] = True |
2270 | - node.update_selected_state() |
2271 | - node.set_ancestors_state(node.selected) |
2272 | - expected = ['[X] - foo', |
2273 | - '[ ] d', |
2274 | - '[X] e', |
2275 | - '[ ] a', |
2276 | - '[ ] c'] |
2277 | - self.assertEqual(self.tree.render(), expected) |
2278 | - node.selected = not(node.selected) |
2279 | - node.set_ancestors_state(node.selected) |
2280 | - node.set_descendants_state(node.selected) |
2281 | - expected = ['[ ] - foo', |
2282 | - '[ ] d', |
2283 | - '[ ] e', |
2284 | - '[ ] a', |
2285 | - '[ ] c'] |
2286 | - self.assertEqual(self.tree.render(), expected) |
2287 | - |
2288 | - def test_selection(self): |
2289 | - self.tree.set_descendants_state(False) |
2290 | - node = self.tree.categories[0] |
2291 | - node.job_selection[self.D] = True |
2292 | - node.update_selected_state() |
2293 | - node.set_ancestors_state(node.selected) |
2294 | - # Note that in addition to the selected (D) test, we need the |
2295 | - # tree selection to contain the resource (F), even though the |
2296 | - # user never saw it in the previous tests for visual presentation. |
2297 | - self.assertEqual(self.tree.selection, [self.D]) |
2298 | diff --git a/checkbox_ng/tools.py b/checkbox_ng/tools.py |
2299 | deleted file mode 100644 |
2300 | index 1aa8f37..0000000 |
2301 | --- a/checkbox_ng/tools.py |
2302 | +++ /dev/null |
2303 | @@ -1,146 +0,0 @@ |
2304 | -# This file is part of Checkbox. |
2305 | -# |
2306 | -# Copyright 2012-2015 Canonical Ltd. |
2307 | -# Written by: |
2308 | -# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
2309 | -# |
2310 | -# Checkbox is free software: you can redistribute it and/or modify |
2311 | -# it under the terms of the GNU General Public License version 3, |
2312 | -# as published by the Free Software Foundation. |
2313 | -# |
2314 | -# Checkbox is distributed in the hope that it will be useful, |
2315 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2316 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2317 | -# GNU General Public License for more details. |
2318 | -# |
2319 | -# You should have received a copy of the GNU General Public License |
2320 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2321 | - |
2322 | -""" |
2323 | -:mod:`checkbox_ng.tools` -- top-level command line tools |
2324 | -======================================================== |
2325 | -""" |
2326 | - |
2327 | -import logging |
2328 | -import os |
2329 | - |
2330 | -from plainbox.impl.clitools import SingleCommandToolMixIn |
2331 | -from plainbox.impl.clitools import ToolBase |
2332 | -from plainbox.impl.commands.cmd_selftest import SelfTestCommand |
2333 | -from plainbox.public import get_providers |
2334 | - |
2335 | -from checkbox_ng import __version__ as version |
2336 | -from checkbox_ng.config import CheckBoxConfig |
2337 | -from checkbox_ng.tests import load_unit_tests |
2338 | - |
2339 | - |
2340 | -logger = logging.getLogger("checkbox.ng.tools") |
2341 | - |
2342 | - |
2343 | -class CheckboxToolBase(ToolBase): |
2344 | - """ |
2345 | - Base class for all checkbox-ng tools. |
2346 | - |
2347 | - This class contains some shared code like configuration, providers, i18n |
2348 | - and version handling. |
2349 | - """ |
2350 | - |
2351 | - def _load_config(self): |
2352 | - return self.get_config_cls().get() |
2353 | - |
2354 | - def _load_providers(self): |
2355 | - return get_providers() |
2356 | - |
2357 | - @classmethod |
2358 | - def get_exec_version(cls): |
2359 | - """ |
2360 | - Get the version of the checkbox-ng package |
2361 | - """ |
2362 | - return version |
2363 | - |
2364 | - @classmethod |
2365 | - def get_config_cls(cls): |
2366 | - """ |
2367 | - Get particular sub-class of the Config class to use |
2368 | - """ |
2369 | - return CheckBoxConfig |
2370 | - |
2371 | - def get_gettext_domain(self): |
2372 | - """ |
2373 | - Get the 'checkbox-ng' gettext domain |
2374 | - """ |
2375 | - return "checkbox-ng" |
2376 | - |
2377 | - def get_locale_dir(self): |
2378 | - """ |
2379 | - Get an optional development locale directory specific to checkbox-ng |
2380 | - """ |
2381 | - return os.getenv("CHECKBOX_NG_LOCALE_DIR", None) |
2382 | - |
2383 | - |
2384 | -class CheckboxTool(CheckboxToolBase): |
2385 | - """ |
2386 | - Tool that implements the new checkbox command. |
2387 | - |
2388 | - This tool has two sub-commands: |
2389 | - |
2390 | - checkbox sru - to run stable release update testing |
2391 | - checkbox check-config - to validate and display system configuration |
2392 | - """ |
2393 | - |
2394 | - @classmethod |
2395 | - def get_exec_name(cls): |
2396 | - return "checkbox" |
2397 | - |
2398 | - def add_subcommands(self, subparsers, early_ns=None): |
2399 | - from checkbox_ng.commands.launcher import LauncherCommand |
2400 | - from checkbox_ng.commands.sru import SRUCommand |
2401 | - from checkbox_ng.commands.submit import SubmitCommand |
2402 | - from plainbox.impl.commands.cmd_check_config import CheckConfigCommand |
2403 | - SRUCommand( |
2404 | - self._load_providers, self._load_config |
2405 | - ).register_parser(subparsers) |
2406 | - CheckConfigCommand( |
2407 | - self._load_config |
2408 | - ).register_parser(subparsers) |
2409 | - SubmitCommand( |
2410 | - self._load_config |
2411 | - ).register_parser(subparsers) |
2412 | - LauncherCommand( |
2413 | - self._load_providers, self._load_config |
2414 | - ).register_parser(subparsers) |
2415 | - SelfTestCommand(load_unit_tests).register_parser(subparsers) |
2416 | - |
2417 | - |
2418 | -class CheckboxSubmitTool(SingleCommandToolMixIn, CheckboxToolBase): |
2419 | - """ |
2420 | - A tool class that implements checkbox-submit. |
2421 | - |
2422 | - This tool implements the submit feature to send test results to the |
2423 | - Canonical certification website |
2424 | - """ |
2425 | - |
2426 | - @classmethod |
2427 | - def get_exec_name(cls): |
2428 | - return "checkbox-submit" |
2429 | - |
2430 | - def get_command(self): |
2431 | - from checkbox_ng.commands.submit import SubmitCommand |
2432 | - return SubmitCommand(self._load_config) |
2433 | - |
2434 | - |
2435 | -class CheckboxLauncherTool(SingleCommandToolMixIn, CheckboxToolBase): |
2436 | - """ |
2437 | - A tool class that implements checkbox-launcher. |
2438 | - |
2439 | - This tool implements configurable text-mode-graphics launchers that perform |
2440 | - a pre-defined testing session based on the launcher profile. |
2441 | - """ |
2442 | - |
2443 | - @classmethod |
2444 | - def get_exec_name(cls): |
2445 | - return "checkbox-launcher" |
2446 | - |
2447 | - def get_command(self): |
2448 | - from checkbox_ng.commands.launcher import LauncherCommand |
2449 | - return LauncherCommand(self._load_providers, self._load_config) |
2450 | diff --git a/checkbox_ng/ui.py b/checkbox_ng/ui.py |
2451 | deleted file mode 100644 |
2452 | index 322c522..0000000 |
2453 | --- a/checkbox_ng/ui.py |
2454 | +++ /dev/null |
2455 | @@ -1,363 +0,0 @@ |
2456 | -# This file is part of Checkbox. |
2457 | -# |
2458 | -# Copyright 2013-2015 Canonical Ltd. |
2459 | -# Written by: |
2460 | -# Sylvain Pineau <sylvain.pineau@canonical.com> |
2461 | -# |
2462 | -# Checkbox is free software: you can redistribute it and/or modify |
2463 | -# it under the terms of the GNU General Public License version 3, |
2464 | -# as published by the Free Software Foundation. |
2465 | - |
2466 | -# |
2467 | -# Checkbox is distributed in the hope that it will be useful, |
2468 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2469 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2470 | -# GNU General Public License for more details. |
2471 | -# |
2472 | -# You should have received a copy of the GNU General Public License |
2473 | -# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2474 | - |
2475 | -""" |
2476 | -:mod:`checkbox_ng.ui` -- user interface elements |
2477 | -================================================ |
2478 | -""" |
2479 | - |
2480 | -from gettext import gettext as _ |
2481 | -from logging import getLogger |
2482 | -import textwrap |
2483 | - |
2484 | -from plainbox.vendor.textland import DrawingContext |
2485 | -from plainbox.vendor.textland import EVENT_KEYBOARD |
2486 | -from plainbox.vendor.textland import EVENT_RESIZE |
2487 | -from plainbox.vendor.textland import Event |
2488 | -from plainbox.vendor.textland import IApplication |
2489 | -from plainbox.vendor.textland import Size |
2490 | -from plainbox.vendor.textland import TextImage |
2491 | -from plainbox.vendor.textland import NORMAL, REVERSE |
2492 | - |
2493 | - |
2494 | -logger = getLogger("checkbox.ng.ui") |
2495 | - |
2496 | - |
2497 | -class ShowWelcome(IApplication): |
2498 | - """ |
2499 | - Display a welcome message |
2500 | - """ |
2501 | - def __init__(self, text): |
2502 | - self.image = TextImage(Size(0, 0)) |
2503 | - self.text = text |
2504 | - |
2505 | - def consume_event(self, event: Event): |
2506 | - if event.kind == EVENT_RESIZE: |
2507 | - self.image = TextImage(event.data) # data is the new size |
2508 | - elif event.kind == EVENT_KEYBOARD and event.data.key == "enter": |
2509 | - raise StopIteration |
2510 | - self.repaint(event) |
2511 | - return self.image |
2512 | - |
2513 | - def repaint(self, event: Event): |
2514 | - ctx = DrawingContext(self.image) |
2515 | - i = 0 |
2516 | - ctx.border() |
2517 | - for paragraph in self.text.splitlines(): |
2518 | - i += 1 |
2519 | - for line in textwrap.fill( |
2520 | - paragraph, |
2521 | - self.image.size.width - 8, |
2522 | - replace_whitespace=False).splitlines(): |
2523 | - ctx.move_to(4, i) |
2524 | - ctx.print(line) |
2525 | - i += 1 |
2526 | - ctx.move_to(4, i + 1) |
2527 | - ctx.attributes.style = REVERSE |
2528 | - ctx.print(_("< Continue >")) |
2529 | - |
2530 | - |
2531 | -class ShowMenu(IApplication): |
2532 | - """ |
2533 | - Display the appropriate menu and return the selected options |
2534 | - """ |
2535 | - def __init__(self, title, menu, selection=[0], multiple_allowed=True): |
2536 | - self.image = TextImage(Size(0, 0)) |
2537 | - self.title = title |
2538 | - self.menu = menu |
2539 | - self.option_count = len(menu) |
2540 | - self.position = 0 # Zero-based index of the selected menu option |
2541 | - self.multiple_allowed = multiple_allowed |
2542 | - if self.option_count: |
2543 | - self.selection = selection |
2544 | - else: |
2545 | - self.selection = [] |
2546 | - |
2547 | - def consume_event(self, event: Event): |
2548 | - if event.kind == EVENT_RESIZE: |
2549 | - self.image = TextImage(event.data) # data is the new size |
2550 | - elif event.kind == EVENT_KEYBOARD: |
2551 | - if event.data.key == "down": |
2552 | - if self.position < self.option_count: |
2553 | - self.position += 1 |
2554 | - else: |
2555 | - self.position = 0 |
2556 | - elif event.data.key == "up": |
2557 | - if self.position > 0: |
2558 | - self.position -= 1 |
2559 | - else: |
2560 | - self.position = self.option_count |
2561 | - elif (event.data.key == "enter" and |
2562 | - self.position == self.option_count): |
2563 | - raise StopIteration(self.selection) |
2564 | - elif event.data.key == "space": |
2565 | - if self.position in self.selection: |
2566 | - self.selection.remove(self.position) |
2567 | - elif self.position < self.option_count: |
2568 | - self.selection.append(self.position) |
2569 | - if not self.multiple_allowed: |
2570 | - self.selection = [self.position] |
2571 | - self.repaint(event) |
2572 | - return self.image |
2573 | - |
2574 | - def repaint(self, event: Event): |
2575 | - ctx = DrawingContext(self.image) |
2576 | - ctx.border(tm=1) |
2577 | - ctx.attributes.style = REVERSE |
2578 | - ctx.print(' ' * self.image.size.width) |
2579 | - ctx.move_to(1, 0) |
2580 | - ctx.print(self.title) |
2581 | - |
2582 | - # Display all the menu items |
2583 | - for i in range(self.option_count): |
2584 | - ctx.attributes.style = NORMAL |
2585 | - if i == self.position: |
2586 | - ctx.attributes.style = REVERSE |
2587 | - # Display options from line 3, column 4 |
2588 | - ctx.move_to(4, 3 + i) |
2589 | - ctx.print("[{}] - {}".format( |
2590 | - 'X' if i in self.selection else ' ', |
2591 | - self.menu[i].replace('ihv-', ''))) |
2592 | - |
2593 | - # Display "OK" at bottom of menu |
2594 | - ctx.attributes.style = NORMAL |
2595 | - if self.position == self.option_count: |
2596 | - ctx.attributes.style = REVERSE |
2597 | - # Add an empty line before the last option |
2598 | - ctx.move_to(4, 4 + self.option_count) |
2599 | - ctx.print("< OK >") |
2600 | - |
2601 | - |
2602 | -class ScrollableTreeNode(IApplication): |
2603 | - """ |
2604 | - Class used to interact with a SelectableJobTreeNode |
2605 | - """ |
2606 | - def __init__(self, tree, title): |
2607 | - self.image = TextImage(Size(0, 0)) |
2608 | - self.tree = tree |
2609 | - self.title = title |
2610 | - self.top = 0 # Top line number |
2611 | - self.highlight = 0 # Highlighted line number |
2612 | - self.summary = True |
2613 | - |
2614 | - def consume_event(self, event: Event): |
2615 | - if event.kind == EVENT_RESIZE: |
2616 | - self.image = TextImage(event.data) # data is the new size |
2617 | - elif event.kind == EVENT_KEYBOARD: |
2618 | - self.image = TextImage(self.image.size) |
2619 | - if event.data.key == "up": |
2620 | - self._scroll("up") |
2621 | - elif event.data.key == "down": |
2622 | - self._scroll("down") |
2623 | - elif event.data.key == "space": |
2624 | - self._selectNode() |
2625 | - elif event.data.key == "enter": |
2626 | - self._toggleNode() |
2627 | - elif event.data.key in 'sS': |
2628 | - self.tree.set_descendants_state(True) |
2629 | - elif event.data.key in 'dD': |
2630 | - self.tree.set_descendants_state(False) |
2631 | - elif event.data.key in 'iI': |
2632 | - self.summary = not self.summary |
2633 | - elif event.data.key in 'tT': |
2634 | - raise StopIteration |
2635 | - self.repaint(event) |
2636 | - return self.image |
2637 | - |
2638 | - def repaint(self, event: Event): |
2639 | - ctx = DrawingContext(self.image) |
2640 | - ctx.border(tm=1, bm=1) |
2641 | - cols = self.image.size.width |
2642 | - extra_cols = 0 |
2643 | - if cols > 80: |
2644 | - extra_cols = cols - 80 |
2645 | - ctx.attributes.style = REVERSE |
2646 | - ctx.print(' ' * cols) |
2647 | - ctx.move_to(1, 0) |
2648 | - bottom = self.top + self.image.size.height - 4 |
2649 | - ctx.print(self.title) |
2650 | - ctx.move_to(1, self.image.size.height - 1) |
2651 | - ctx.attributes.style = REVERSE |
2652 | - ctx.print(_("Enter")) |
2653 | - ctx.move_to(6, self.image.size.height - 1) |
2654 | - ctx.attributes.style = NORMAL |
2655 | - ctx.print(_(": Expand/Collapse")) |
2656 | - ctx.move_to(27, self.image.size.height - 1) |
2657 | - ctx.attributes.style = REVERSE |
2658 | - # FIXME: i18n problem |
2659 | - ctx.print("S") |
2660 | - ctx.move_to(28, self.image.size.height - 1) |
2661 | - ctx.attributes.style = NORMAL |
2662 | - ctx.print("elect All") |
2663 | - ctx.move_to(41, self.image.size.height - 1) |
2664 | - ctx.attributes.style = REVERSE |
2665 | - # FIXME: i18n problem |
2666 | - ctx.print("D") |
2667 | - ctx.move_to(42, self.image.size.height - 1) |
2668 | - ctx.attributes.style = NORMAL |
2669 | - ctx.print("eselect All") |
2670 | - ctx.move_to(66 + extra_cols, self.image.size.height - 1) |
2671 | - ctx.print(_("Start ")) |
2672 | - ctx.move_to(72 + extra_cols, self.image.size.height - 1) |
2673 | - ctx.attributes.style = REVERSE |
2674 | - # FIXME: i18n problem |
2675 | - ctx.print("T") |
2676 | - ctx.move_to(73 + extra_cols, self.image.size.height - 1) |
2677 | - ctx.attributes.style = NORMAL |
2678 | - ctx.print("esting") |
2679 | - for i, line in enumerate(self.tree.render(cols - 3, |
2680 | - as_summary=self.summary)[self.top:bottom]): |
2681 | - ctx.move_to(2, i + 2) |
2682 | - if i != self.highlight: |
2683 | - ctx.attributes.style = NORMAL |
2684 | - else: # highlight the current line |
2685 | - ctx.attributes.style = REVERSE |
2686 | - ctx.print(line) |
2687 | - |
2688 | - def _selectNode(self): |
2689 | - """ |
2690 | - Mark a node/job as selected for this test run. |
2691 | - See :meth:`SelectableJobTreeNode.set_ancestors_state()` and |
2692 | - :meth:`SelectableJobTreeNode.set_descendants_state()` for details |
2693 | - about the automatic selection of parents and descendants. |
2694 | - """ |
2695 | - node, category = self.tree.get_node_by_index(self.top + self.highlight) |
2696 | - if category: # then the selected node is a job not a category |
2697 | - job = node |
2698 | - category.job_selection[job] = not(category.job_selection[job]) |
2699 | - category.update_selected_state() |
2700 | - category.set_ancestors_state(category.job_selection[job]) |
2701 | - else: |
2702 | - node.selected = not(node.selected) |
2703 | - node.set_descendants_state(node.selected) |
2704 | - node.set_ancestors_state(node.selected) |
2705 | - |
2706 | - def _toggleNode(self): |
2707 | - """ |
2708 | - Expand/collapse a node |
2709 | - """ |
2710 | - node, is_job = self.tree.get_node_by_index(self.top + self.highlight) |
2711 | - if node is not None and not is_job: |
2712 | - node.expanded = not(node.expanded) |
2713 | - |
2714 | - def _scroll(self, direction): |
2715 | - visible_length = len(self.tree) |
2716 | - # Scroll the tree view |
2717 | - if (direction == "up" and |
2718 | - self.highlight == 0 and self.top != 0): |
2719 | - self.top -= 1 |
2720 | - return |
2721 | - elif (direction == "down" and |
2722 | - (self.highlight + 1) == (self.image.size.height - 4) and |
2723 | - (self.top + self.image.size.height - 4) != visible_length): |
2724 | - self.top += 1 |
2725 | - return |
2726 | - # Move the highlighted line |
2727 | - if (direction == "up" and |
2728 | - (self.top != 0 or self.highlight != 0)): |
2729 | - self.highlight -= 1 |
2730 | - elif (direction == "down" and |
2731 | - (self.top + self.highlight + 1) != visible_length and |
2732 | - (self.highlight + 1) != (self.image.size.height - 4)): |
2733 | - self.highlight += 1 |
2734 | - |
2735 | - |
2736 | -class ShowRerun(ScrollableTreeNode): |
2737 | - """ Display the re-run screen.""" |
2738 | - def __init__(self, tree, title): |
2739 | - super().__init__(tree, title) |
2740 | - |
2741 | - def consume_event(self, event: Event): |
2742 | - if event.kind == EVENT_RESIZE: |
2743 | - self.image = TextImage(event.data) # data is the new size |
2744 | - elif event.kind == EVENT_KEYBOARD: |
2745 | - self.image = TextImage(self.image.size) |
2746 | - if event.data.key == "up": |
2747 | - self._scroll("up") |
2748 | - elif event.data.key == "down": |
2749 | - self._scroll("down") |
2750 | - elif event.data.key == "space": |
2751 | - self._selectNode() |
2752 | - elif event.data.key == "enter": |
2753 | - self._toggleNode() |
2754 | - elif event.data.key in 'sS': |
2755 | - self.tree.set_descendants_state(True) |
2756 | - elif event.data.key in 'dD': |
2757 | - self.tree.set_descendants_state(False) |
2758 | - elif event.data.key in 'fF': |
2759 | - self.tree.set_descendants_state(False) |
2760 | - raise StopIteration |
2761 | - elif event.data.key in 'rR': |
2762 | - raise StopIteration |
2763 | - self.repaint(event) |
2764 | - return self.image |
2765 | - |
2766 | - def repaint(self, event: Event): |
2767 | - ctx = DrawingContext(self.image) |
2768 | - ctx.border(tm=1, bm=1) |
2769 | - cols = self.image.size.width |
2770 | - extra_cols = 0 |
2771 | - if cols > 80: |
2772 | - extra_cols = cols - 80 |
2773 | - ctx.attributes.style = REVERSE |
2774 | - ctx.print(' ' * cols) |
2775 | - ctx.move_to(1, 0) |
2776 | - bottom = self.top + self.image.size.height - 4 |
2777 | - ctx.print(self.title) |
2778 | - ctx.move_to(1, self.image.size.height - 1) |
2779 | - ctx.attributes.style = REVERSE |
2780 | - ctx.print(_("Enter")) |
2781 | - ctx.move_to(6, self.image.size.height - 1) |
2782 | - ctx.attributes.style = NORMAL |
2783 | - ctx.print(_(": Expand/Collapse")) |
2784 | - ctx.move_to(27, self.image.size.height - 1) |
2785 | - ctx.attributes.style = REVERSE |
2786 | - # FIXME: i18n problem |
2787 | - ctx.print("S") |
2788 | - ctx.move_to(28, self.image.size.height - 1) |
2789 | - ctx.attributes.style = NORMAL |
2790 | - ctx.print("elect All") |
2791 | - ctx.move_to(41, self.image.size.height - 1) |
2792 | - ctx.attributes.style = REVERSE |
2793 | - # FIXME: i18n problem |
2794 | - ctx.print("D") |
2795 | - ctx.move_to(42, self.image.size.height - 1) |
2796 | - ctx.attributes.style = NORMAL |
2797 | - ctx.print("eselect All") |
2798 | - ctx.move_to(63 + extra_cols, self.image.size.height - 1) |
2799 | - ctx.attributes.style = REVERSE |
2800 | - # FIXME: i18n problem |
2801 | - ctx.print("F") |
2802 | - ctx.move_to(64 + extra_cols, self.image.size.height - 1) |
2803 | - ctx.attributes.style = NORMAL |
2804 | - ctx.print(_("inish")) |
2805 | - ctx.move_to(73 + extra_cols, self.image.size.height - 1) |
2806 | - ctx.attributes.style = REVERSE |
2807 | - # FIXME: i18n problem |
2808 | - ctx.print("R") |
2809 | - ctx.move_to(74 + extra_cols, self.image.size.height - 1) |
2810 | - ctx.attributes.style = NORMAL |
2811 | - ctx.print("e-run") |
2812 | - for i, line in enumerate(self.tree.render(cols - 3)[self.top:bottom]): |
2813 | - ctx.move_to(2, i + 2) |
2814 | - if i != self.highlight: |
2815 | - ctx.attributes.style = NORMAL |
2816 | - else: # highlight the current line |
2817 | - ctx.attributes.style = REVERSE |
2818 | - ctx.print(line) |
2819 | diff --git a/po/POTFILES.in b/po/POTFILES.in |
2820 | index c9804d5..8934fc2 100644 |
2821 | --- a/po/POTFILES.in |
2822 | +++ b/po/POTFILES.in |
2823 | @@ -1,19 +1,13 @@ |
2824 | [encoding: UTF-8] |
2825 | +./docs/conf.py |
2826 | ./checkbox_ng/__init__.py |
2827 | ./checkbox_ng/certification.py |
2828 | -./checkbox_ng/commands/__init__.py |
2829 | -./checkbox_ng/commands/cli.py |
2830 | -./checkbox_ng/commands/launcher.py |
2831 | -./checkbox_ng/commands/newcli.py |
2832 | -./checkbox_ng/commands/sru.py |
2833 | -./checkbox_ng/commands/submit.py |
2834 | -./checkbox_ng/commands/test_sru.py |
2835 | ./checkbox_ng/config.py |
2836 | -./checkbox_ng/launchpad.py |
2837 | -./checkbox_ng/main.py |
2838 | +./checkbox_ng/launcher/__init__.py |
2839 | +./checkbox_ng/launcher/checkbox_cli.py |
2840 | +./checkbox_ng/launcher/stages.py |
2841 | +./checkbox_ng/launcher/subcommands.py |
2842 | ./checkbox_ng/test_certification.py |
2843 | -./checkbox_ng/test_config.py |
2844 | -./checkbox_ng/test_main.py |
2845 | -./checkbox_ng/test_misc.py |
2846 | ./checkbox_ng/tests.py |
2847 | -./checkbox_ng/ui.py |
2848 | +./checkbox_ng/urwid_ui.py |
2849 | +./setup.py |
2850 | diff --git a/setup.py b/setup.py |
2851 | index b6226bf..f4ddbf5 100755 |
2852 | --- a/setup.py |
2853 | +++ b/setup.py |
2854 | @@ -68,9 +68,6 @@ setup( |
2855 | entry_points={ |
2856 | 'console_scripts': [ |
2857 | 'checkbox-cli=checkbox_ng.launcher.checkbox_cli:main', |
2858 | - 'checkbox=checkbox_ng.main:main', |
2859 | - 'checkbox-submit=checkbox_ng.main:submit', |
2860 | - 'checkbox-launcher=checkbox_ng.main:launcher', |
2861 | ], |
2862 | 'plainbox.transport': [ |
2863 | 'certification=' |
As of 61decd1 commit everything looks (and works) good.
I wonder if there are any configurations using the old syntax... Guess we'll learn after this lands :-)