Merge lp:~sylvain-pineau/checkbox/fix-1577831 into lp:checkbox

Proposed by Sylvain Pineau
Status: Merged
Approved by: Sylvain Pineau
Approved revision: 4355
Merged at revision: 4366
Proposed branch: lp:~sylvain-pineau/checkbox/fix-1577831
Merge into: lp:checkbox
Diff against target: 657 lines (+17/-603)
3 files modified
checkbox-ng/launchers/checkbox-cli (+4/-3)
checkbox-ng/launchers/checkbox-cli2 (+0/-596)
plainbox/plainbox/impl/session/restart.py (+13/-4)
To merge this branch: bzr merge lp:~sylvain-pineau/checkbox/fix-1577831
Reviewer Review Type Date Requested Status
Paul Larson Approve
Sylvain Pineau (community) Needs Resubmitting
Review via email: mp+294903@code.launchpad.net

Description of the change

Fixes the linked bug, updating the snappy restart strategy to comply with s16 and kill checkbox-cli2 then just requesting a binary called checkbox-cli in the snapcraft.yaml (less binaries is less confusing for end-users)

To post a comment you must log in.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

Ready for review, heavily tested on a snappy vivid system running stress tests reboot loops.

review: Needs Resubmitting
Revision history for this message
Paul Larson (pwlars) wrote :

One really minor comment below, feel free to take it or leave it. Otherwise looks fine to me, and love the removeal of the extra launcher with the name that didn't make it's purpose clear :)

review: Approve
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

Thanks for the review, adiós checkbox-cli2

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'checkbox-ng/launchers/checkbox-cli'
2--- checkbox-ng/launchers/checkbox-cli 2016-05-18 08:38:44 +0000
3+++ checkbox-ng/launchers/checkbox-cli 2016-05-19 19:44:13 +0000
4@@ -717,10 +717,11 @@
5 'submissions/submit/')
6 secure_id = self.launcher.transports[transport].get(
7 'secure_id', None)
8- if not secure_id:
9+ if not secure_id and self.is_interactive:
10 secure_id = input(self.C.BLUE(_('Enter secure-id:')))
11- options = "secure_id={}".format(secure_id)
12- self.transports[transport] = cls(url, options)
13+ if secure_id:
14+ options = "secure_id={}".format(secure_id)
15+ self.transports[transport] = cls(url, options)
16
17 def _export_results(self):
18 for report in self.launcher.stock_reports:
19
20=== removed file 'checkbox-ng/launchers/checkbox-cli2'
21--- checkbox-ng/launchers/checkbox-cli2 2015-12-14 18:15:22 +0000
22+++ checkbox-ng/launchers/checkbox-cli2 1970-01-01 00:00:00 +0000
23@@ -1,596 +0,0 @@
24-#!/usr/bin/env python3
25-# This file is part of Checkbox.
26-#
27-# Copyright 2015 Canonical Ltd.
28-# Written by:
29-# Sylvain Pineau <sylvain.pineau@canonical.com>
30-#
31-# Checkbox is free software: you can redistribute it and/or modify
32-# it under the terms of the GNU General Public License version 3,
33-# as published by the Free Software Foundation.
34-#
35-# Checkbox is distributed in the hope that it will be useful,
36-# but WITHOUT ANY WARRANTY; without even the implied warranty of
37-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38-# GNU General Public License for more details.
39-#
40-# You should have received a copy of the GNU General Public License
41-# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
42-
43-"""
44-Checkbox CLI Application.
45-
46-WARNING: this is not a launcher interpreter.
47-"""
48-
49-from argparse import SUPPRESS
50-from shutil import copyfileobj
51-import gettext
52-import io
53-import json
54-import logging
55-import os
56-import sys
57-
58-from guacamole import Command
59-from guacamole.core import Ingredient
60-from guacamole.ingredients import ansi
61-from guacamole.ingredients import argparse
62-from guacamole.ingredients import cmdtree
63-from guacamole.recipes.cmd import CommandRecipe
64-
65-# TODO: use public APIs here
66-from plainbox.abc import IJobResult
67-from plainbox.i18n import ngettext
68-from plainbox.i18n import pgettext as C_
69-from plainbox.impl.commands.inv_run import seconds_to_human_duration
70-from plainbox.impl.commands.inv_run import Action
71-from plainbox.impl.commands.inv_run import ActionUI
72-from plainbox.impl.commands.inv_run import NormalUI
73-from plainbox.impl.commands.inv_run import ReRunJob
74-from plainbox.impl.color import Colorizer
75-from plainbox.impl.exporter import ByteStringStreamTranslator
76-from plainbox.impl.ingredients import CanonicalCrashIngredient
77-from plainbox.impl.ingredients import RenderingContextIngredient
78-from plainbox.impl.ingredients import SessionAssistantIngredient
79-from plainbox.impl.result import tr_outcome
80-from plainbox.impl.result import JobResultBuilder
81-from plainbox.impl.result import MemoryJobResult
82-from plainbox.impl.session.assistant import SA_RESTARTABLE
83-from plainbox.impl.session.jobs import InhibitionCause
84-from plainbox.vendor.textland import get_display
85-
86-from checkbox_ng.misc import SelectableJobTreeNode
87-from checkbox_ng.ui import ScrollableTreeNode
88-from checkbox_ng.ui import ShowMenu
89-from checkbox_ng.ui import ShowRerun
90-
91-
92-_ = gettext.gettext
93-
94-_logger = logging.getLogger("checkbox-cli")
95-
96-
97-class DisplayIngredient(Ingredient):
98-
99- """Ingredient that adds a Textland display to guacamole."""
100-
101- def late_init(self, context):
102- """Add a DisplayIngredient as ``display`` to the guacamole context."""
103- context.display = get_display()
104-
105-
106-class CheckboxCommandRecipe(CommandRecipe):
107-
108- """A recipe for using Checkbox-enhanced commands."""
109-
110- def get_ingredients(self):
111- """Get a list of ingredients for guacamole."""
112- return [
113- cmdtree.CommandTreeBuilder(self.command),
114- cmdtree.CommandTreeDispatcher(),
115- argparse.ParserIngredient(),
116- CanonicalCrashIngredient(),
117- ansi.ANSIIngredient(),
118- SessionAssistantIngredient(),
119- RenderingContextIngredient(),
120- DisplayIngredient()
121- ]
122-
123-
124-class CheckboxUI(NormalUI):
125-
126- def considering_job(self, job, job_state):
127- pass
128-
129-
130-class CheckboxCommand(Command):
131-
132- """
133- A command with Checkbox-enhanced ingredients.
134-
135- This command has two additional items in the guacamole execution context,
136- the :class:`DisplayIngredient` object ``display`` and the
137- :class:`SessionAssistant` object ``sa``.
138- """
139-
140- bug_report_url = "https://bugs.launchpad.net/checkbox-ng/+filebug"
141-
142- def main(self, argv=None, exit=True):
143- """
144- Shortcut for running a command.
145-
146- See :meth:`guacamole.recipes.Recipe.main()` for details.
147- """
148- return CheckboxCommandRecipe(self).main(argv, exit)
149-
150-
151-class checkbox_cli(CheckboxCommand):
152-
153- """Tool to run Checkbox jobs interactively from the command line."""
154-
155- app_id = 'checkbox-cli'
156-
157- def get_sa_api_version(self):
158- return '0.99'
159-
160- def get_sa_api_flags(self):
161- return (SA_RESTARTABLE,)
162-
163- def register_arguments(self, parser):
164- """Method called to register command line arguments."""
165- parser.add_argument(
166- '-t', '--test-plan', action="store", metavar=_("TEST-PLAN-ID"),
167- default=None,
168- # TRANSLATORS: this is in imperative form
169- help=_("load the specified test plan"))
170- parser.add_argument(
171- '--secure_id', metavar="SECURE_ID",
172- help=_("Canonical hardware identifier (optional)"))
173- parser.add_argument(
174- '--non-interactive', action='store_true',
175- help=_("skip tests that require interactivity"))
176- parser.add_argument(
177- '--dont-suppress-output', action="store_true", default=False,
178- help=_("don't suppress the output of certain job plugin types"))
179- parser.add_argument(
180- '--staging', action='store_true', default=False,
181- # Hide staging from help message (See pad.lv/1350005)
182- help=SUPPRESS)
183- parser.add_argument(
184- '--resume', dest='session_id', metavar="SESSION_ID",
185- help=SUPPRESS)
186-
187- def invoked(self, ctx):
188- """Method called when the command is invoked."""
189- self.ctx = ctx
190- self.transport = self._create_transport()
191- self.C = Colorizer()
192- try:
193- self._do_normal_sequence()
194- self._export_results()
195- if ctx.args.secure_id:
196- self._send_results()
197- ctx.sa.finalize_session()
198- except KeyboardInterrupt:
199- return 1
200-
201- def _export_results(self):
202- if self.is_interactive:
203- print(self.C.header(_("Results")))
204- # This requires a bit more finesse, as exporters output bytes
205- # and stdout needs a string.
206- translating_stream = ByteStringStreamTranslator(
207- sys.stdout, "utf-8")
208- self.ctx.sa.export_to_stream(
209- '2013.com.canonical.plainbox::text', (), translating_stream)
210- base_dir = os.path.join(
211- os.getenv(
212- 'XDG_DATA_HOME', os.path.expanduser("~/.local/share/")),
213- "checkbox-ng")
214- if not os.path.exists(base_dir):
215- os.makedirs(base_dir)
216- exp_options = ['with-sys-info', 'with-summary', 'with-job-description',
217- 'with-text-attachments', 'with-certification-status',
218- 'with-job-defs', 'with-io-log', 'with-comments']
219- exporters = [
220- '2013.com.canonical.plainbox::hexr',
221- '2013.com.canonical.plainbox::html',
222- '2013.com.canonical.plainbox::xlsx',
223- '2013.com.canonical.plainbox::json',
224- ]
225- print()
226- for unit_name in exporters:
227- results_path = self.ctx.sa.export_to_file(unit_name, exp_options,
228- base_dir)
229- print("file://{}".format(results_path))
230-
231- def _send_results(self):
232- print()
233- print(_("Sending hardware report to Canonical Certification"))
234- print(_("Server URL is: {0}").format(self.transport.url))
235- result = self.ctx.sa.export_to_transport(
236- "2013.com.canonical.plainbox::hexr", self.transport)
237- if 'url' in result:
238- print(result['url'])
239-
240- def _create_transport(self):
241- if self.ctx.args.secure_id:
242- return self.ctx.sa.get_canonical_certification_transport(
243- self.ctx.args.secure_id, staging=self.ctx.args.staging)
244-
245- def _get_interactively_picked_testplans(self):
246- test_plan_ids = self.ctx.sa.get_test_plans()
247- test_plan_names = [self.ctx.sa.get_test_plan(tp_id).name for tp_id in
248- test_plan_ids]
249- try:
250- selected_index = self.ctx.display.run(
251- ShowMenu(_("Select test plan"),
252- test_plan_names, [],
253- multiple_allowed=False))[0]
254- except IndexError:
255- return None
256- return test_plan_ids[selected_index]
257-
258- def _interactively_pick_jobs_to_run(self):
259- job_list = [self.ctx.sa.get_job(job_id) for job_id in
260- self.ctx.sa.get_static_todo_list()]
261- tree = SelectableJobTreeNode.create_simple_tree(self.ctx.sa, job_list)
262- title = _('Choose tests to run on your system:')
263- self.ctx.display.run(ScrollableTreeNode(tree, title))
264- # NOTE: tree.selection is correct but ordered badly. To retain
265- # the original ordering we should just treat it as a mask and
266- # use it to filter jobs from get_static_todo_list.
267- wanted_set = frozenset([job.id for job in tree.selection])
268- job_id_list = [job_id for job_id in self.ctx.sa.get_static_todo_list()
269- if job_id in wanted_set]
270- self.ctx.sa.use_alternate_selection(job_id_list)
271-
272- @property
273- def is_interactive(self):
274- """
275- Flag indicating that this is an interactive invocation.
276-
277- We can then interact with the user when we encounter OUTCOME_UNDECIDED.
278- """
279- return (sys.stdin.isatty() and sys.stdout.isatty() and not
280- self.ctx.args.non_interactive)
281-
282- def _get_ui_for_job(self, job):
283- if self.ctx.args.dont_suppress_output is False and job.plugin in (
284- 'local', 'resource', 'attachment'):
285- return CheckboxUI(self.C.c, show_cmd_output=False)
286- else:
287- return CheckboxUI(self.C.c, show_cmd_output=True)
288-
289- def _run_single_job_with_ui_loop(self, job, ui):
290- print(self.C.header(job.tr_summary(), fill='-'))
291- print(_("ID: {0}").format(job.id))
292- print(_("Category: {0}").format(
293- self.ctx.sa.get_job_state(job.id).effective_category_id))
294- comments = ""
295- while True:
296- if job.plugin in ('user-interact', 'user-interact-verify',
297- 'user-verify', 'manual'):
298- ui.notify_about_purpose(job)
299- if (self.is_interactive and
300- job.plugin in ('user-interact',
301- 'user-interact-verify',
302- 'manual')):
303- ui.notify_about_steps(job)
304- if job.plugin == 'manual':
305- cmd = 'run'
306- else:
307- cmd = ui.wait_for_interaction_prompt(job)
308- if cmd == 'run' or cmd is None:
309- result_builder = self.ctx.sa.run_job(job.id, ui, False)
310- elif cmd == 'comment':
311- new_comment = input(self.C.BLUE(
312- _('Please enter your comments:') + '\n'))
313- if new_comment:
314- comments += new_comment + '\n'
315- continue
316- elif cmd == 'skip':
317- result_builder = JobResultBuilder(
318- outcome=IJobResult.OUTCOME_SKIP,
319- comments=_("Explicitly skipped before"
320- " execution"))
321- if comments != "":
322- result_builder.comments = comments
323- break
324- elif cmd == 'quit':
325- raise SystemExit()
326- else:
327- result_builder = self.ctx.sa.run_job(job.id, ui, False)
328- else:
329- if 'noreturn' in job.get_flag_set():
330- ui.noreturn_job()
331- result_builder = self.ctx.sa.run_job(job.id, ui, False)
332- if (self.is_interactive and
333- result_builder.outcome == IJobResult.OUTCOME_UNDECIDED):
334- try:
335- if comments != "":
336- result_builder.comments = comments
337- ui.notify_about_verification(job)
338- self._interaction_callback(job, result_builder)
339- except ReRunJob:
340- self.ctx.sa.use_job_result(job.id,
341- result_builder.get_result())
342- continue
343- break
344- return result_builder
345-
346- def _pick_action_cmd(self, action_list, prompt=None):
347- return ActionUI(action_list, prompt).run()
348-
349- def _interaction_callback(self, job, result_builder,
350- prompt=None, allowed_outcome=None):
351- result = result_builder.get_result()
352- if prompt is None:
353- prompt = _("Select an outcome or an action: ")
354- if allowed_outcome is None:
355- allowed_outcome = [IJobResult.OUTCOME_PASS,
356- IJobResult.OUTCOME_FAIL,
357- IJobResult.OUTCOME_SKIP]
358- allowed_actions = [
359- Action('c', _('add a comment'), 'set-comments')
360- ]
361- if IJobResult.OUTCOME_PASS in allowed_outcome:
362- allowed_actions.append(
363- Action('p', _('set outcome to {0}').format(
364- self.C.GREEN(C_('set outcome to <pass>', 'pass'))),
365- 'set-pass'))
366- if IJobResult.OUTCOME_FAIL in allowed_outcome:
367- allowed_actions.append(
368- Action('f', _('set outcome to {0}').format(
369- self.C.RED(C_('set outcome to <fail>', 'fail'))),
370- 'set-fail'))
371- if IJobResult.OUTCOME_SKIP in allowed_outcome:
372- allowed_actions.append(
373- Action('s', _('set outcome to {0}').format(
374- self.C.YELLOW(C_('set outcome to <skip>', 'skip'))),
375- 'set-skip'))
376- if job.command is not None:
377- allowed_actions.append(
378- Action('r', _('re-run this job'), 're-run'))
379- if result.return_code is not None:
380- if result.return_code == 0:
381- suggested_outcome = IJobResult.OUTCOME_PASS
382- else:
383- suggested_outcome = IJobResult.OUTCOME_FAIL
384- allowed_actions.append(
385- Action('', _('set suggested outcome [{0}]').format(
386- tr_outcome(suggested_outcome)), 'set-suggested'))
387- while result.outcome not in allowed_outcome:
388- print(_("Please decide what to do next:"))
389- print(" " + _("outcome") + ": {0}".format(
390- self.C.result(result)))
391- if result.comments is None:
392- print(" " + _("comments") + ": {0}".format(
393- C_("none comment", "none")))
394- else:
395- print(" " + _("comments") + ": {0}".format(
396- self.C.CYAN(result.comments, bright=False)))
397- cmd = self._pick_action_cmd(allowed_actions)
398- if cmd == 'set-pass':
399- result_builder.outcome = IJobResult.OUTCOME_PASS
400- elif cmd == 'set-fail':
401- result_builder.outcome = IJobResult.OUTCOME_FAIL
402- elif cmd == 'set-skip' or cmd is None:
403- result_builder.outcome = IJobResult.OUTCOME_SKIP
404- elif cmd == 'set-suggested':
405- result_builder.outcome = suggested_outcome
406- elif cmd == 'set-comments':
407- new_comment = input(self.C.BLUE(
408- _('Please enter your comments:') + '\n'))
409- if new_comment:
410- result_builder.add_comment(new_comment)
411- elif cmd == 're-run':
412- raise ReRunJob
413- result = result_builder.get_result()
414-
415- def _run_jobs(self, jobs_to_run):
416- estimated_time = 0
417- for job_id in jobs_to_run:
418- job = self.ctx.sa.get_job(job_id)
419- if (job.estimated_duration is not None
420- and estimated_time is not None):
421- estimated_time += job.estimated_duration
422- else:
423- estimated_time = None
424- for job_no, job_id in enumerate(jobs_to_run, start=1):
425- print(self.C.header(
426- _('Running job {} / {}. Estimated time left: {}').format(
427- job_no, len(jobs_to_run),
428- seconds_to_human_duration(max(0, estimated_time))
429- if estimated_time is not None else _("unknown")),
430- fill='-'))
431- job = self.ctx.sa.get_job(job_id)
432- builder = self._run_single_job_with_ui_loop(
433- job, self._get_ui_for_job(job))
434- result = builder.get_result()
435- self.ctx.sa.use_job_result(job_id, result)
436- if (job.estimated_duration is not None
437- and estimated_time is not None):
438- estimated_time -= job.estimated_duration
439-
440- def _get_rerun_candidates(self):
441- """Get all the tests that might be selected for rerunning."""
442- def rerun_predicate(job_state):
443- return job_state.result.outcome in (
444- IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH,
445- IJobResult.OUTCOME_NOT_SUPPORTED, IJobResult.OUTCOME_SKIP)
446- rerun_candidates = []
447- todo_list = self.ctx.sa.get_static_todo_list()
448- job_states = {job_id: self.ctx.sa.get_job_state(job_id) for job_id
449- in todo_list}
450- for job_id, job_state in job_states.items():
451- if rerun_predicate(job_state):
452- rerun_candidates.append(self.ctx.sa.get_job(job_id))
453- return rerun_candidates
454-
455- def _maybe_rerun_jobs(self):
456- # create a list of jobs that qualify for rerunning
457- rerun_candidates = self._get_rerun_candidates()
458- # bail-out early if no job qualifies for rerunning
459- if not rerun_candidates:
460- return False
461- tree = SelectableJobTreeNode.create_simple_tree(self.ctx.sa,
462- rerun_candidates)
463- # nothing to select in root node and categories - bailing out
464- if not tree.jobs and not tree._categories:
465- return False
466- # deselect all by default
467- tree.set_descendants_state(False)
468- self.ctx.display.run(ShowRerun(tree, _("Select jobs to re-run")))
469- wanted_set = frozenset(tree.selection)
470- if not wanted_set:
471- # nothing selected - nothing to run
472- return False
473- rerun_candidates = []
474- # include resource jobs that selected jobs depend on
475- resources_to_rerun = []
476- for job in wanted_set:
477- job_state = self.ctx.sa.get_job_state(job.id)
478- for inhibitor in job_state.readiness_inhibitor_list:
479- if inhibitor.cause == InhibitionCause.FAILED_DEP:
480- resources_to_rerun.append(inhibitor.related_job)
481- # reset outcome of jobs that are selected for re-running
482- for job in list(wanted_set) + resources_to_rerun:
483- self.ctx.sa.get_job_state(job.id).result = MemoryJobResult({})
484- rerun_candidates.append(job.id)
485- self._run_jobs(rerun_candidates)
486- return True
487-
488- def _do_normal_sequence(self):
489- self.ctx.sa.select_providers("*")
490- self.ctx.sa.configure_application_restart(
491- lambda session_id: [
492- 'sh', '-c', ' '.join([
493- os.path.abspath(__file__),
494- "--resume", session_id])
495- ])
496- resumed = self._maybe_resume_session()
497- if not resumed:
498- print(_("Preparing..."))
499- self.ctx.sa.start_new_session(_("Checkbox CLI Session"))
500- testplan_id = None
501- if self.ctx.args.test_plan:
502- if self.ctx.args.test_plan in self.ctx.sa.get_test_plans():
503- testplan_id = self.ctx.args.test_plan
504- elif self.is_interactive:
505- testplan_id = self._get_interactively_picked_testplans()
506- if not testplan_id:
507- self.ctx.rc.reset()
508- self.ctx.rc.bg = 'red'
509- self.ctx.rc.fg = 'bright_white'
510- self.ctx.rc.bold = 1
511- self.ctx.rc.para(_("Test plan not found!"))
512- raise SystemExit(1)
513- self.ctx.sa.select_test_plan(testplan_id)
514- self.ctx.sa.update_app_blob(json.dumps(
515- {'testplan_id': testplan_id, }).encode("UTF-8"))
516- self.ctx.sa.bootstrap()
517- if self.is_interactive:
518- self._interactively_pick_jobs_to_run()
519- self._run_jobs(self.ctx.sa.get_dynamic_todo_list())
520- if self.is_interactive:
521- while True:
522- if self._maybe_rerun_jobs():
523- continue
524- else:
525- break
526-
527- def _handle_last_job_after_resume(self, metadata):
528- last_job = metadata.running_job_name
529- if last_job is None:
530- return
531- print(_("Previous session run tried to execute job: {}").format(
532- last_job))
533- cmd = self._pick_action_cmd([
534- Action('s', _("skip that job"), 'skip'),
535- Action('p', _("mark it as passed and continue"), 'pass'),
536- Action('f', _("mark it as failed and continue"), 'fail'),
537- Action('r', _("run it again"), 'run'),
538- ], _("What do you want to do with that job?"))
539- if cmd == 'skip' or cmd is None:
540- result = MemoryJobResult({
541- 'outcome': IJobResult.OUTCOME_SKIP,
542- 'comments': _("Skipped after resuming execution")
543- })
544- elif cmd == 'pass':
545- result = MemoryJobResult({
546- 'outcome': IJobResult.OUTCOME_PASS,
547- 'comments': _("Passed after resuming execution")
548- })
549- elif cmd == 'fail':
550- result = MemoryJobResult({
551- 'outcome': IJobResult.OUTCOME_FAIL,
552- 'comments': _("Failed after resuming execution")
553- })
554- elif cmd == 'run':
555- result = None
556- if result:
557- self.ctx.sa.use_job_result(last_job, result)
558-
559- def _maybe_resume_session(self):
560- # Try to use the first session that can be resumed if the user agrees
561- resume_candidates = list(self.ctx.sa.get_resumable_sessions())
562- resumed = False
563- if resume_candidates:
564- if self.ctx.args.session_id:
565- for candidate in resume_candidates:
566- if candidate.id == self.ctx.args.session_id:
567- resume_candidates = (candidate, )
568- break
569- else:
570- raise RuntimeError("Requested session is not resumable!")
571- elif self.is_interactive:
572- print(self.C.header(_("Resume Incomplete Session")))
573- print(ngettext(
574- "There is {0} incomplete session that might be resumed",
575- "There are {0} incomplete sessions that might be resumed",
576- len(resume_candidates)
577- ).format(len(resume_candidates)))
578- else:
579- return
580- for candidate in resume_candidates:
581- if self.ctx.args.session_id:
582- cmd = 'resume'
583- else:
584- # Skip sessions that the user doesn't want to resume
585- cmd = self._pick_action_cmd([
586- Action('r', _("resume this session"), 'resume'),
587- Action('n', _("next session"), 'next'),
588- Action('c', _("create new session"), 'create')
589- ], _("Do you want to resume session {0!a}?").format(
590- candidate.id))
591- if cmd == 'next':
592- continue
593- elif cmd == 'create' or cmd is None:
594- break
595- elif cmd == 'resume':
596- metadata = self.ctx.sa.resume_session(candidate.id)
597- app_blob = json.loads(metadata.app_blob.decode("UTF-8"))
598- test_plan_id = app_blob['testplan_id']
599- # FIXME selecting again the testplan on resume resets both
600- # the static and dynamic todo lists.
601- # We're then saving the selection from the saved run_list
602- # by accessing the session private context object.
603- selected_id_list = [job.id for job in
604- self.ctx.sa._context.state.run_list]
605- self.ctx.sa.select_test_plan(test_plan_id)
606- self.ctx.sa.bootstrap()
607- self.ctx.sa.use_alternate_selection(selected_id_list)
608- # If we resumed maybe not rerun the same, probably broken
609- # job
610- self._handle_last_job_after_resume(metadata)
611- self._run_jobs(self.ctx.sa.get_dynamic_todo_list())
612- resumed = True
613- # Finally ignore other sessions that can be resumed
614- break
615- return resumed
616-
617-
618-if __name__ == '__main__':
619- checkbox_cli().main()
620
621=== modified file 'plainbox/plainbox/impl/session/restart.py'
622--- plainbox/plainbox/impl/session/restart.py 2016-05-04 09:21:57 +0000
623+++ plainbox/plainbox/impl/session/restart.py 2016-05-19 19:44:13 +0000
624@@ -22,6 +22,7 @@
625 import abc
626 import errno
627 import os
628+import shlex
629 import subprocess
630
631 from plainbox.impl.secure.config import PlainBoxConfigParser
632@@ -161,13 +162,21 @@
633 In this stategy plainbox will create and enable a systemd unit that
634 will be run when the OS resumes.
635 """
636+ cmd = shlex.split(cmd)[0]
637 snap_name = os.getenv('SNAP_NAME')
638+ data_path = os.getenv('SNAP_DATA')
639+ base_dir = 'snap'
640+ if os.getenv("SNAP_APP_PATH"):
641+ data_path = os.getenv('SNAP_APP_DATA_PATH')
642+ base_dir = 'apps'
643 # NOTE: This implies that any snap wishing to include a Checkbox
644 # application to be autostarted creates snapcraft binary
645- # called "checkbox-autostart"
646- self.config.set('Service', 'ExecStart',
647- '/apps/bin/{}.checkbox-autostart --resume {}'.format(
648- snap_name, session_id))
649+ # called "checkbox-cli"
650+ binary_name = '/{}/bin/{}.checkbox-cli'.format(base_dir, snap_name)
651+ self.config.set('Service', 'Environment',
652+ '"PLAINBOX_SESSION_REPOSITORY={}"'.format(data_path))
653+ self.config.set('Service', 'ExecStart', '{} {}'.format(
654+ binary_name, ' '.join(cmd.split()[1:])))
655 filename = self.get_autostart_config_filename()
656 os.makedirs(os.path.dirname(filename), exist_ok=True)
657 with open(filename, 'wt') as stream:

Subscribers

People subscribed via source and target branches